Merge "Add metrics for Setting dialogs."
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
index 13a5e83..ce0c357 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
@@ -41,7 +41,6 @@
 import android.perftests.utils.StubActivity;
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
-import android.support.test.annotation.UiThreadTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.InstrumentationRegistry;
 
@@ -106,20 +105,21 @@
     }
 
     @Test
-    @UiThreadTest
-    public void testEditText() {
-        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        final EditText editText = setupEditText();
-        final KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
-        final int steps = 100;
-        while (state.keepRunning()) {
-            for (int i = 0; i < steps; i++) {
-                int offset = (editText.getText().length() * i) / steps;
-                editText.setSelection(offset);
-                editText.bringPointIntoView(offset);
-                editText.onKeyDown(keyEvent.getKeyCode(), keyEvent);
-                editText.updateDisplayListIfDirty();
+    public void testEditText() throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+            final EditText editText = setupEditText();
+            final KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
+            final int steps = 100;
+            while (state.keepRunning()) {
+                for (int i = 0; i < steps; i++) {
+                    int offset = (editText.getText().length() * i) / steps;
+                    editText.setSelection(offset);
+                    editText.bringPointIntoView(offset);
+                    editText.onKeyDown(keyEvent.getKeyCode(), keyEvent);
+                    editText.updateDisplayListIfDirty();
+                }
             }
-        }
+        });
     }
 }
diff --git a/cmds/wm/src/com/android/commands/wm/Wm.java b/cmds/wm/src/com/android/commands/wm/Wm.java
index 383cd01..84fb626 100644
--- a/cmds/wm/src/com/android/commands/wm/Wm.java
+++ b/cmds/wm/src/com/android/commands/wm/Wm.java
@@ -21,16 +21,22 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.AndroidException;
 import android.util.DisplayMetrics;
+import android.system.Os;
 import android.view.Display;
 import android.view.IWindowManager;
 import com.android.internal.os.BaseCommand;
 
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.DataInputStream;
 import java.io.PrintStream;
+import java.lang.Runtime;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -69,7 +75,9 @@
                 "wm screen-capture: enable/disable screen capture.\n" +
                 "\n" +
                 "wm dismiss-keyguard: dismiss the keyguard, prompting the user for auth if " +
-                "necessary.\n"
+                "necessary.\n" +
+                "\n" +
+                "wm surface-trace: log surface commands to stdout in a binary format.\n"
                 );
     }
 
@@ -96,12 +104,29 @@
             runSetScreenCapture();
         } else if (op.equals("dismiss-keyguard")) {
             runDismissKeyguard();
+        } else if (op.equals("surface-trace")) {
+            runSurfaceTrace();
         } else {
             showError("Error: unknown command '" + op + "'");
             return;
         }
     }
 
+    private void runSurfaceTrace() throws Exception {
+        ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(FileDescriptor.out);
+        mWm.enableSurfaceTrace(pfd);
+
+        try {
+            // No one is going to wake us up, we are just waiting on SIGINT. Otherwise
+            // the WM can happily continue writing to our stdout.
+            synchronized (this) {
+                this.wait();
+            }
+        } finally {
+            mWm.disableSurfaceTrace();
+        }
+    }
+
     private void runSetScreenCapture() throws Exception {
         String userIdStr = nextArg();
         String enableStr = nextArg();
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2595c45..680e518 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1233,7 +1233,7 @@
                     getIcon(),
                     title,
                     actionIntent, // safe to alias
-                    new Bundle(mExtras),
+                    mExtras == null ? new Bundle() : new Bundle(mExtras),
                     getRemoteInputs(),
                     getAllowGeneratedReplies());
         }
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 35d3dc3..ee1f190 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1139,78 +1139,88 @@
      * PackageManager.ActivityInfo.CONFIG_LAYOUT_DIRECTION}.
      */
     public int diff(Configuration delta) {
+        return diff(delta, false /* compareUndefined */);
+    }
+
+    /**
+     * Variation of {@link #diff(Configuration)} with an option to skip checks for undefined values.
+     *
+     * @hide
+     */
+    public int diff(Configuration delta, boolean compareUndefined) {
         int changed = 0;
-        if (delta.fontScale > 0 && fontScale != delta.fontScale) {
+        if ((compareUndefined || delta.fontScale > 0) && fontScale != delta.fontScale) {
             changed |= ActivityInfo.CONFIG_FONT_SCALE;
         }
-        if (delta.mcc != 0 && mcc != delta.mcc) {
+        if ((compareUndefined || delta.mcc != 0) && mcc != delta.mcc) {
             changed |= ActivityInfo.CONFIG_MCC;
         }
-        if (delta.mnc != 0 && mnc != delta.mnc) {
+        if ((compareUndefined || delta.mnc != 0) && mnc != delta.mnc) {
             changed |= ActivityInfo.CONFIG_MNC;
         }
         fixUpLocaleList();
         delta.fixUpLocaleList();
-        if (!delta.mLocaleList.isEmpty() && !mLocaleList.equals(delta.mLocaleList)) {
+        if ((compareUndefined || !delta.mLocaleList.isEmpty())
+                && !mLocaleList.equals(delta.mLocaleList)) {
             changed |= ActivityInfo.CONFIG_LOCALE;
             changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
         }
         final int deltaScreenLayoutDir = delta.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK;
-        if (deltaScreenLayoutDir != SCREENLAYOUT_LAYOUTDIR_UNDEFINED &&
-                deltaScreenLayoutDir != (screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)) {
+        if ((compareUndefined || deltaScreenLayoutDir != SCREENLAYOUT_LAYOUTDIR_UNDEFINED)
+                && deltaScreenLayoutDir != (screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)) {
             changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
         }
-        if (delta.touchscreen != TOUCHSCREEN_UNDEFINED
+        if ((compareUndefined || delta.touchscreen != TOUCHSCREEN_UNDEFINED)
                 && touchscreen != delta.touchscreen) {
             changed |= ActivityInfo.CONFIG_TOUCHSCREEN;
         }
-        if (delta.keyboard != KEYBOARD_UNDEFINED
+        if ((compareUndefined || delta.keyboard != KEYBOARD_UNDEFINED)
                 && keyboard != delta.keyboard) {
             changed |= ActivityInfo.CONFIG_KEYBOARD;
         }
-        if (delta.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED
+        if ((compareUndefined || delta.keyboardHidden != KEYBOARDHIDDEN_UNDEFINED)
                 && keyboardHidden != delta.keyboardHidden) {
             changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
         }
-        if (delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED
+        if ((compareUndefined || delta.hardKeyboardHidden != HARDKEYBOARDHIDDEN_UNDEFINED)
                 && hardKeyboardHidden != delta.hardKeyboardHidden) {
             changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
         }
-        if (delta.navigation != NAVIGATION_UNDEFINED
+        if ((compareUndefined || delta.navigation != NAVIGATION_UNDEFINED)
                 && navigation != delta.navigation) {
             changed |= ActivityInfo.CONFIG_NAVIGATION;
         }
-        if (delta.navigationHidden != NAVIGATIONHIDDEN_UNDEFINED
+        if ((compareUndefined || delta.navigationHidden != NAVIGATIONHIDDEN_UNDEFINED)
                 && navigationHidden != delta.navigationHidden) {
             changed |= ActivityInfo.CONFIG_KEYBOARD_HIDDEN;
         }
-        if (delta.orientation != ORIENTATION_UNDEFINED
+        if ((compareUndefined || delta.orientation != ORIENTATION_UNDEFINED)
                 && orientation != delta.orientation) {
             changed |= ActivityInfo.CONFIG_ORIENTATION;
         }
-        if (getScreenLayoutNoDirection(delta.screenLayout) !=
-                    (SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED)
+        if ((compareUndefined || getScreenLayoutNoDirection(delta.screenLayout) !=
+                (SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED))
                 && getScreenLayoutNoDirection(screenLayout) !=
-                    getScreenLayoutNoDirection(delta.screenLayout)) {
+                getScreenLayoutNoDirection(delta.screenLayout)) {
             changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
         }
-        if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED)
+        if ((compareUndefined || delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED))
                 && uiMode != delta.uiMode) {
             changed |= ActivityInfo.CONFIG_UI_MODE;
         }
-        if (delta.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED
+        if ((compareUndefined || delta.screenWidthDp != SCREEN_WIDTH_DP_UNDEFINED)
                 && screenWidthDp != delta.screenWidthDp) {
             changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
         }
-        if (delta.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED
+        if ((compareUndefined || delta.screenHeightDp != SCREEN_HEIGHT_DP_UNDEFINED)
                 && screenHeightDp != delta.screenHeightDp) {
             changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
         }
-        if (delta.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+        if ((compareUndefined || delta.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED)
                 && smallestScreenWidthDp != delta.smallestScreenWidthDp) {
             changed |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
         }
-        if (delta.densityDpi != DENSITY_DPI_UNDEFINED
+        if ((compareUndefined || delta.densityDpi != DENSITY_DPI_UNDEFINED)
                 && densityDpi != delta.densityDpi) {
             changed |= ActivityInfo.CONFIG_DENSITY;
         }
diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java
index 9478dc0..45fa15e 100644
--- a/core/java/android/hardware/camera2/DngCreator.java
+++ b/core/java/android/hardware/camera2/DngCreator.java
@@ -27,6 +27,7 @@
 import android.media.ExifInterface;
 import android.media.Image;
 import android.os.SystemClock;
+import android.util.Log;
 import android.util.Size;
 
 import java.io.IOException;
@@ -89,17 +90,33 @@
             throw new IllegalArgumentException("Null argument to DngCreator constructor");
         }
 
-        // Find current time
+        // Find current time in milliseconds since 1970
         long currentTime = System.currentTimeMillis();
+        // Assume that sensor timestamp has that timebase to start
+        long timeOffset = 0;
 
-        // Find boot time
-        long bootTimeMillis = currentTime - SystemClock.elapsedRealtime();
+        int timestampSource = characteristics.get(
+                CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
+
+        if (timestampSource == CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME) {
+            // This means the same timebase as SystemClock.elapsedRealtime(),
+            // which is CLOCK_BOOTTIME
+            timeOffset = currentTime - SystemClock.elapsedRealtime();
+        } else if (timestampSource == CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN) {
+            // This means the same timebase as System.currentTimeMillis(),
+            // which is CLOCK_MONOTONIC
+            timeOffset = currentTime - SystemClock.uptimeMillis();
+        } else {
+            // Unexpected time source - treat as CLOCK_MONOTONIC
+            Log.w(TAG, "Sensor timestamp source is unexpected: " + timestampSource);
+            timeOffset = currentTime - SystemClock.uptimeMillis();
+        }
 
         // Find capture time (nanos since boot)
         Long timestamp = metadata.get(CaptureResult.SENSOR_TIMESTAMP);
         long captureTime = currentTime;
         if (timestamp != null) {
-            captureTime = timestamp / 1000000 + bootTimeMillis;
+            captureTime = timestamp / 1000000 + timeOffset;
         }
 
         // Format for metadata
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index ec1e102..2bfe33b 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -58,14 +58,14 @@
  * This class contains a {@code key} that will be used as the key into the
  * {@link SharedPreferences}. It is up to the subclass to decide how to store
  * the value.
- * 
+ *
  * <div class="special reference">
  * <h3>Developer Guides</h3>
  * <p>For information about building a settings UI with Preferences,
  * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
  * guide.</p>
  * </div>
- * 
+ *
  * @attr ref android.R.styleable#Preference_icon
  * @attr ref android.R.styleable#Preference_key
  * @attr ref android.R.styleable#Preference_title
@@ -89,13 +89,13 @@
 
     private Context mContext;
     private PreferenceManager mPreferenceManager;
-    
+
     /**
      * Set when added to hierarchy since we need a unique ID within that
      * hierarchy.
      */
     private long mId;
-    
+
     private OnPreferenceChangeListener mOnChangeListener;
     private OnPreferenceClickListener mOnClickListener;
 
@@ -120,22 +120,22 @@
     private Object mDefaultValue;
     private boolean mDependencyMet = true;
     private boolean mParentDependencyMet = true;
-    
+
     /**
      * @see #setShouldDisableView(boolean)
      */
     private boolean mShouldDisableView = true;
-    
+
     private int mLayoutResId = com.android.internal.R.layout.preference;
     private int mWidgetLayoutResId;
     private boolean mCanRecycleLayout = true;
-    
+
     private OnPreferenceChangeInternalListener mListener;
-    
+
     private List<Preference> mDependents;
-    
+
     private boolean mBaseMethodCalled;
-    
+
     /**
      * Interface definition for a callback to be invoked when the value of this
      * {@link Preference} has been changed by the user and is
@@ -147,7 +147,7 @@
          * Called when a Preference has been changed by the user. This is
          * called before the state of the Preference is about to be updated and
          * before the state is persisted.
-         * 
+         *
          * @param preference The changed Preference.
          * @param newValue The new value of the Preference.
          * @return True to update the state of the Preference with the new value.
@@ -177,14 +177,14 @@
     interface OnPreferenceChangeInternalListener {
         /**
          * Called when this Preference has changed.
-         * 
+         *
          * @param preference This preference.
          */
         void onPreferenceChange(Preference preference);
-        
+
         /**
          * Called when this group has added/removed {@link Preference}(s).
-         * 
+         *
          * @param preference This Preference.
          */
         void onPreferenceHierarchyChange(Preference preference);
@@ -220,7 +220,7 @@
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes);
         for (int i = a.getIndexCount() - 1; i >= 0; i--) {
-            int attr = a.getIndex(i); 
+            int attr = a.getIndex(i);
             switch (attr) {
                 case com.android.internal.R.styleable.Preference_icon:
                     mIconResId = a.getResourceId(attr, 0);
@@ -229,16 +229,16 @@
                 case com.android.internal.R.styleable.Preference_key:
                     mKey = a.getString(attr);
                     break;
-                    
+
                 case com.android.internal.R.styleable.Preference_title:
                     mTitleRes = a.getResourceId(attr, 0);
-                    mTitle = a.getString(attr);
+                    mTitle = a.getText(attr);
                     break;
-                    
+
                 case com.android.internal.R.styleable.Preference_summary:
-                    mSummary = a.getString(attr);
+                    mSummary = a.getText(attr);
                     break;
-                    
+
                 case com.android.internal.R.styleable.Preference_order:
                     mOrder = a.getInt(attr, mOrder);
                     break;
@@ -254,27 +254,27 @@
                 case com.android.internal.R.styleable.Preference_widgetLayout:
                     mWidgetLayoutResId = a.getResourceId(attr, mWidgetLayoutResId);
                     break;
-                    
+
                 case com.android.internal.R.styleable.Preference_enabled:
                     mEnabled = a.getBoolean(attr, true);
                     break;
-                    
+
                 case com.android.internal.R.styleable.Preference_selectable:
                     mSelectable = a.getBoolean(attr, true);
                     break;
-                    
+
                 case com.android.internal.R.styleable.Preference_persistent:
                     mPersistent = a.getBoolean(attr, mPersistent);
                     break;
-                    
+
                 case com.android.internal.R.styleable.Preference_dependency:
                     mDependencyKey = a.getString(attr);
                     break;
-                    
+
                 case com.android.internal.R.styleable.Preference_defaultValue:
                     mDefaultValue = onGetDefaultValue(a, attr);
                     break;
-                    
+
                 case com.android.internal.R.styleable.Preference_shouldDisableView:
                     mShouldDisableView = a.getBoolean(attr, mShouldDisableView);
                     break;
@@ -312,14 +312,14 @@
     public Preference(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
     }
-    
+
     /**
      * Constructor that is called when inflating a Preference from XML. This is
      * called when a Preference is being constructed from an XML file, supplying
      * attributes that were specified in the XML file. This version uses a
      * default style of 0, so the only attribute values applied are those in the
      * Context's Theme and the given AttributeSet.
-     * 
+     *
      * @param context The Context this is associated with, through which it can
      *            access the current theme, resources, {@link SharedPreferences},
      *            etc.
@@ -333,7 +333,7 @@
 
     /**
      * Constructor to create a Preference.
-     * 
+     *
      * @param context The Context in which to store Preference values.
      */
     public Preference(Context context) {
@@ -348,7 +348,7 @@
      * <p>
      * For example, if the value type is String, the body of the method would
      * proxy to {@link TypedArray#getString(int)}.
-     * 
+     *
      * @param a The set of attributes.
      * @param index The index of the default value attribute.
      * @return The default value of this preference type.
@@ -356,20 +356,20 @@
     protected Object onGetDefaultValue(TypedArray a, int index) {
         return null;
     }
-    
+
     /**
      * Sets an {@link Intent} to be used for
      * {@link Context#startActivity(Intent)} when this Preference is clicked.
-     * 
+     *
      * @param intent The intent associated with this Preference.
      */
     public void setIntent(Intent intent) {
         mIntent = intent;
     }
-    
+
     /**
      * Return the {@link Intent} associated with this Preference.
-     * 
+     *
      * @return The {@link Intent} last set via {@link #setIntent(Intent)} or XML. 
      */
     public Intent getIntent() {
@@ -423,7 +423,7 @@
      * {@link android.R.id#widget_frame} to be the parent of the specific widget
      * for this Preference. It should similarly contain
      * {@link android.R.id#title} and {@link android.R.id#summary}.
-     * 
+     *
      * @param layoutResId The layout resource ID to be inflated and returned as
      *            a {@link View}.
      * @see #setWidgetLayoutResource(int)
@@ -436,23 +436,23 @@
 
         mLayoutResId = layoutResId;
     }
-    
+
     /**
      * Gets the layout resource that will be shown as the {@link View} for this Preference.
-     * 
+     *
      * @return The layout resource ID.
      */
     @LayoutRes
     public int getLayoutResource() {
         return mLayoutResId;
     }
-    
+
     /**
      * Sets the layout for the controllable widget portion of this Preference. This
      * is inflated into the main layout. For example, a {@link CheckBoxPreference}
      * would specify a custom layout (consisting of just the CheckBox) here,
      * instead of creating its own main layout.
-     * 
+     *
      * @param widgetLayoutResId The layout resource ID to be inflated into the
      *            main layout.
      * @see #setLayoutResource(int)
@@ -467,17 +467,17 @@
 
     /**
      * Gets the layout resource for the controllable widget portion of this Preference.
-     * 
+     *
      * @return The layout resource ID.
      */
     @LayoutRes
     public int getWidgetLayoutResource() {
         return mWidgetLayoutResId;
     }
-    
+
     /**
      * Gets the View that will be shown in the {@link PreferenceActivity}.
-     * 
+     *
      * @param convertView The old View to reuse, if possible. Note: You should
      *            check that this View is non-null and of an appropriate type
      *            before using. If it is not possible to convert this View to
@@ -495,7 +495,7 @@
         onBindView(convertView);
         return convertView;
     }
-    
+
     /**
      * Creates the View to be shown for this Preference in the
      * {@link PreferenceActivity}. The default behavior is to inflate the main
@@ -504,7 +504,7 @@
      * {@link android.R.id#widget_frame}.
      * <p>
      * Make sure to call through to the superclass's implementation.
-     * 
+     *
      * @param parent The parent that this View will eventually be attached to.
      * @return The View that displays this Preference.
      * @see #onBindView(View)
@@ -512,10 +512,10 @@
     @CallSuper
     protected View onCreateView(ViewGroup parent) {
         final LayoutInflater layoutInflater =
-            (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-        
-        final View layout = layoutInflater.inflate(mLayoutResId, parent, false); 
-        
+                (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        final View layout = layoutInflater.inflate(mLayoutResId, parent, false);
+
         final ViewGroup widgetFrame = (ViewGroup) layout
                 .findViewById(com.android.internal.R.id.widget_frame);
         if (widgetFrame != null) {
@@ -527,7 +527,7 @@
         }
         return layout;
     }
-    
+
     /**
      * Binds the created View to the data for this Preference.
      * <p>
@@ -535,7 +535,7 @@
      * set properties on them.
      * <p>
      * Make sure to call through to the superclass's implementation.
-     * 
+     *
      * @param view The View that shows this Preference.
      * @see #onCreateView(ViewGroup)
      */
@@ -592,7 +592,7 @@
      */
     private void setEnabledStateOnViews(View v, boolean enabled) {
         v.setEnabled(enabled);
-        
+
         if (v instanceof ViewGroup) {
             final ViewGroup vg = (ViewGroup) v;
             for (int i = vg.getChildCount() - 1; i >= 0; i--) {
@@ -600,14 +600,14 @@
             }
         }
     }
-    
+
     /**
      * Sets the order of this Preference with respect to other
      * Preference objects on the same level. If this is not specified, the
      * default behavior is to sort alphabetically. The
      * {@link PreferenceGroup#setOrderingAsAdded(boolean)} can be used to order
      * Preference objects based on the order they appear in the XML.
-     * 
+     *
      * @param order The order for this Preference. A lower value will be shown
      *            first. Use {@link #DEFAULT_ORDER} to sort alphabetically or
      *            allow ordering from XML.
@@ -622,11 +622,11 @@
             notifyHierarchyChanged();
         }
     }
-    
+
     /**
      * Gets the order of this Preference with respect to other Preference objects
      * on the same level.
-     * 
+     *
      * @return The order of this Preference.
      * @see #setOrder(int)
      */
@@ -639,7 +639,7 @@
      * This title will be placed into the ID
      * {@link android.R.id#title} within the View created by
      * {@link #onCreateView(ViewGroup)}.
-     * 
+     *
      * @param title The title for this Preference.
      */
     public void setTitle(CharSequence title) {
@@ -649,10 +649,10 @@
             notifyChanged();
         }
     }
-    
+
     /**
      * Sets the title for this Preference with a resource ID. 
-     * 
+     *
      * @see #setTitle(CharSequence)
      * @param titleResId The title as a resource ID.
      */
@@ -660,7 +660,7 @@
         setTitle(mContext.getString(titleResId));
         mTitleRes = titleResId;
     }
-    
+
     /**
      * Returns the title resource ID of this Preference.  If the title did
      * not come from a resource, 0 is returned.
@@ -675,7 +675,7 @@
 
     /**
      * Returns the title of this Preference.
-     * 
+     *
      * @return The title.
      * @see #setTitle(CharSequence)
      */
@@ -688,7 +688,7 @@
      * This icon will be placed into the ID
      * {@link android.R.id#icon} within the View created by
      * {@link #onCreateView(ViewGroup)}.
-     * 
+     *
      * @param icon The optional icon for this Preference.
      */
     public void setIcon(Drawable icon) {
@@ -701,7 +701,7 @@
 
     /**
      * Sets the icon for this Preference with a resource ID. 
-     * 
+     *
      * @see #setIcon(Drawable)
      * @param iconResId The icon as a resource ID.
      */
@@ -714,7 +714,7 @@
 
     /**
      * Returns the icon of this Preference.
-     * 
+     *
      * @return The icon.
      * @see #setIcon(Drawable)
      */
@@ -727,7 +727,7 @@
 
     /**
      * Returns the summary of this Preference.
-     * 
+     *
      * @return The summary.
      * @see #setSummary(CharSequence)
      */
@@ -737,7 +737,7 @@
 
     /**
      * Sets the summary for this Preference with a CharSequence. 
-     * 
+     *
      * @param summary The summary for the preference.
      */
     public void setSummary(CharSequence summary) {
@@ -749,18 +749,18 @@
 
     /**
      * Sets the summary for this Preference with a resource ID. 
-     * 
+     *
      * @see #setSummary(CharSequence)
      * @param summaryResId The summary as a resource.
      */
     public void setSummary(@StringRes int summaryResId) {
         setSummary(mContext.getString(summaryResId));
     }
-    
+
     /**
      * Sets whether this Preference is enabled. If disabled, it will
      * not handle clicks.
-     * 
+     *
      * @param enabled Set true to enable it.
      */
     public void setEnabled(boolean enabled) {
@@ -773,10 +773,10 @@
             notifyChanged();
         }
     }
-    
+
     /**
      * Checks whether this Preference should be enabled in the list.
-     * 
+     *
      * @return True if this Preference is enabled, false otherwise.
      */
     public boolean isEnabled() {
@@ -785,7 +785,7 @@
 
     /**
      * Sets whether this Preference is selectable.
-     * 
+     *
      * @param selectable Set true to make it selectable.
      */
     public void setSelectable(boolean selectable) {
@@ -794,10 +794,10 @@
             notifyChanged();
         }
     }
-    
+
     /**
      * Checks whether this Preference should be selectable in the list.
-     * 
+     *
      * @return True if it is selectable, false otherwise.
      */
     public boolean isSelectable() {
@@ -811,7 +811,7 @@
      * For example, set this and {@link #setEnabled(boolean)} to false for
      * preferences that are only displaying information and 1) should not be
      * clickable 2) should not have the view set to the disabled state.
-     * 
+     *
      * @param shouldDisableView Set true if this preference should disable its view
      *            when the preference is disabled.
      */
@@ -819,7 +819,7 @@
         mShouldDisableView = shouldDisableView;
         notifyChanged();
     }
-    
+
     /**
      * Checks whether this Preference should disable its view when it's action is disabled.
      * @see #setShouldDisableView(boolean)
@@ -832,13 +832,13 @@
     /**
      * Returns a unique ID for this Preference.  This ID should be unique across all
      * Preference objects in a hierarchy.
-     * 
+     *
      * @return A unique ID for this Preference.
      */
     long getId() {
         return mId;
     }
-    
+
     /**
      * Processes a click on the preference. This includes saving the value to
      * the {@link SharedPreferences}. However, the overridden method should
@@ -847,93 +847,93 @@
      */
     protected void onClick() {
     }
-    
+
     /**
      * Sets the key for this Preference, which is used as a key to the
      * {@link SharedPreferences}. This should be unique for the package.
-     * 
+     *
      * @param key The key for the preference.
      */
     public void setKey(String key) {
         mKey = key;
-        
+
         if (mRequiresKey && !hasKey()) {
             requireKey();
         }
     }
-    
+
     /**
      * Gets the key for this Preference, which is also the key used for storing
      * values into SharedPreferences.
-     * 
+     *
      * @return The key.
      */
     public String getKey() {
         return mKey;
     }
-    
+
     /**
      * Checks whether the key is present, and if it isn't throws an
      * exception. This should be called by subclasses that store preferences in
      * the {@link SharedPreferences}.
-     * 
+     *
      * @throws IllegalStateException If there is no key assigned.
      */
     void requireKey() {
         if (mKey == null) {
             throw new IllegalStateException("Preference does not have a key assigned.");
         }
-        
+
         mRequiresKey = true;
     }
-    
+
     /**
      * Checks whether this Preference has a valid key.
-     * 
+     *
      * @return True if the key exists and is not a blank string, false otherwise.
      */
     public boolean hasKey() {
         return !TextUtils.isEmpty(mKey);
     }
-    
+
     /**
      * Checks whether this Preference is persistent. If it is, it stores its value(s) into
      * the persistent {@link SharedPreferences} storage.
-     * 
+     *
      * @return True if it is persistent.
      */
     public boolean isPersistent() {
         return mPersistent;
     }
-    
+
     /**
      * Checks whether, at the given time this method is called,
      * this Preference should store/restore its value(s) into the
      * {@link SharedPreferences}. This, at minimum, checks whether this
      * Preference is persistent and it currently has a key. Before you
      * save/restore from the {@link SharedPreferences}, check this first.
-     * 
+     *
      * @return True if it should persist the value.
      */
     protected boolean shouldPersist() {
         return mPreferenceManager != null && isPersistent() && hasKey();
     }
-    
+
     /**
      * Sets whether this Preference is persistent. When persistent,
      * it stores its value(s) into the persistent {@link SharedPreferences}
      * storage.
-     * 
+     *
      * @param persistent Set true if it should store its value(s) into the {@link SharedPreferences}.
      */
     public void setPersistent(boolean persistent) {
         mPersistent = persistent;
     }
-    
+
     /**
      * Call this method after the user changes the preference, but before the
      * internal state is set. This allows the client to ignore the user value.
-     * 
+     *
      * @param newValue The new value of this Preference.
      * @return True if the user value should be set as the preference
      *         value (and persisted).
@@ -941,11 +941,11 @@
     protected boolean callChangeListener(Object newValue) {
         return mOnChangeListener == null ? true : mOnChangeListener.onPreferenceChange(this, newValue);
     }
-    
+
     /**
      * Sets the callback to be invoked when this Preference is changed by the
      * user (but before the internal state has been updated).
-     * 
+     *
      * @param onPreferenceChangeListener The callback to be invoked.
      */
     public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) {
@@ -955,7 +955,7 @@
     /**
      * Returns the callback to be invoked when this Preference is changed by the
      * user (but before the internal state has been updated).
-     * 
+     *
      * @return The callback to be invoked.
      */
     public OnPreferenceChangeListener getOnPreferenceChangeListener() {
@@ -964,7 +964,7 @@
 
     /**
      * Sets the callback to be invoked when this Preference is clicked.
-     * 
+     *
      * @param onPreferenceClickListener The callback to be invoked.
      */
     public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) {
@@ -973,7 +973,7 @@
 
     /**
      * Returns the callback to be invoked when this Preference is clicked.
-     * 
+     *
      * @return The callback to be invoked.
      */
     public OnPreferenceClickListener getOnPreferenceClickListener() {
@@ -982,24 +982,24 @@
 
     /**
      * Called when a click should be performed.
-     * 
+     *
      * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click
      *            listener should be called in the proper order (between other
      *            processing). May be null.
      * @hide
      */
     public void performClick(PreferenceScreen preferenceScreen) {
-        
+
         if (!isEnabled()) {
             return;
         }
-        
+
         onClick();
-        
+
         if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
             return;
         }
-        
+
         PreferenceManager preferenceManager = getPreferenceManager();
         if (preferenceManager != null) {
             PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
@@ -1009,7 +1009,7 @@
                 return;
             }
         }
-        
+
         if (mIntent != null) {
             Context context = getContext();
             context.startActivity(mIntent);
@@ -1026,19 +1026,19 @@
     public boolean onKey(View v, int keyCode, KeyEvent event) {
         return false;
     }
-    
+
     /**
      * Returns the {@link android.content.Context} of this Preference. 
      * Each Preference in a Preference hierarchy can be
      * from different Context (for example, if multiple activities provide preferences into a single
      * {@link PreferenceActivity}). This Context will be used to save the Preference values.
-     * 
+     *
      * @return The Context of this Preference.
      */
     public Context getContext() {
         return mContext;
     }
-    
+
     /**
      * Returns the {@link SharedPreferences} where this Preference can read its
      * value(s). Usually, it's easier to use one of the helper read methods:
@@ -1051,7 +1051,7 @@
      * right away and hence not show up in the returned
      * {@link SharedPreferences}, this is intended behavior to improve
      * performance.
-     * 
+     *
      * @return The {@link SharedPreferences} where this Preference reads its
      *         value(s), or null if it isn't attached to a Preference hierarchy.
      * @see #getEditor()
@@ -1060,10 +1060,10 @@
         if (mPreferenceManager == null) {
             return null;
         }
-        
+
         return mPreferenceManager.getSharedPreferences();
     }
-    
+
     /**
      * Returns an {@link SharedPreferences.Editor} where this Preference can
      * save its value(s). Usually it's easier to use one of the helper save
@@ -1076,7 +1076,7 @@
      * In some cases, writes to this will not be committed right away and hence
      * not show up in the SharedPreferences, this is intended behavior to
      * improve performance.
-     * 
+     *
      * @return A {@link SharedPreferences.Editor} where this preference saves
      *         its value(s), or null if it isn't attached to a Preference
      *         hierarchy.
@@ -1087,15 +1087,15 @@
         if (mPreferenceManager == null) {
             return null;
         }
-        
+
         return mPreferenceManager.getEditor();
     }
-    
+
     /**
      * Returns whether the {@link Preference} should commit its saved value(s) in
      * {@link #getEditor()}. This may return false in situations where batch
      * committing is being done (by the manager) to improve performance.
-     * 
+     *
      * @return Whether the Preference should commit its saved value(s).
      * @see #getEditor()
      */
@@ -1103,13 +1103,13 @@
         if (mPreferenceManager == null) {
             return false;
         }
-        
+
         return mPreferenceManager.shouldCommit();
     }
-    
+
     /**
      * Compares Preference objects based on order (if set), otherwise alphabetically on the titles.
-     * 
+     *
      * @param another The Preference to compare to this one.
      * @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>;
      *          greater than 0 if this Preference sorts after <var>another</var>.
@@ -1131,10 +1131,10 @@
             return CharSequences.compareToIgnoreCase(mTitle, another.mTitle);
         }
     }
-    
+
     /**
      * Sets the internal change listener.
-     * 
+     *
      * @param listener The listener.
      * @see #notifyChanged()
      */
@@ -1150,7 +1150,7 @@
             mListener.onPreferenceChange(this);
         }
     }
-    
+
     /**
      * Should be called when a Preference has been
      * added/removed from this group, or the ordering should be
@@ -1164,27 +1164,27 @@
 
     /**
      * Gets the {@link PreferenceManager} that manages this Preference object's tree.
-     * 
+     *
      * @return The {@link PreferenceManager}.
      */
     public PreferenceManager getPreferenceManager() {
         return mPreferenceManager;
     }
-    
+
     /**
      * Called when this Preference has been attached to a Preference hierarchy.
      * Make sure to call the super implementation.
-     * 
+     *
      * @param preferenceManager The PreferenceManager of the hierarchy.
      */
     protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
         mPreferenceManager = preferenceManager;
-        
+
         mId = preferenceManager.getNextId();
-        
+
         dispatchSetInitialValue();
     }
-    
+
     /**
      * Called when the Preference hierarchy has been attached to the
      * {@link PreferenceActivity}. This can also be called when this
@@ -1198,9 +1198,9 @@
     }
 
     private void registerDependency() {
-        
+
         if (TextUtils.isEmpty(mDependencyKey)) return;
-        
+
         Preference preference = findPreferenceInHierarchy(mDependencyKey);
         if (preference != null) {
             preference.registerDependent(this);
@@ -1218,14 +1218,14 @@
             }
         }
     }
-    
+
     /**
      * Finds a Preference in this hierarchy (the whole thing,
      * even above/below your {@link PreferenceScreen} screen break) with the given
      * key.
      * <p>
      * This only functions after we have been attached to a hierarchy.
-     * 
+     *
      * @param key The key of the Preference to find.
      * @return The Preference that uses the given key.
      */
@@ -1233,16 +1233,16 @@
         if (TextUtils.isEmpty(key) || mPreferenceManager == null) {
             return null;
         }
-        
+
         return mPreferenceManager.findPreference(key);
     }
-    
+
     /**
      * Adds a dependent Preference on this Preference so we can notify it.
      * Usually, the dependent Preference registers itself (it's good for it to
      * know it depends on something), so please use
      * {@link Preference#setDependency(String)} on the dependent Preference.
-     * 
+     *
      * @param dependent The dependent Preference that will be enabled/disabled
      *            according to the state of this Preference.
      */
@@ -1250,15 +1250,15 @@
         if (mDependents == null) {
             mDependents = new ArrayList<Preference>();
         }
-        
+
         mDependents.add(dependent);
-        
+
         dependent.onDependencyChanged(this, shouldDisableDependents());
     }
-    
+
     /**
      * Removes a dependent Preference on this Preference.
-     * 
+     *
      * @param dependent The dependent Preference that will be enabled/disabled
      *            according to the state of this Preference.
      * @return Returns the same Preference object, for chaining multiple calls
@@ -1269,21 +1269,21 @@
             mDependents.remove(dependent);
         }
     }
-    
+
     /**
      * Notifies any listening dependents of a change that affects the
      * dependency.
-     * 
+     *
      * @param disableDependents Whether this Preference should disable
      *            its dependents.
      */
     public void notifyDependencyChange(boolean disableDependents) {
         final List<Preference> dependents = mDependents;
-        
+
         if (dependents == null) {
             return;
         }
-        
+
         final int dependentsCount = dependents.size();
         for (int i = 0; i < dependentsCount; i++) {
             dependents.get(i).onDependencyChanged(this, disableDependents);
@@ -1292,7 +1292,7 @@
 
     /**
      * Called when the dependency changes.
-     * 
+     *
      * @param dependency The Preference that this Preference depends on.
      * @param disableDependent Set true to disable this Preference.
      */
@@ -1327,38 +1327,38 @@
     /**
      * Checks whether this preference's dependents should currently be
      * disabled.
-     * 
+     *
      * @return True if the dependents should be disabled, otherwise false.
      */
     public boolean shouldDisableDependents() {
         return !isEnabled();
     }
-    
+
     /**
      * Sets the key of a Preference that this Preference will depend on. If that
      * Preference is not set or is off, this Preference will be disabled.
-     * 
+     *
      * @param dependencyKey The key of the Preference that this depends on.
      */
     public void setDependency(String dependencyKey) {
         // Unregister the old dependency, if we had one
         unregisterDependency();
-        
+
         // Register the new
         mDependencyKey = dependencyKey;
         registerDependency();
     }
-    
+
     /**
      * Returns the key of the dependency on this Preference.
-     * 
+     *
      * @return The key of the dependency.
      * @see #setDependency(String)
      */
     public String getDependency() {
         return mDependencyKey;
     }
-    
+
     /**
      * Called when this Preference is being removed from the hierarchy. You
      * should remove any references to this Preference that you know about. Make
@@ -1368,18 +1368,18 @@
     protected void onPrepareForRemoval() {
         unregisterDependency();
     }
-    
+
     /**
      * Sets the default value for this Preference, which will be set either if
      * persistence is off or persistence is on and the preference is not found
      * in the persistent storage.
-     * 
+     *
      * @param defaultValue The default value.
      */
     public void setDefaultValue(Object defaultValue) {
         mDefaultValue = defaultValue;
     }
-    
+
     private void dispatchSetInitialValue() {
         // By now, we know if we are persistent.
         final boolean shouldPersist = shouldPersist();
@@ -1391,7 +1391,7 @@
             onSetInitialValue(true, null);
         }
     }
-    
+
     /**
      * Implement this to set the initial value of the Preference. 
      * <p>
@@ -1403,7 +1403,7 @@
      * <p>
      * This may not always be called. One example is if it should not persist
      * but there is no default value given.
-     * 
+     *
      * @param restorePersistedValue True to restore the persisted value;
      *            false to use the given <var>defaultValue</var>.
      * @param defaultValue The default value for this Preference. Only use this
@@ -1424,14 +1424,14 @@
             }
         }
     }
-    
+
     /**
      * Attempts to persist a String to the {@link android.content.SharedPreferences}.
      * <p>
      * This will check if this Preference is persistent, get an editor from
      * the {@link PreferenceManager}, put in the string, and check if we should commit (and
      * commit if so).
-     * 
+     *
      * @param value The value to persist.
      * @return True if the Preference is persistent. (This is not whether the
      *         value was persisted, since we may not necessarily commit if there
@@ -1445,7 +1445,7 @@
                 // It's already there, so the same as persisting
                 return true;
             }
-            
+
             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
             editor.putString(mKey, value);
             tryCommit(editor);
@@ -1453,13 +1453,13 @@
         }
         return false;
     }
-    
+
     /**
      * Attempts to get a persisted String from the {@link android.content.SharedPreferences}.
      * <p>
      * This will check if this Preference is persistent, get the SharedPreferences
      * from the {@link PreferenceManager}, and get the value.
-     * 
+     *
      * @param defaultReturnValue The default value to return if either the
      *            Preference is not persistent or the Preference is not in the
      *            shared preferences.
@@ -1471,17 +1471,17 @@
         if (!shouldPersist()) {
             return defaultReturnValue;
         }
-        
+
         return mPreferenceManager.getSharedPreferences().getString(mKey, defaultReturnValue);
     }
-    
+
     /**
      * Attempts to persist a set of Strings to the {@link android.content.SharedPreferences}.
      * <p>
      * This will check if this Preference is persistent, get an editor from
      * the {@link PreferenceManager}, put in the strings, and check if we should commit (and
      * commit if so).
-     * 
+     *
      * @param values The values to persist.
      * @return True if the Preference is persistent. (This is not whether the
      *         value was persisted, since we may not necessarily commit if there
@@ -1495,7 +1495,7 @@
                 // It's already there, so the same as persisting
                 return true;
             }
-            
+
             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
             editor.putStringSet(mKey, values);
             tryCommit(editor);
@@ -1510,7 +1510,7 @@
      * <p>
      * This will check if this Preference is persistent, get the SharedPreferences
      * from the {@link PreferenceManager}, and get the value.
-     * 
+     *
      * @param defaultReturnValue The default value to return if either the
      *            Preference is not persistent or the Preference is not in the
      *            shared preferences.
@@ -1522,13 +1522,13 @@
         if (!shouldPersist()) {
             return defaultReturnValue;
         }
-        
+
         return mPreferenceManager.getSharedPreferences().getStringSet(mKey, defaultReturnValue);
     }
-    
+
     /**
      * Attempts to persist an int to the {@link android.content.SharedPreferences}.
-     * 
+     *
      * @param value The value to persist.
      * @return True if the Preference is persistent. (This is not whether the
      *         value was persisted, since we may not necessarily commit if there
@@ -1542,7 +1542,7 @@
                 // It's already there, so the same as persisting
                 return true;
             }
-            
+
             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
             editor.putInt(mKey, value);
             tryCommit(editor);
@@ -1550,10 +1550,10 @@
         }
         return false;
     }
-    
+
     /**
      * Attempts to get a persisted int from the {@link android.content.SharedPreferences}.
-     * 
+     *
      * @param defaultReturnValue The default value to return if either this
      *            Preference is not persistent or this Preference is not in the
      *            SharedPreferences.
@@ -1566,13 +1566,13 @@
         if (!shouldPersist()) {
             return defaultReturnValue;
         }
-        
+
         return mPreferenceManager.getSharedPreferences().getInt(mKey, defaultReturnValue);
     }
-    
+
     /**
      * Attempts to persist a float to the {@link android.content.SharedPreferences}.
-     * 
+     *
      * @param value The value to persist.
      * @return True if this Preference is persistent. (This is not whether the
      *         value was persisted, since we may not necessarily commit if there
@@ -1594,10 +1594,10 @@
         }
         return false;
     }
-    
+
     /**
      * Attempts to get a persisted float from the {@link android.content.SharedPreferences}.
-     * 
+     *
      * @param defaultReturnValue The default value to return if either this
      *            Preference is not persistent or this Preference is not in the
      *            SharedPreferences.
@@ -1610,13 +1610,13 @@
         if (!shouldPersist()) {
             return defaultReturnValue;
         }
-        
+
         return mPreferenceManager.getSharedPreferences().getFloat(mKey, defaultReturnValue);
     }
-    
+
     /**
      * Attempts to persist a long to the {@link android.content.SharedPreferences}.
-     * 
+     *
      * @param value The value to persist.
      * @return True if this Preference is persistent. (This is not whether the
      *         value was persisted, since we may not necessarily commit if there
@@ -1630,7 +1630,7 @@
                 // It's already there, so the same as persisting
                 return true;
             }
-            
+
             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
             editor.putLong(mKey, value);
             tryCommit(editor);
@@ -1638,10 +1638,10 @@
         }
         return false;
     }
-    
+
     /**
      * Attempts to get a persisted long from the {@link android.content.SharedPreferences}.
-     * 
+     *
      * @param defaultReturnValue The default value to return if either this
      *            Preference is not persistent or this Preference is not in the
      *            SharedPreferences.
@@ -1654,13 +1654,13 @@
         if (!shouldPersist()) {
             return defaultReturnValue;
         }
-        
+
         return mPreferenceManager.getSharedPreferences().getLong(mKey, defaultReturnValue);
     }
-    
+
     /**
      * Attempts to persist a boolean to the {@link android.content.SharedPreferences}.
-     * 
+     *
      * @param value The value to persist.
      * @return True if this Preference is persistent. (This is not whether the
      *         value was persisted, since we may not necessarily commit if there
@@ -1674,7 +1674,7 @@
                 // It's already there, so the same as persisting
                 return true;
             }
-            
+
             SharedPreferences.Editor editor = mPreferenceManager.getEditor();
             editor.putBoolean(mKey, value);
             tryCommit(editor);
@@ -1682,10 +1682,10 @@
         }
         return false;
     }
-    
+
     /**
      * Attempts to get a persisted boolean from the {@link android.content.SharedPreferences}.
-     * 
+     *
      * @param defaultReturnValue The default value to return if either this
      *            Preference is not persistent or this Preference is not in the
      *            SharedPreferences.
@@ -1698,26 +1698,26 @@
         if (!shouldPersist()) {
             return defaultReturnValue;
         }
-        
+
         return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue);
     }
-    
+
     boolean canRecycleLayout() {
         return mCanRecycleLayout;
     }
-    
+
     @Override
     public String toString() {
         return getFilterableStringBuilder().toString();
     }
-        
+
     /**
      * Returns the text that will be used to filter this Preference depending on
      * user input.
      * <p>
      * If overridding and calling through to the superclass, make sure to prepend
      * your additions with a space.
-     * 
+     *
      * @return Text as a {@link StringBuilder} that will be used to filter this
      *         preference. By default, this is the title and summary
      *         (concatenated with a space).
@@ -1741,9 +1741,9 @@
 
     /**
      * Store this Preference hierarchy's frozen state into the given container.
-     * 
+     *
      * @param container The Bundle in which to save the instance of this Preference.
-     * 
+     *
      * @see #restoreHierarchyState
      * @see #onSaveInstanceState
      */
@@ -1755,9 +1755,9 @@
      * Called by {@link #saveHierarchyState} to store the instance for this Preference and its children.
      * May be overridden to modify how the save happens for children. For example, some
      * Preference objects may want to not store an instance for their children.
-     * 
+     *
      * @param container The Bundle in which to save the instance of this Preference.
-     * 
+     *
      * @see #saveHierarchyState
      * @see #onSaveInstanceState
      */
@@ -1780,7 +1780,7 @@
      * state that can later be used to create a new instance with that same
      * state. This state should only contain information that is not persistent
      * or can be reconstructed later.
-     * 
+     *
      * @return A Parcelable object containing the current dynamic state of
      *         this Preference, or null if there is nothing interesting to save.
      *         The default implementation returns null.
@@ -1794,9 +1794,9 @@
 
     /**
      * Restore this Preference hierarchy's previously saved state from the given container.
-     * 
+     *
      * @param container The Bundle that holds the previously saved state.
-     * 
+     *
      * @see #saveHierarchyState
      * @see #onRestoreInstanceState
      */
@@ -1809,7 +1809,7 @@
      * Preference and its children. May be overridden to modify how restoring
      * happens to the children of a Preference. For example, some Preference objects may
      * not want to save state for their children.
-     * 
+     *
      * @param container The Bundle that holds the previously saved state.
      * @see #restoreHierarchyState
      * @see #onRestoreInstanceState
@@ -1832,7 +1832,7 @@
      * Hook allowing a Preference to re-apply a representation of its internal
      * state that had previously been generated by {@link #onSaveInstanceState}.
      * This function will never be called with a null state.
-     * 
+     *
      * @param state The saved state that had previously been returned by
      *            {@link #onSaveInstanceState}.
      * @see #onSaveInstanceState
@@ -1856,17 +1856,17 @@
         public BaseSavedState(Parcelable superState) {
             super(superState);
         }
-        
+
         public static final Parcelable.Creator<BaseSavedState> CREATOR =
                 new Parcelable.Creator<BaseSavedState>() {
-            public BaseSavedState createFromParcel(Parcel in) {
-                return new BaseSavedState(in);
-            }
+                    public BaseSavedState createFromParcel(Parcel in) {
+                        return new BaseSavedState(in);
+                    }
 
-            public BaseSavedState[] newArray(int size) {
-                return new BaseSavedState[size];
-            }
-        };
+                    public BaseSavedState[] newArray(int size) {
+                        return new BaseSavedState[size];
+                    }
+                };
     }
 
 }
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 55df206..ce3e282 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -279,7 +279,9 @@
                 if (width < 0.0f) break;
                 i--;
             }
-            while (i < limit - 1 && mChars[i + 1] == ' ') i++;
+            while (i < limit - 1 && (mChars[i + 1] == ' ' || w[i + 1] == 0.0f)) {
+                i++;
+            }
             return limit - i - 1;
         }
     }
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 70d183d..bdbe8b0 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -1029,8 +1029,10 @@
 
                 for (i = len; i > 0; i--) {
                     float w = widths[i - 1 + lineStart - widthStart];
-
                     if (w + sum + ellipsisWidth > avail) {
+                        while (i < len && widths[i + lineStart - widthStart] == 0.0f) {
+                            i++;
+                        }
                         break;
                     }
 
@@ -1076,9 +1078,11 @@
                     float w = widths[right - 1 + lineStart - widthStart];
 
                     if (w + rsum > ravail) {
+                        while (right < len && widths[right + lineStart - widthStart] == 0.0f) {
+                            right++;
+                        }
                         break;
                     }
-
                     rsum += w;
                 }
 
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 1d9f99f..c163393 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -29,6 +29,7 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IRemoteCallback;
+import android.os.ParcelFileDescriptor;
 import android.view.IApplicationToken;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IDockedStackListener;
@@ -256,6 +257,13 @@
     void setScreenCaptureDisabled(int userId, boolean disabled);
 
     /**
+     * Testing and debugging infrastructure for writing surface events
+     * to given FD. See RemoteSurfaceTrace.java or Wm.java for format.
+     */
+    void enableSurfaceTrace(in ParcelFileDescriptor fd);
+    void disableSurfaceTrace();
+
+    /**
      * Cancels the window transitions for the given task.
      */
     void cancelTaskWindowTransition(int taskId);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index e778a7f..6456826 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -308,6 +308,17 @@
         mCloseGuard.open("release");
     }
 
+    // This is a transfer constructor, useful for transferring a live SurfaceControl native
+    // object to another Java wrapper which could have some different behavior, e.g.
+    // event logging.
+    public SurfaceControl(SurfaceControl other) {
+        mName = other.mName;
+        mNativeObject = other.mNativeObject;
+        other.mCloseGuard.close();
+        other.mNativeObject = 0;
+        mCloseGuard.open("release");
+    }
+
     @Override
     protected void finalize() throws Throwable {
         try {
diff --git a/core/java/com/android/internal/os/WifiPowerEstimator.java b/core/java/com/android/internal/os/WifiPowerEstimator.java
index 3bd79f7e..d175202 100644
--- a/core/java/com/android/internal/os/WifiPowerEstimator.java
+++ b/core/java/com/android/internal/os/WifiPowerEstimator.java
@@ -38,13 +38,13 @@
     }
 
     /**
-     * Return estimated power (in mAs) of sending a byte with the Wi-Fi radio.
+     * Return estimated power per Wi-Fi packet in mAh/packet where 1 packet = 2 KB.
      */
     private static double getWifiPowerPerPacket(PowerProfile profile) {
         final long WIFI_BPS = 1000000; // TODO: Extract average bit rates from system
         final double WIFI_POWER = profile.getAveragePower(PowerProfile.POWER_WIFI_ACTIVE)
                 / 3600;
-        return (WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048)) / (60*60);
+        return WIFI_POWER / (((double)WIFI_BPS) / 8 / 2048);
     }
 
     @Override
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index cd49d02..4c6350b 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -124,7 +124,6 @@
     android/graphics/PathMeasure.cpp \
     android/graphics/PathEffect.cpp \
     android/graphics/Picture.cpp \
-    android/graphics/PorterDuff.cpp \
     android/graphics/BitmapRegionDecoder.cpp \
     android/graphics/Rasterizer.cpp \
     android/graphics/Region.cpp \
@@ -132,7 +131,6 @@
     android/graphics/SurfaceTexture.cpp \
     android/graphics/Typeface.cpp \
     android/graphics/Utils.cpp \
-    android/graphics/Xfermode.cpp \
     android/graphics/YuvToJpegEncoder.cpp \
     android/graphics/pdf/PdfDocument.cpp \
     android/graphics/pdf/PdfEditor.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d75d53e..cdaa4dc 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -127,11 +127,9 @@
 extern int register_android_graphics_Path(JNIEnv* env);
 extern int register_android_graphics_PathMeasure(JNIEnv* env);
 extern int register_android_graphics_Picture(JNIEnv*);
-extern int register_android_graphics_PorterDuff(JNIEnv* env);
 extern int register_android_graphics_Rasterizer(JNIEnv* env);
 extern int register_android_graphics_Region(JNIEnv* env);
 extern int register_android_graphics_SurfaceTexture(JNIEnv* env);
-extern int register_android_graphics_Xfermode(JNIEnv* env);
 extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
 extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
@@ -1343,13 +1341,11 @@
     REG_JNI(register_android_graphics_PathMeasure),
     REG_JNI(register_android_graphics_PathEffect),
     REG_JNI(register_android_graphics_Picture),
-    REG_JNI(register_android_graphics_PorterDuff),
     REG_JNI(register_android_graphics_Rasterizer),
     REG_JNI(register_android_graphics_Region),
     REG_JNI(register_android_graphics_Shader),
     REG_JNI(register_android_graphics_SurfaceTexture),
     REG_JNI(register_android_graphics_Typeface),
-    REG_JNI(register_android_graphics_Xfermode),
     REG_JNI(register_android_graphics_YuvImage),
     REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
     REG_JNI(register_android_graphics_drawable_VectorDrawable),
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 9be659e..315dd6b 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -275,10 +275,30 @@
         return reinterpret_cast<jlong>(obj->setColorFilter(filter));
     }
 
-    static jlong setXfermode(JNIEnv* env, jobject clazz, jlong objHandle, jlong xfermodeHandle) {
-        Paint* obj = reinterpret_cast<Paint*>(objHandle);
-        SkXfermode* xfermode = reinterpret_cast<SkXfermode*>(xfermodeHandle);
-        return reinterpret_cast<jlong>(obj->setXfermode(xfermode));
+    static void setXfermode(JNIEnv* env, jobject clazz, jlong paintHandle, jint xfermodeHandle) {
+        // validate that the Java enum values match our expectations
+        static_assert(0 == SkXfermode::kClear_Mode, "xfermode_mismatch");
+        static_assert(1 == SkXfermode::kSrc_Mode, "xfermode_mismatch");
+        static_assert(2 == SkXfermode::kDst_Mode, "xfermode_mismatch");
+        static_assert(3 == SkXfermode::kSrcOver_Mode, "xfermode_mismatch");
+        static_assert(4 == SkXfermode::kDstOver_Mode, "xfermode_mismatch");
+        static_assert(5 == SkXfermode::kSrcIn_Mode, "xfermode_mismatch");
+        static_assert(6 == SkXfermode::kDstIn_Mode, "xfermode_mismatch");
+        static_assert(7 == SkXfermode::kSrcOut_Mode, "xfermode_mismatch");
+        static_assert(8 == SkXfermode::kDstOut_Mode, "xfermode_mismatch");
+        static_assert(9 == SkXfermode::kSrcATop_Mode, "xfermode_mismatch");
+        static_assert(10 == SkXfermode::kDstATop_Mode, "xfermode_mismatch");
+        static_assert(11 == SkXfermode::kXor_Mode, "xfermode_mismatch");
+        static_assert(16 == SkXfermode::kDarken_Mode, "xfermode_mismatch");
+        static_assert(17 == SkXfermode::kLighten_Mode, "xfermode_mismatch");
+        static_assert(13 == SkXfermode::kModulate_Mode, "xfermode_mismatch");
+        static_assert(14 == SkXfermode::kScreen_Mode, "xfermode_mismatch");
+        static_assert(12 == SkXfermode::kPlus_Mode, "xfermode_mismatch");
+        static_assert(15 == SkXfermode::kOverlay_Mode, "xfermode_mismatch");
+
+        SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(xfermodeHandle);
+        Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+        paint->setXfermodeMode(mode);
     }
 
     static jlong setPathEffect(JNIEnv* env, jobject clazz, jlong objHandle, jlong effectHandle) {
@@ -961,7 +981,7 @@
     {"nGetFillPath","!(JJJ)Z", (void*) PaintGlue::getFillPath},
     {"nSetShader","!(JJ)J", (void*) PaintGlue::setShader},
     {"nSetColorFilter","!(JJ)J", (void*) PaintGlue::setColorFilter},
-    {"nSetXfermode","!(JJ)J", (void*) PaintGlue::setXfermode},
+    {"nSetXfermode","!(JI)V", (void*) PaintGlue::setXfermode},
     {"nSetPathEffect","!(JJ)J", (void*) PaintGlue::setPathEffect},
     {"nSetMaskFilter","!(JJ)J", (void*) PaintGlue::setMaskFilter},
     {"nSetTypeface","!(JJ)J", (void*) PaintGlue::setTypeface},
diff --git a/core/jni/android/graphics/PorterDuff.cpp b/core/jni/android/graphics/PorterDuff.cpp
deleted file mode 100644
index dfc3c9d..0000000
--- a/core/jni/android/graphics/PorterDuff.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/* libs/android_runtime/android/graphics/PorterDuff.cpp
-**
-** Copyright 2006, 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.
-*/
-
-// This file was generated from the C++ include file: SkPorterDuff.h
-// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, 
-// or one of the auxilary file specifications in device/tools/gluemaker.
-
-#include "jni.h"
-#include "GraphicsJNI.h"
-#include "core_jni_helpers.h"
-
-#include "SkXfermode.h"
-
-namespace android {
-
-class SkPorterDuffGlue {
-public:
-
-    static jlong CreateXfermode(JNIEnv* env, jobject, jint modeHandle) {
-        // validate that the Java enum values match our expectations
-        static_assert(0  == SkXfermode::kClear_Mode,    "xfermode_mismatch");
-        static_assert(1  == SkXfermode::kSrc_Mode,      "xfermode_mismatch");
-        static_assert(2  == SkXfermode::kDst_Mode,      "xfermode_mismatch");
-        static_assert(3  == SkXfermode::kSrcOver_Mode,  "xfermode_mismatch");
-        static_assert(4  == SkXfermode::kDstOver_Mode,  "xfermode_mismatch");
-        static_assert(5  == SkXfermode::kSrcIn_Mode,    "xfermode_mismatch");
-        static_assert(6  == SkXfermode::kDstIn_Mode,    "xfermode_mismatch");
-        static_assert(7  == SkXfermode::kSrcOut_Mode,   "xfermode_mismatch");
-        static_assert(8  == SkXfermode::kDstOut_Mode,   "xfermode_mismatch");
-        static_assert(9  == SkXfermode::kSrcATop_Mode,  "xfermode_mismatch");
-        static_assert(10 == SkXfermode::kDstATop_Mode,  "xfermode_mismatch");
-        static_assert(11 == SkXfermode::kXor_Mode,      "xfermode_mismatch");
-        static_assert(16 == SkXfermode::kDarken_Mode,   "xfermode_mismatch");
-        static_assert(17 == SkXfermode::kLighten_Mode,  "xfermode_mismatch");
-        static_assert(13 == SkXfermode::kModulate_Mode, "xfermode_mismatch");
-        static_assert(14 == SkXfermode::kScreen_Mode,   "xfermode_mismatch");
-        static_assert(12 == SkXfermode::kPlus_Mode,     "xfermode_mismatch");
-        static_assert(15 == SkXfermode::kOverlay_Mode,  "xfermode_mismatch");
-
-        SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(modeHandle);
-        return reinterpret_cast<jlong>(SkXfermode::Create(mode));
-    }
-
-};
-
-static const JNINativeMethod methods[] = {
-    {"nativeCreateXfermode","(I)J", (void*) SkPorterDuffGlue::CreateXfermode},
-};
-
-int register_android_graphics_PorterDuff(JNIEnv* env) {
-    return RegisterMethodsOrDie(env, "android/graphics/PorterDuffXfermode", methods, NELEM(methods));
-}
-
-}
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index de32dd9..9781ae1 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -225,24 +225,13 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static jlong ComposeShader_create1(JNIEnv* env, jobject o,
-        jlong shaderAHandle, jlong shaderBHandle, jlong modeHandle)
-{
-    SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
-    SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
-    SkXfermode* mode = reinterpret_cast<SkXfermode *>(modeHandle);
-    SkShader* shader = SkShader::CreateComposeShader(shaderA, shaderB, mode);
-    return reinterpret_cast<jlong>(shader);
-}
-
-static jlong ComposeShader_create2(JNIEnv* env, jobject o,
+static jlong ComposeShader_create(JNIEnv* env, jobject o,
         jlong shaderAHandle, jlong shaderBHandle, jint xfermodeHandle)
 {
     SkShader* shaderA = reinterpret_cast<SkShader *>(shaderAHandle);
     SkShader* shaderB = reinterpret_cast<SkShader *>(shaderBHandle);
     SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(xfermodeHandle);
-    SkAutoTUnref<SkXfermode> xfermode(SkXfermode::Create(mode));
-    SkShader* shader = SkShader::CreateComposeShader(shaderA, shaderB, xfermode.get());
+    SkShader* shader = SkShader::CreateComposeShader(shaderA, shaderB, mode);
     return reinterpret_cast<jlong>(shader);
 }
 
@@ -278,8 +267,7 @@
 };
 
 static const JNINativeMethod gComposeShaderMethods[] = {
-    { "nativeCreate1",      "(JJJ)J",   (void*)ComposeShader_create1     },
-    { "nativeCreate2",      "(JJI)J",   (void*)ComposeShader_create2     },
+    { "nativeCreate",      "(JJI)J",   (void*)ComposeShader_create     },
 };
 
 int register_android_graphics_Shader(JNIEnv* env)
diff --git a/core/jni/android/graphics/Xfermode.cpp b/core/jni/android/graphics/Xfermode.cpp
deleted file mode 100644
index 78975a4f..0000000
--- a/core/jni/android/graphics/Xfermode.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "jni.h"
-//#include "GraphicsJNI.h"
-#include "core_jni_helpers.h"
-
-#include <SkXfermode.h>
-
-namespace android {
-
-class SkXfermodeGlue {
-public:
-    static void finalizer(JNIEnv* env, jobject, jlong objHandle)
-    {
-        SkXfermode* obj = reinterpret_cast<SkXfermode *>(objHandle);
-        SkSafeUnref(obj);
-    }
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-static const JNINativeMethod gXfermodeMethods[] = {
-    {"finalizer", "(J)V", (void*) SkXfermodeGlue::finalizer}
-};
-
-int register_android_graphics_Xfermode(JNIEnv* env) {
-    android::RegisterMethodsOrDie(env, "android/graphics/Xfermode", gXfermodeMethods,
-                                  NELEM(gXfermodeMethods));
-    return 0;
-}
-
-}
diff --git a/core/res/res/layout-watch/alert_dialog_material.xml b/core/res/res/layout-watch/alert_dialog_material.xml
index 002dde8..2fe13de 100644
--- a/core/res/res/layout-watch/alert_dialog_material.xml
+++ b/core/res/res/layout-watch/alert_dialog_material.xml
@@ -45,7 +45,7 @@
             <!-- Content Panel -->
             <FrameLayout android:id="@+id/contentPanel"
                     android:layout_width="match_parent"
-                    android:layout_height="match_parent"
+                    android:layout_height="wrap_content"
                     android:clipToPadding="false">
                 <TextView android:id="@+id/message"
                         android:layout_width="match_parent"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9940f28..aca2bcc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -6965,9 +6965,11 @@
         <attr name="icon" />
         <!-- The key to store the Preference value. -->
         <attr name="key" format="string" />
-        <!-- The title for the Preference in a PreferenceActivity screen. -->
+        <!-- The title for the Preference. In API 25 and earlier, this value is read as a
+         plain string with styling information stripped. -->
         <attr name="title" />
-        <!-- The summary for the Preference in a PreferenceActivity screen. -->
+        <!-- The summary for the Preference. In API 25 and earlier, this value is read as a
+         plain string with styling information stripped. -->
         <attr name="summary" />
         <!-- The order for the Preference (lower values are to be ordered first). If this is not
              specified, the default ordering will be alphabetic. -->
diff --git a/docs/html/develop/index.jd b/docs/html/develop/index.jd
index bd933f4..cee8327 100644
--- a/docs/html/develop/index.jd
+++ b/docs/html/develop/index.jd
@@ -14,24 +14,26 @@
   <div class="wrap">
     <div class="cols dac-hero-content">
       <div class="col-1of2 col-push-1of2 dac-hero-figure">
-        <img class="dac-hero-image" src="/images/develop/hero_image_studio5_2x.png" srcset="/images/develop/hero_image_studio5.png 1x, /images/develop/hero_image_studio5_2x.png 2x">
+        <img class="dac-hero-image" style="padding-top:32px"
+          src="/images/develop/hero-layout-editor_2x.png">
       </div>
       <div class="col-1of2 col-pull-1of2" style="margin-bottom:40px">
         <h1 class="dac-hero-title">
             <a style="color:inherit" href="{@docRoot}studio/index.html">
-            Android Studio 2.1,<br>now available!</a></h1>
+            Android Studio 2.2 <nobr>is here!</nobr></a></h1>
 
-<p class="dac-hero-description">Android Studio provides the fastest tools for
-building apps on every type of Android device.</p>
+<p class="dac-hero-description">The latest version of Android Studio includes a
+rewritten <b>layout editor</b> with the new constraint layout,
+helping you build rich UI with less work.</p>
 
-<p class="dac-hero-description">The latest version, Android Studio 2.1, adds
-support for N Preview development on top of the faster Android Emulator and
-Instant Run feature from 2.0.</p>
+
+<p class="dac-hero-description">With over a dozen new features, Android Studio
+2.2 helps you code faster and smarter.</p>
 
 <p style="margin-top:24px">
     <a class="dac-hero-cta" href="{@docRoot}studio/index.html">
       <span class="dac-sprite dac-auto-chevron"></span>
-      Get Android Studio
+      Get Android Studio 2.2
     </a>
   &nbsp;&nbsp;&nbsp;&nbsp;<wbr>
     <a class="dac-hero-cta" href="{@docRoot}studio/releases/index.html">
diff --git a/docs/html/google/play/billing/billing_admin.jd b/docs/html/google/play/billing/billing_admin.jd
index 292cfcc..ad09f1f 100644
--- a/docs/html/google/play/billing/billing_admin.jd
+++ b/docs/html/google/play/billing/billing_admin.jd
@@ -235,7 +235,7 @@
 
 <p>"<em>product_id</em>","<em>publish_state</em>","<em>purchase_type</em>","<em>autotranslate</em>
 ","<em>locale</em>; <em>title</em>; <em>description</em>","<em>autofill</em>","<em>country</em>;
-<em>price</em>"
+<em>price</em>", "<em>pricing_template_id</em>"
 </p>
 
 <p>Descriptions and usage details are provided below.</p>
@@ -316,8 +316,9 @@
         <p>"true","<em>default_price_in_home_currency</em>"
       </li>
       <li>
-        <p>If <em>autofill</em> is set to <code>false</code>, you need to specify a <em>country</em>
-        and a <em>price</em> for each currency, and you must use the following syntax:</p>
+        <p>If <em>autofill</em> is set to <code>false</code>, you need to either specify the <em>pricing_template_id</em>
+        that is linked to the in-app product or specify a <em>country</em> and a <em>price</em> for each currency.
+        If you choose to specify countries and prices, you must use the following syntax:</p>
         <p>"false", "<em>home_country</em>; <em>default_price_in_home_currency</em>; <em>country_2</em>;
         <em>country_2_price</em>; <em>country_3</em>; <em>country_3_price</em>; ..."</p>
       </li>
@@ -335,11 +336,41 @@
   </dd>
   <dt>price</dt>
   <dd>
+    <p>
+    If you use this value, you shouldn't specify a value for the <em>pricing_template_id</em>.
+    </p>
+    <p>
     This is equivalent to the Price in the In-app Products UI. The price must be specified in
     micro-units. To convert a currency value to micro-units, you multiply the real value by
     1,000,000.
     For example, if you want to sell an in-app item for $1.99, you specify <code>1990000</code> in the
     <em>price</em> field.
+    </p>
+  </dd>
+  <dt>pricing_template_id</dt>
+  <dd>
+  <p>
+    If you use this value, you should set <em>autofill</em> to
+    <code>false</code> and leave the <em>price</em> column empty.
+  </p>
+  <p>
+    This value represents the ID of the pricing template that you've linked to
+    the in-app product. This ID appears under a pricing template's name
+    on the <strong>Pricing template</strong> page. If an in-app product isn't
+    linked to a pricing template, its <em>pricing_template_id</em> value is
+    empty.
+  </p>
+  <p>
+    If you import a CSV file and choose to overwrite the product list, you can
+    update the links between in-app products and pricing templates by changing
+    the value of an in-app product's <em>pricing_template_id</em>. Leave the
+    value empty to unlink an in-app product from all pricing templates.
+  </p>
+  <p>
+    <strong>Note: </strong>You can link up to 100 app prices or in-app product
+    prices with a particular pricing template. Therefore, don't specify the same
+    <em>pricing_template_id</em> value in more than 100 rows of your CSV file.
+  </p>
   </dd>
 </dl>
 
@@ -432,8 +463,11 @@
 
 <p>
   When creating a pricing template, you provide new pricing information that you
-  can apply to paid apps and in-app products. To add a pricing template, do the
-  following:
+  can apply to paid apps and in-app products. You can link the prices of up to
+  100 apps and in-app products to a single pricing template.
+</p>
+</p>
+  To add a pricing template, do the following:
 </p>
 
 <ol>
diff --git a/docs/html/guide/topics/resources/multilingual-support.jd b/docs/html/guide/topics/resources/multilingual-support.jd
index 8d8484b..28699fe 100644
--- a/docs/html/guide/topics/resources/multilingual-support.jd
+++ b/docs/html/guide/topics/resources/multilingual-support.jd
@@ -88,15 +88,17 @@
 
 <h2 id="postN">Improvements to Resource-Resolution Strategy</h2>
 <p>Android 7.0 (API level 24) brings more robust resource resolution, and
- finds better fallbacks automatically. However, to speed up resolution and
- improve
+ finds better fallbacks automatically.
+ However, to speed up resolution and improve
  maintainability, you should store resources in the most common parent dialect.
- For example, if you were storing Spanish resources in the {@code es-US}
- directory
- before, move them into the {@code es-419} directory, which contains Latin
- American Spanish.
- Similarly, if you have resource strings in a folder named {@code en-GB}, rename
- the folder to {@code en-001} (international English), because the most common
+ For example, if you were storing Spanish resources
+ in the {@code values-es-rUS} directory
+ before, move them into the {@code values-b+es+419} directory,
+ which contains Latin American Spanish.
+ Similarly, if you have resource strings in a
+ directory named {@code values-en-rGB}, rename
+ the directory to {@code values-b+en+001} (International
+ English), because the most common
  parent for <code>en-GB</code> strings is {@code en-001}.
  The following examples explain why these practices improve performance and
 reliability of resource resolution.</p>
diff --git a/docs/html/images/develop/hero-layout-editor_2x.png b/docs/html/images/develop/hero-layout-editor_2x.png
new file mode 100644
index 0000000..56dfbf3
--- /dev/null
+++ b/docs/html/images/develop/hero-layout-editor_2x.png
Binary files differ
diff --git a/docs/html/index.jd b/docs/html/index.jd
index fe5dada..32360fd 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -63,6 +63,40 @@
   </div><!-- end .wrap -->
 </div><!-- end .dac-actions -->
 
+
+<section class="dac-expand dac-hero" style="background-color:#FFF0B4;">
+  <div class="wrap" style="max-width:1000px;margin-top:0;overflow:auto">
+    <div class="col-7of16 col-push-8of16 dac-hero-figure">
+      <a href="/studio/index.html">
+        <img class="dac-hero-image" style="padding-top:24px"
+          src="/studio/images/hero_image_studio_2-2_2x.png">
+      </a>
+    </div>
+    <div class="col-7of16 col-pull-6of16">
+        <h1 class="dac-hero-title" style="color:#004d40">Android Studio 2.2!</h1>
+<p class="dac-hero-description" style="color:#004d40">The latest update is
+packed with over a dozen new features, like a rewritten layout editor with the
+new constraint layout, support for Android 7.0 Nougat, Espresso test recording,
+enhanced Jack compiler / Java 8 support, expanded C++ support with CMake and
+NDK-Build, and much more!</p>
+<p class="dac-hero-description" style="color:#004d40">Android Studio 2.2
+helps you code faster and smarter.</p>
+
+<p style="margin-top:24px">
+   <a class="dac-hero-cta" href="/studio/index.html" style="color:#004d40">
+      <span class="dac-sprite dac-auto-chevron"></span>
+      Get Android Studio 2.2
+    </a>
+  &nbsp;&nbsp;&nbsp;&nbsp;<wbr>
+   <a class="dac-hero-cta" href="/studio/releases/index.html" style="color:#004d40">
+    <span class="dac-sprite dac-auto-chevron"></span>
+    See the release notes</a>
+</p>
+    </div>
+  </div>
+</section>
+
+
 <section class="dac-section dac-light" id="build-apps"><div class="wrap">
   <h1 class="dac-section-title">Build Beautiful Apps</h1>
   <div class="dac-section-subtitle">
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index b2adcf6..08a68f4 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -21,23 +21,8 @@
 */
 public class ComposeShader extends Shader {
 
-    private static final int TYPE_XFERMODE = 1;
-    private static final int TYPE_PORTERDUFFMODE = 2;
-
-    /**
-     * Type of the ComposeShader: can be either TYPE_XFERMODE or TYPE_PORTERDUFFMODE
-     */
-    private int mType;
-
-    private Xfermode mXferMode;
-    private PorterDuff.Mode mPorterDuffMode;
-
-    /**
-     * Hold onto the shaders to avoid GC.
-     */
-    @SuppressWarnings({"UnusedDeclaration"})
+    private int mPorterDuffMode;
     private final Shader mShaderA;
-    @SuppressWarnings({"UnusedDeclaration"})
     private final Shader mShaderB;
 
     /** Create a new compose shader, given shaders A, B, and a combining mode.
@@ -49,12 +34,7 @@
                         is null, then SRC_OVER is assumed.
     */
     public ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) {
-        mType = TYPE_XFERMODE;
-        mShaderA = shaderA;
-        mShaderB = shaderB;
-        mXferMode = mode;
-        init(nativeCreate1(shaderA.getNativeInstance(), shaderB.getNativeInstance(),
-                (mode != null) ? mode.native_instance : 0));
+        this(shaderA, shaderB, mode.porterDuffMode);
     }
 
     /** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode.
@@ -65,12 +45,15 @@
         @param mode     The PorterDuff mode that combines the colors from the two shaders.
     */
     public ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) {
-        mType = TYPE_PORTERDUFFMODE;
+        this(shaderA, shaderB, mode.nativeInt);
+    }
+
+    private ComposeShader(Shader shaderA, Shader shaderB, int nativeMode) {
         mShaderA = shaderA;
         mShaderB = shaderB;
-        mPorterDuffMode = mode;
-        init(nativeCreate2(shaderA.getNativeInstance(), shaderB.getNativeInstance(),
-                mode.nativeInt));
+        mPorterDuffMode = nativeMode;
+        init(nativeCreate(shaderA.getNativeInstance(), shaderB.getNativeInstance(),
+                nativeMode));
     }
 
     /**
@@ -78,24 +61,12 @@
      */
     @Override
     protected Shader copy() {
-        final ComposeShader copy;
-        switch (mType) {
-            case TYPE_XFERMODE:
-                copy = new ComposeShader(mShaderA.copy(), mShaderB.copy(), mXferMode);
-                break;
-            case TYPE_PORTERDUFFMODE:
-                copy = new ComposeShader(mShaderA.copy(), mShaderB.copy(), mPorterDuffMode);
-                break;
-            default:
-                throw new IllegalArgumentException(
-                        "ComposeShader should be created with either Xfermode or PorterDuffMode");
-        }
+        final ComposeShader copy = new ComposeShader(
+                mShaderA.copy(), mShaderB.copy(), mPorterDuffMode);
         copyLocalMatrix(copy);
         return copy;
     }
 
-    private static native long nativeCreate1(long native_shaderA, long native_shaderB,
-            long native_mode);
-    private static native long nativeCreate2(long native_shaderA, long native_shaderB,
+    private static native long nativeCreate(long native_shaderA, long native_shaderB,
             int porterDuffMode);
 }
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 81bbfa9..4abe50f 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1096,10 +1096,11 @@
      * @return         xfermode
      */
     public Xfermode setXfermode(Xfermode xfermode) {
-        long xfermodeNative = 0;
-        if (xfermode != null)
-            xfermodeNative = xfermode.native_instance;
-        nSetXfermode(mNativePaint, xfermodeNative);
+        int newMode = xfermode != null ? xfermode.porterDuffMode : Xfermode.DEFAULT;
+        int curMode = mXfermode != null ? mXfermode.porterDuffMode : Xfermode.DEFAULT;
+        if (newMode != curMode) {
+            nSetXfermode(mNativePaint, newMode);
+        }
         mXfermode = xfermode;
         return xfermode;
     }
@@ -2694,8 +2695,7 @@
     private static native long nSetShader(long paintPtr, long shader);
     private static native long nSetColorFilter(long paintPtr,
                                                     long filter);
-    private static native long nSetXfermode(long paintPtr,
-                                                  long xfermode);
+    private static native void nSetXfermode(long paintPtr, int xfermode);
     private static native long nSetPathEffect(long paintPtr,
                                                     long effect);
     private static native long nSetMaskFilter(long paintPtr,
diff --git a/graphics/java/android/graphics/PorterDuffXfermode.java b/graphics/java/android/graphics/PorterDuffXfermode.java
index d9d7689..5104410 100644
--- a/graphics/java/android/graphics/PorterDuffXfermode.java
+++ b/graphics/java/android/graphics/PorterDuffXfermode.java
@@ -18,19 +18,11 @@
 
 public class PorterDuffXfermode extends Xfermode {
     /**
-     * @hide
-     */
-    public final PorterDuff.Mode mode;
-
-    /**
      * Create an xfermode that uses the specified porter-duff mode.
      *
      * @param mode           The porter-duff mode that is applied
      */
     public PorterDuffXfermode(PorterDuff.Mode mode) {
-        this.mode = mode;
-        native_instance = nativeCreateXfermode(mode.nativeInt);
+        porterDuffMode = mode.nativeInt;
     }
-    
-    private static native long nativeCreateXfermode(int mode);
 }
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index c049e41..a5da5d0 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -29,17 +29,6 @@
  * objects drawn with that paint have the xfermode applied.
  */
 public class Xfermode {
-
-    protected void finalize() throws Throwable {
-        try {
-            finalizer(native_instance);
-            native_instance = 0;
-        } finally {
-            super.finalize();
-        }
-    }
-
-    private static native void finalizer(long native_instance);
-
-    long native_instance;
+    static final int DEFAULT = PorterDuff.Mode.SRC_OVER.nativeInt;
+    int porterDuffMode = DEFAULT;
 }
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 3aca867..af20f8a 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -573,6 +573,12 @@
      * </p>
      */
     public void setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
+        if (getColorFilter() instanceof PorterDuffColorFilter) {
+            PorterDuffColorFilter existing = (PorterDuffColorFilter) getColorFilter();
+            if (existing.getColor() == color && existing.getMode() == mode) {
+                return;
+            }
+        }
         setColorFilter(new PorterDuffColorFilter(color, mode));
     }
 
diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
index cd759cb..158938a 100644
--- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
+++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp
@@ -68,3 +68,13 @@
         EXPECT_FALSE(failFilter->asColorMode(nullptr, nullptr));
     }
 }
+
+TEST(SkiaBehavior, porterDuffCreateIsCached) {
+    SkPaint paint;
+    paint.setXfermodeMode(SkXfermode::kOverlay_Mode);
+    auto expected = paint.getXfermode();
+    paint.setXfermodeMode(SkXfermode::kClear_Mode);
+    ASSERT_NE(expected, paint.getXfermode());
+    paint.setXfermodeMode(SkXfermode::kOverlay_Mode);
+    ASSERT_EQ(expected, paint.getXfermode());
+}
diff --git a/packages/SettingsLib/tests/Android.mk b/packages/SettingsLib/tests/Android.mk
index 1ed1121..4208522 100644
--- a/packages/SettingsLib/tests/Android.mk
+++ b/packages/SettingsLib/tests/Android.mk
@@ -24,8 +24,10 @@
 
 LOCAL_PACKAGE_NAME := SettingsLibTests
 
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target \
-    android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    espresso-core \
+    mockito-target-minus-junit4
 
 include frameworks/base/packages/SettingsLib/common.mk
 
diff --git a/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java b/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
index 0f73a34..4d7d4cf 100644
--- a/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
+++ b/packages/SettingsLib/tests/src/com/android/settingslib/drawer/SettingsDrawerActivityTest.java
@@ -16,11 +16,9 @@
 
 package com.android.settingslib.drawer;
 
-import static junit.framework.Assert.assertEquals;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.verify;
-
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
 import android.annotation.Nullable;
 import android.content.Intent;
 import android.content.pm.UserInfo;
@@ -30,8 +28,11 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+
 import com.android.settingslib.drawer.SettingsDrawerActivity;
 import com.android.settingslib.drawer.Tile;
+import com.android.settingslib.R;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -39,6 +40,16 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class SettingsDrawerActivityTest {
@@ -96,6 +107,38 @@
         verify(mUserManager, times(2)).getUserInfo(REMOVED_USER.getIdentifier());
     }
 
+    @Test
+    public void startActivityWithNoExtra_showNoHamburgerMenu() {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.startActivitySync(new Intent(instrumentation.getTargetContext(),
+                TestActivity.class));
+
+        onView(withContentDescription(R.string.content_description_menu_button))
+                .check(doesNotExist());
+    }
+
+    @Test
+    public void startActivityWithExtraToHideMenu_showNoHamburgerMenu() {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
+                .putExtra(TestActivity.EXTRA_SHOW_MENU, false);
+        instrumentation.startActivitySync(intent);
+
+        onView(withContentDescription(R.string.content_description_menu_button))
+                .check(doesNotExist());
+    }
+
+    @Test
+    public void startActivityWithExtraToShowMenu_showHamburgerMenu() {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        Intent intent = new Intent(instrumentation.getTargetContext(), TestActivity.class)
+                .putExtra(TestActivity.EXTRA_SHOW_MENU, true);
+        instrumentation.startActivitySync(intent);
+
+        onView(withContentDescription(R.string.content_description_menu_button))
+                .check(matches(isDisplayed()));
+    }
+
     /**
      * Test Activity in this test.
      *
@@ -103,5 +146,4 @@
      * AndroidManifest.xml
      */
     public static class TestActivity extends SettingsDrawerActivity {}
-
 }
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 8c1ce24..71bfe85 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -23,6 +23,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
+    SystemUIPluginLib \
     Keyguard \
     android-support-v7-recyclerview \
     android-support-v7-preference \
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3cc16de..8ed1be5 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -138,6 +138,9 @@
             android:protectionLevel="signature" />
     <uses-permission android:name="com.android.systemui.permission.SELF" />
 
+    <permission android:name="com.android.systemui.permission.PLUGIN"
+            android:protectionLevel="signature" />
+
     <!-- Adding Quick Settings tiles -->
     <uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
 
diff --git a/packages/SystemUI/plugin/Android.mk b/packages/SystemUI/plugin/Android.mk
new file mode 100644
index 0000000..86527db
--- /dev/null
+++ b/packages/SystemUI/plugin/Android.mk
@@ -0,0 +1,29 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := SystemUIPluginLib
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_JAR_EXCLUDE_FILES := none
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/packages/SystemUI/plugin/AndroidManifest.xml b/packages/SystemUI/plugin/AndroidManifest.xml
new file mode 100644
index 0000000..7c057dc
--- /dev/null
+++ b/packages/SystemUI/plugin/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.plugins">
+
+    <uses-sdk
+        android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SystemUI/plugin/ExamplePlugin/Android.mk b/packages/SystemUI/plugin/ExamplePlugin/Android.mk
new file mode 100644
index 0000000..4c82c75
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/Android.mk
@@ -0,0 +1,15 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_PACKAGE_NAME := ExamplePlugin
+
+LOCAL_JAVA_LIBRARIES := SystemUIPluginLib
+
+LOCAL_CERTIFICATE := platform
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/plugin/ExamplePlugin/AndroidManifest.xml b/packages/SystemUI/plugin/ExamplePlugin/AndroidManifest.xml
new file mode 100644
index 0000000..bd2c71c
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.systemui.plugin.testoverlayplugin">
+
+    <uses-permission android:name="com.android.systemui.permission.PLUGIN" />
+
+    <application>
+        <service android:name=".SampleOverlayPlugin">
+            <intent-filter>
+                <action android:name="com.android.systemui.action.PLUGIN_OVERLAY" />
+            </intent-filter>
+        </service>
+    </application>
+
+</manifest>
diff --git a/packages/SystemUI/plugin/ExamplePlugin/res/layout/colored_overlay.xml b/packages/SystemUI/plugin/ExamplePlugin/res/layout/colored_overlay.xml
new file mode 100644
index 0000000..b2910cb
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/res/layout/colored_overlay.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+** Copyright 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.systemui.plugin.testoverlayplugin.CustomView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#80ff0000" />
diff --git a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java
new file mode 100644
index 0000000..5fdbbf9
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/CustomView.java
@@ -0,0 +1,46 @@
+/*
+ * 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.plugin.testoverlayplugin;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+/**
+ * View with some logging to show that its being run.
+ */
+public class CustomView extends View {
+
+    private static final String TAG = "CustomView";
+
+    public CustomView(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        Log.d(TAG, "new instance");
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        Log.d(TAG, "onAttachedToWindow");
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        Log.d(TAG, "onDetachedFromWindow");
+    }
+}
diff --git a/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java
new file mode 100644
index 0000000..a2f84dc
--- /dev/null
+++ b/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java
@@ -0,0 +1,71 @@
+/*
+ * 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.plugin.testoverlayplugin;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.plugins.OverlayPlugin;
+
+public class SampleOverlayPlugin implements OverlayPlugin {
+    private static final String TAG = "SampleOverlayPlugin";
+    private Context mPluginContext;
+
+    private View mStatusBarView;
+    private View mNavBarView;
+
+    @Override
+    public int getVersion() {
+        Log.d(TAG, "getVersion " + VERSION);
+        return VERSION;
+    }
+
+    @Override
+    public void onCreate(Context sysuiContext, Context pluginContext) {
+        Log.d(TAG, "onCreate");
+        mPluginContext = pluginContext;
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy");
+        if (mStatusBarView != null) {
+            mStatusBarView.post(
+                    () -> ((ViewGroup) mStatusBarView.getParent()).removeView(mStatusBarView));
+        }
+        if (mNavBarView != null) {
+            mNavBarView.post(() -> ((ViewGroup) mNavBarView.getParent()).removeView(mNavBarView));
+        }
+    }
+
+    @Override
+    public void setup(View statusBar, View navBar) {
+        Log.d(TAG, "Setup");
+
+        if (statusBar instanceof ViewGroup) {
+            mStatusBarView = LayoutInflater.from(mPluginContext)
+                    .inflate(R.layout.colored_overlay, (ViewGroup) statusBar, false);
+            ((ViewGroup) statusBar).addView(mStatusBarView);
+        }
+        if (navBar instanceof ViewGroup) {
+            mNavBarView = LayoutInflater.from(mPluginContext)
+                    .inflate(R.layout.colored_overlay, (ViewGroup) navBar, false);
+            ((ViewGroup) navBar).addView(mNavBarView);
+        }
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
new file mode 100644
index 0000000..91a2604
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
@@ -0,0 +1,24 @@
+/*
+ * 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.plugins;
+
+import android.view.View;
+
+public interface OverlayPlugin extends Plugin {
+
+    String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY";
+    int VERSION = 1;
+
+    void setup(View statusBar, View navBar);
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
new file mode 100644
index 0000000..b31b199
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Plugin.java
@@ -0,0 +1,132 @@
+/*
+ * 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.plugins;
+
+import android.content.Context;
+
+/**
+ * Plugins are separate APKs that
+ * are expected to implement interfaces provided by SystemUI.  Their
+ * code is dynamically loaded into the SysUI process which can allow
+ * for multiple prototypes to be created and run on a single android
+ * build.
+ *
+ * PluginLifecycle:
+ * <pre class="prettyprint">
+ *
+ * plugin.onCreate(Context sysuiContext, Context pluginContext);
+ * --- This is always called before any other calls
+ *
+ * pluginListener.onPluginConnected(Plugin p);
+ * --- This lets the plugin hook know that a plugin is now connected.
+ *
+ * ** Any other calls back and forth between sysui/plugin **
+ *
+ * pluginListener.onPluginDisconnected(Plugin p);
+ * --- Lets the plugin hook know that it should stop interacting with
+ *     this plugin and drop all references to it.
+ *
+ * plugin.onDestroy();
+ * --- Finally the plugin can perform any cleanup to ensure that its not
+ *     leaking into the SysUI process.
+ *
+ * Any time a plugin APK is updated the plugin is destroyed and recreated
+ * to load the new code/resources.
+ *
+ * </pre>
+ *
+ * Creating plugin hooks:
+ *
+ * To create a plugin hook, first create an interface in
+ * frameworks/base/packages/SystemUI/plugin that extends Plugin.
+ * Include in it any hooks you want to be able to call into from
+ * sysui and create callback interfaces for anything you need to
+ * pass through into the plugin.
+ *
+ * Then to attach to any plugins simply add a plugin listener and
+ * onPluginConnected will get called whenever new plugins are installed,
+ * updated, or enabled.  Like this example from SystemUIApplication:
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * PluginManager.getInstance(this).addPluginListener(OverlayPlugin.COMPONENT,
+ *        new PluginListener<OverlayPlugin>() {
+ *        @Override
+ *        public void onPluginConnected(OverlayPlugin plugin) {
+ *            PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
+ *            if (phoneStatusBar != null) {
+ *                plugin.setup(phoneStatusBar.getStatusBarWindow(),
+ *                phoneStatusBar.getNavigationBarView());
+ *            }
+ *        }
+ * }, OverlayPlugin.VERSION, true /* Allow multiple plugins *\/);
+ * }
+ * </pre>
+ * Note the VERSION included here.  Any time incompatible changes in the
+ * interface are made, this version should be changed to ensure old plugins
+ * aren't accidentally loaded.  Since the plugin library is provided by
+ * SystemUI, default implementations can be added for new methods to avoid
+ * version changes when possible.
+ *
+ * Implementing a Plugin:
+ *
+ * See the ExamplePlugin for an example Android.mk on how to compile
+ * a plugin.  Note that SystemUILib is not static for plugins, its classes
+ * are provided by SystemUI.
+ *
+ * Plugin security is based around a signature permission, so plugins must
+ * hold the following permission in their manifest.
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <uses-permission android:name="com.android.systemui.permission.PLUGIN" />
+ * }
+ * </pre>
+ *
+ * A plugin is found through a querying for services, so to let SysUI know
+ * about it, create a service with a name that points at your implementation
+ * of the plugin interface with the action accompanying it:
+ *
+ * <pre class="prettyprint">
+ * {@literal
+ * <service android:name=".TestOverlayPlugin">
+ *    <intent-filter>
+ *        <action android:name="com.android.systemui.action.PLUGIN_COMPONENT" />
+ *    </intent-filter>
+ * </service>
+ * }
+ * </pre>
+ */
+public interface Plugin {
+
+    /**
+     * Should be implemented as the following directly referencing the version constant
+     * from the plugin interface being implemented, this will allow recompiles to automatically
+     * pick up the current version.
+     * <pre class="prettyprint">
+     * {@literal
+     * public int getVersion() {
+     *     return VERSION;
+     * }
+     * }
+     * @return
+     */
+    int getVersion();
+
+    default void onCreate(Context sysuiContext, Context pluginContext) {
+    }
+
+    default void onDestroy() {
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
new file mode 100644
index 0000000..2a7139c
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginInstanceManager.java
@@ -0,0 +1,342 @@
+/*
+ * 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.plugins;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.LayoutInflater;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import dalvik.system.PathClassLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PluginInstanceManager<T extends Plugin> extends BroadcastReceiver {
+
+    private static final boolean DEBUG = false;
+
+    private static final String TAG = "PluginInstanceManager";
+    private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
+
+    private final Context mContext;
+    private final PluginListener<T> mListener;
+    private final String mAction;
+    private final boolean mAllowMultiple;
+    private final int mVersion;
+
+    @VisibleForTesting
+    final MainHandler mMainHandler;
+    @VisibleForTesting
+    final PluginHandler mPluginHandler;
+    private final boolean isDebuggable;
+    private final PackageManager mPm;
+    private final ClassLoaderFactory mClassLoaderFactory;
+
+    PluginInstanceManager(Context context, String action, PluginListener<T> listener,
+            boolean allowMultiple, Looper looper, int version) {
+        this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version,
+                Build.IS_DEBUGGABLE, new ClassLoaderFactory());
+    }
+
+    @VisibleForTesting
+    PluginInstanceManager(Context context, PackageManager pm, String action,
+            PluginListener<T> listener, boolean allowMultiple, Looper looper, int version,
+            boolean debuggable, ClassLoaderFactory classLoaderFactory) {
+        mMainHandler = new MainHandler(Looper.getMainLooper());
+        mPluginHandler = new PluginHandler(looper);
+        mContext = context;
+        mPm = pm;
+        mAction = action;
+        mListener = listener;
+        mAllowMultiple = allowMultiple;
+        mVersion = version;
+        isDebuggable = debuggable;
+        mClassLoaderFactory = classLoaderFactory;
+    }
+
+    public void startListening() {
+        if (DEBUG) Log.d(TAG, "startListening");
+        mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(this, filter);
+        filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
+        mContext.registerReceiver(this, filter);
+    }
+
+    public void stopListening() {
+        if (DEBUG) Log.d(TAG, "stopListening");
+        ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
+        for (PluginInfo plugin : plugins) {
+            mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED,
+                    plugin.mPlugin).sendToTarget();
+        }
+        mContext.unregisterReceiver(this);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (DEBUG) Log.d(TAG, "onReceive " + intent);
+        if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+            mPluginHandler.sendEmptyMessage(PluginHandler.QUERY_ALL);
+        } else {
+            Uri data = intent.getData();
+            String pkgName = data.getEncodedSchemeSpecificPart();
+            mPluginHandler.obtainMessage(PluginHandler.REMOVE_PKG, pkgName).sendToTarget();
+            if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) {
+                mPluginHandler.obtainMessage(PluginHandler.QUERY_PKG, pkgName).sendToTarget();
+            }
+        }
+    }
+
+    public boolean checkAndDisable(String className) {
+        boolean disableAny = false;
+        ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
+        for (PluginInfo info : plugins) {
+            if (className.startsWith(info.mPackage)) {
+                disable(info);
+                disableAny = true;
+            }
+        }
+        return disableAny;
+    }
+
+    public void disableAll() {
+        ArrayList<PluginInfo> plugins = new ArrayList<>(mPluginHandler.mPlugins);
+        plugins.forEach(this::disable);
+    }
+
+    private void disable(PluginInfo info) {
+        // Live by the sword, die by the sword.
+        // Misbehaving plugins get disabled and won't come back until uninstall/reinstall.
+
+        // If a plugin is detected in the stack of a crash then this will be called for that
+        // plugin, if the plugin causing a crash cannot be identified, they are all disabled
+        // assuming one of them must be bad.
+        Log.w(TAG, "Disabling plugin " + info.mPackage + "/" + info.mClass);
+        mPm.setComponentEnabledSetting(
+                new ComponentName(info.mPackage, info.mClass),
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+    }
+
+    private class MainHandler extends Handler {
+        private static final int PLUGIN_CONNECTED = 1;
+        private static final int PLUGIN_DISCONNECTED = 2;
+
+        public MainHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case PLUGIN_CONNECTED:
+                    if (DEBUG) Log.d(TAG, "onPluginConnected");
+                    PluginInfo<T> info = (PluginInfo<T>) msg.obj;
+                    info.mPlugin.onCreate(mContext, info.mPluginContext);
+                    mListener.onPluginConnected(info.mPlugin);
+                    break;
+                case PLUGIN_DISCONNECTED:
+                    if (DEBUG) Log.d(TAG, "onPluginDisconnected");
+                    mListener.onPluginDisconnected((T) msg.obj);
+                    ((T) msg.obj).onDestroy();
+                    break;
+                default:
+                    super.handleMessage(msg);
+                    break;
+            }
+        }
+    }
+
+    static class ClassLoaderFactory {
+        public ClassLoader createClassLoader(String path, ClassLoader base) {
+            return new PathClassLoader(path, base);
+        }
+    }
+
+    private class PluginHandler extends Handler {
+        private static final int QUERY_ALL = 1;
+        private static final int QUERY_PKG = 2;
+        private static final int REMOVE_PKG = 3;
+
+        private final ArrayList<PluginInfo<T>> mPlugins = new ArrayList<>();
+
+        public PluginHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case QUERY_ALL:
+                    if (DEBUG) Log.d(TAG, "queryAll " + mAction);
+                    for (int i = mPlugins.size() - 1; i >= 0; i--) {
+                        PluginInfo<T> plugin = mPlugins.get(i);
+                        mListener.onPluginDisconnected(plugin.mPlugin);
+                        plugin.mPlugin.onDestroy();
+                    }
+                    mPlugins.clear();
+                    handleQueryPlugins(null);
+                    break;
+                case REMOVE_PKG:
+                    String pkg = (String) msg.obj;
+                    for (int i = mPlugins.size() - 1; i >= 0; i--) {
+                        final PluginInfo<T> plugin = mPlugins.get(i);
+                        if (plugin.mPackage.equals(pkg)) {
+                            mMainHandler.obtainMessage(MainHandler.PLUGIN_DISCONNECTED,
+                                    plugin.mPlugin).sendToTarget();
+                            mPlugins.remove(i);
+                        }
+                    }
+                    break;
+                case QUERY_PKG:
+                    String p = (String) msg.obj;
+                    if (DEBUG) Log.d(TAG, "queryPkg " + p + " " + mAction);
+                    if (mAllowMultiple || (mPlugins.size() == 0)) {
+                        handleQueryPlugins(p);
+                    } else {
+                        if (DEBUG) Log.d(TAG, "Too many of " + mAction);
+                    }
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+
+        private void handleQueryPlugins(String pkgName) {
+            // This isn't actually a service and shouldn't ever be started, but is
+            // a convenient PM based way to manage our plugins.
+            Intent intent = new Intent(mAction);
+            if (pkgName != null) {
+                intent.setPackage(pkgName);
+            }
+            List<ResolveInfo> result =
+                    mPm.queryIntentServices(intent, 0);
+            if (DEBUG) Log.d(TAG, "Found " + result.size() + " plugins");
+            if (result.size() > 1 && !mAllowMultiple) {
+                // TODO: Show warning.
+                Log.w(TAG, "Multiple plugins found for " + mAction);
+                return;
+            }
+            for (ResolveInfo info : result) {
+                ComponentName name = new ComponentName(info.serviceInfo.packageName,
+                        info.serviceInfo.name);
+                PluginInfo<T> t = handleLoadPlugin(name);
+                if (t == null) continue;
+                mMainHandler.obtainMessage(mMainHandler.PLUGIN_CONNECTED, t).sendToTarget();
+                mPlugins.add(t);
+            }
+        }
+
+        protected PluginInfo<T> handleLoadPlugin(ComponentName component) {
+            // This was already checked, but do it again here to make extra extra sure, we don't
+            // use these on production builds.
+            if (!isDebuggable) {
+                // Never ever ever allow these on production builds, they are only for prototyping.
+                Log.d(TAG, "Somehow hit second debuggable check");
+                return null;
+            }
+            String pkg = component.getPackageName();
+            String cls = component.getClassName();
+            try {
+                PackageManager pm = mPm;
+                ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
+                // TODO: This probably isn't needed given that we don't have IGNORE_SECURITY on
+                if (pm.checkPermission(PLUGIN_PERMISSION, pkg)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    Log.d(TAG, "Plugin doesn't have permission: " + pkg);
+                    return null;
+                }
+                // Create our own ClassLoader so we can use our own code as the parent.
+                ClassLoader classLoader = mClassLoaderFactory.createClassLoader(info.sourceDir,
+                        getClass().getClassLoader());
+                Context pluginContext = new PluginContextWrapper(
+                        mContext.createApplicationContext(info, 0), classLoader);
+                Class<?> pluginClass = Class.forName(cls, true, classLoader);
+                T plugin = (T) pluginClass.newInstance();
+                if (plugin.getVersion() != mVersion) {
+                    // TODO: Warn user.
+                    Log.w(TAG, "Plugin has invalid interface version " + plugin.getVersion()
+                            + ", expected " + mVersion);
+                    return null;
+                }
+                if (DEBUG) Log.d(TAG, "createPlugin");
+                return new PluginInfo(pkg, cls, plugin, pluginContext);
+            } catch (Exception e) {
+                Log.w(TAG, "Couldn't load plugin: " + pkg, e);
+                return null;
+            }
+        }
+    }
+
+    public static class PluginContextWrapper extends ContextWrapper {
+        private final ClassLoader mClassLoader;
+        private LayoutInflater mInflater;
+
+        public PluginContextWrapper(Context base, ClassLoader classLoader) {
+            super(base);
+            mClassLoader = classLoader;
+        }
+
+        @Override
+        public ClassLoader getClassLoader() {
+            return mClassLoader;
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            if (LAYOUT_INFLATER_SERVICE.equals(name)) {
+                if (mInflater == null) {
+                    mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
+                }
+                return mInflater;
+            }
+            return getBaseContext().getSystemService(name);
+        }
+    }
+
+    private static class PluginInfo<T> {
+        private final Context mPluginContext;
+        private T mPlugin;
+        private String mClass;
+        private String mPackage;
+
+        public PluginInfo(String pkg, String cls, T plugin, Context pluginContext) {
+            mPlugin = plugin;
+            mClass = cls;
+            mPackage = pkg;
+            mPluginContext = pluginContext;
+        }
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
new file mode 100644
index 0000000..b2f92d6
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginListener.java
@@ -0,0 +1,36 @@
+/*
+ * 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.plugins;
+
+/**
+ * Interface for listening to plugins being connected.
+ */
+public interface PluginListener<T extends Plugin> {
+    /**
+     * Called when the plugin has been loaded and is ready to be used.
+     * This may be called multiple times if multiple plugins are allowed.
+     * It may also be called in the future if the plugin package changes
+     * and needs to be reloaded.
+     */
+    void onPluginConnected(T plugin);
+
+    /**
+     * Called when a plugin has been uninstalled/updated and should be removed
+     * from use.
+     */
+    default void onPluginDisconnected(T plugin) {
+        // Optional.
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
new file mode 100644
index 0000000..aa0b3c5
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginManager.java
@@ -0,0 +1,138 @@
+/*
+ * 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.plugins;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+/**
+ * @see Plugin
+ */
+public class PluginManager {
+
+    private static PluginManager sInstance;
+
+    private final HandlerThread mBackgroundThread;
+    private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap
+            = new ArrayMap<>();
+    private final Context mContext;
+    private final PluginInstanceManagerFactory mFactory;
+    private final boolean isDebuggable;
+
+    private PluginManager(Context context) {
+        this(context, new PluginInstanceManagerFactory(), Build.IS_DEBUGGABLE,
+                Thread.getDefaultUncaughtExceptionHandler());
+    }
+
+    @VisibleForTesting
+    PluginManager(Context context, PluginInstanceManagerFactory factory, boolean debuggable,
+            UncaughtExceptionHandler defaultHandler) {
+        mContext = context;
+        mFactory = factory;
+        mBackgroundThread = new HandlerThread("Plugins");
+        mBackgroundThread.start();
+        isDebuggable = debuggable;
+
+        PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler(
+                defaultHandler);
+        Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
+    }
+
+    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            int version) {
+        addPluginListener(action, listener, version, false);
+    }
+
+    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
+            int version, boolean allowMultiple) {
+        if (!isDebuggable) {
+            // Never ever ever allow these on production builds, they are only for prototyping.
+            return;
+        }
+        PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener,
+                allowMultiple, mBackgroundThread.getLooper(), version);
+        p.startListening();
+        mPluginMap.put(listener, p);
+    }
+
+    public void removePluginListener(PluginListener<?> listener) {
+        if (!isDebuggable) {
+            // Never ever ever allow these on production builds, they are only for prototyping.
+            return;
+        }
+        if (!mPluginMap.containsKey(listener)) return;
+        mPluginMap.remove(listener).stopListening();
+    }
+
+    public static PluginManager getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new PluginManager(context.getApplicationContext());
+        }
+        return sInstance;
+    }
+
+    @VisibleForTesting
+    public static class PluginInstanceManagerFactory {
+        public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context,
+                String action, PluginListener<T> listener, boolean allowMultiple, Looper looper,
+                int version) {
+            return new PluginInstanceManager(context, action, listener, allowMultiple, looper,
+                    version);
+        }
+    }
+
+    private class PluginExceptionHandler implements UncaughtExceptionHandler {
+        private final UncaughtExceptionHandler mHandler;
+
+        private PluginExceptionHandler(UncaughtExceptionHandler handler) {
+            mHandler = handler;
+        }
+
+        @Override
+        public void uncaughtException(Thread thread, Throwable throwable) {
+            // Search for and disable plugins that may have been involved in this crash.
+            boolean disabledAny = checkStack(throwable);
+            if (!disabledAny) {
+                // We couldn't find any plugins involved in this crash, just to be safe
+                // disable all the plugins, so we can be sure that SysUI is running as
+                // best as possible.
+                for (PluginInstanceManager manager : mPluginMap.values()) {
+                    manager.disableAll();
+                }
+            }
+
+            // Run the normal exception handler so we can crash and cleanup our state.
+            mHandler.uncaughtException(thread, throwable);
+        }
+
+        private boolean checkStack(Throwable throwable) {
+            if (throwable == null) return false;
+            boolean disabledAny = false;
+            for (StackTraceElement element : throwable.getStackTrace()) {
+                for (PluginInstanceManager manager : mPluginMap.values()) {
+                    disabledAny |= manager.checkAndDisable(element.getClassName());
+                }
+            }
+            return disabledAny | checkStack(throwable.getCause());
+        }
+    }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginUtils.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginUtils.java
new file mode 100644
index 0000000..af49d43
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/PluginUtils.java
@@ -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.
+ */
+
+package com.android.systemui.plugins;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+
+public class PluginUtils {
+
+    public static void setId(Context sysuiContext, View view, String id) {
+        int i = sysuiContext.getResources().getIdentifier(id, "id", sysuiContext.getPackageName());
+        view.setId(i);
+    }
+}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 9182f7e..364885a 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -37,3 +37,6 @@
 
 -keep class ** extends android.support.v14.preference.PreferenceFragment
 -keep class com.android.systemui.tuner.*
+-keep class com.android.systemui.plugins.** {
+    public protected **;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 4b9ae2a..e300aff 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -29,7 +29,11 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.systemui.plugins.OverlayPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.stackdivider.Divider;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -183,6 +187,18 @@
                 mServices[i].onBootCompleted();
             }
         }
+        PluginManager.getInstance(this).addPluginListener(OverlayPlugin.ACTION,
+                new PluginListener<OverlayPlugin>() {
+            @Override
+            public void onPluginConnected(OverlayPlugin plugin) {
+                PhoneStatusBar phoneStatusBar = getComponent(PhoneStatusBar.class);
+                if (phoneStatusBar != null) {
+                    plugin.setup(phoneStatusBar.getStatusBarWindow(),
+                            phoneStatusBar.getNavigationBarView());
+                }
+            }
+        }, OverlayPlugin.VERSION, true /* Allow multiple plugins */);
+
         mServicesStarted = true;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 222e8df..9e5b881 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -43,6 +43,7 @@
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.inputmethod.InputMethodManager;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
@@ -52,7 +53,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-public class NavigationBarView extends LinearLayout {
+public class NavigationBarView extends FrameLayout {
     final static boolean DEBUG = false;
     final static String TAG = "StatusBar/NavBarView";
 
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 5d6ac12..23967aa 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -34,6 +34,7 @@
     frameworks/base/packages/SystemUI/res \
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
+    SystemUIPluginLib \
     Keyguard \
     android-support-v7-recyclerview \
     android-support-v7-preference \
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 869805e..d943eb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -17,13 +17,17 @@
 
 import android.content.Context;
 import android.support.test.InstrumentationRegistry;
-import android.test.AndroidTestCase;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue;
 import org.junit.Before;
 
 /**
  * Base class that does System UI specific setup.
  */
 public class SysuiTestCase {
+
+    private Handler mHandler;
     protected Context mContext;
 
     @Before
@@ -34,4 +38,65 @@
     protected Context getContext() {
         return mContext;
     }
+
+    protected void waitForIdleSync() {
+        if (mHandler == null) {
+            mHandler = new Handler(Looper.getMainLooper());
+        }
+        waitForIdleSync(mHandler);
+    }
+
+    protected void waitForIdleSync(Handler h) {
+        validateThread(h.getLooper());
+        Idler idler = new Idler(null);
+        h.getLooper().getQueue().addIdleHandler(idler);
+        // Ensure we are non-idle, so the idle handler can run.
+        h.post(new EmptyRunnable());
+        idler.waitForIdle();
+    }
+
+    private static final void validateThread(Looper l) {
+        if (Looper.myLooper() == l) {
+            throw new RuntimeException(
+                "This method can not be called from the looper being synced");
+        }
+    }
+
+    public static final class EmptyRunnable implements Runnable {
+        public void run() {
+        }
+    }
+
+    public static final class Idler implements MessageQueue.IdleHandler {
+        private final Runnable mCallback;
+        private boolean mIdle;
+
+        public Idler(Runnable callback) {
+            mCallback = callback;
+            mIdle = false;
+        }
+
+        @Override
+        public boolean queueIdle() {
+            if (mCallback != null) {
+                mCallback.run();
+            }
+            synchronized (this) {
+                mIdle = true;
+                notifyAll();
+            }
+            return false;
+        }
+
+        public void waitForIdle() {
+            synchronized (this) {
+                while (!mIdle) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
new file mode 100644
index 0000000..ab7de39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java
@@ -0,0 +1,284 @@
+/*
+ * 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.plugins;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginInstanceManager.ClassLoaderFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PluginInstanceManagerTest extends SysuiTestCase {
+
+    // Static since the plugin needs to be generated by the PluginInstanceManager using newInstance.
+    private static Plugin sMockPlugin;
+
+    private HandlerThread mHandlerThread;
+    private Context mContextWrapper;
+    private PackageManager mMockPm;
+    private PluginListener mMockListener;
+    private PluginInstanceManager mPluginInstanceManager;
+
+    @Before
+    public void setup() throws Exception {
+        mHandlerThread = new HandlerThread("test_thread");
+        mHandlerThread.start();
+        mContextWrapper = new MyContextWrapper(getContext());
+        mMockPm = mock(PackageManager.class);
+        mMockListener = mock(PluginListener.class);
+        mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
+                mMockListener, true, mHandlerThread.getLooper(), 1, true,
+                new TestClassLoaderFactory());
+        sMockPlugin = mock(Plugin.class);
+        when(sMockPlugin.getVersion()).thenReturn(1);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        sMockPlugin = null;
+    }
+
+    @Test
+    public void testNoPlugins() {
+        when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn(
+                Collections.emptyList());
+        mPluginInstanceManager.startListening();
+
+        waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+        waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+        verify(mMockListener, Mockito.never()).onPluginConnected(
+                ArgumentCaptor.forClass(Plugin.class).capture());
+    }
+
+    @Test
+    public void testPluginCreate() {
+        createPlugin();
+
+        // Verify startup lifecycle
+        verify(sMockPlugin).onCreate(ArgumentCaptor.forClass(Context.class).capture(),
+                ArgumentCaptor.forClass(Context.class).capture());
+        verify(mMockListener).onPluginConnected(ArgumentCaptor.forClass(Plugin.class).capture());
+    }
+
+    @Test
+    public void testPluginDestroy() {
+        createPlugin(); // Get into valid created state.
+
+        mPluginInstanceManager.stopListening();
+
+        waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+        waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+        // Verify shutdown lifecycle
+        verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture());
+        verify(sMockPlugin).onDestroy();
+    }
+
+    @Test
+    public void testIncorrectVersion() {
+        setupFakePmQuery();
+        when(sMockPlugin.getVersion()).thenReturn(2);
+
+        mPluginInstanceManager.startListening();
+
+        waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+        waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+        // Plugin shouldn't be connected because it is the wrong version.
+        verify(mMockListener, Mockito.never()).onPluginConnected(
+                ArgumentCaptor.forClass(Plugin.class).capture());
+    }
+
+    @Test
+    public void testReloadOnChange() {
+        createPlugin(); // Get into valid created state.
+
+        // Send a package changed broadcast.
+        Intent i = new Intent(Intent.ACTION_PACKAGE_CHANGED,
+                Uri.fromParts("package", "com.android.systemui", null));
+        mPluginInstanceManager.onReceive(mContextWrapper, i);
+
+        waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+        waitForIdleSync(mPluginInstanceManager.mMainHandler);
+
+        // Verify the old one was destroyed.
+        verify(mMockListener).onPluginDisconnected(ArgumentCaptor.forClass(Plugin.class).capture());
+        verify(sMockPlugin).onDestroy();
+        // Also verify we got a second onCreate.
+        verify(sMockPlugin, Mockito.times(2)).onCreate(
+                ArgumentCaptor.forClass(Context.class).capture(),
+                ArgumentCaptor.forClass(Context.class).capture());
+        verify(mMockListener, Mockito.times(2)).onPluginConnected(
+                ArgumentCaptor.forClass(Plugin.class).capture());
+    }
+
+    @Test
+    public void testNonDebuggable() {
+        // Create a version that thinks the build is not debuggable.
+        mPluginInstanceManager = new PluginInstanceManager(mContextWrapper, mMockPm, "myAction",
+                mMockListener, true, mHandlerThread.getLooper(), 1, false,
+                new TestClassLoaderFactory());
+        setupFakePmQuery();
+
+        mPluginInstanceManager.startListening();
+
+        waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+        waitForIdleSync(mPluginInstanceManager.mMainHandler);;
+
+        // Non-debuggable build should receive no plugins.
+        verify(mMockListener, Mockito.never()).onPluginConnected(
+                ArgumentCaptor.forClass(Plugin.class).capture());
+    }
+
+    @Test
+    public void testCheckAndDisable() {
+        createPlugin(); // Get into valid created state.
+
+        // Start with an unrelated class.
+        boolean result = mPluginInstanceManager.checkAndDisable(Activity.class.getName());
+        assertFalse(result);
+        verify(mMockPm, Mockito.never()).setComponentEnabledSetting(
+                ArgumentCaptor.forClass(ComponentName.class).capture(),
+                ArgumentCaptor.forClass(int.class).capture(),
+                ArgumentCaptor.forClass(int.class).capture());
+
+        // Now hand it a real class and make sure it disables the plugin.
+        result = mPluginInstanceManager.checkAndDisable(TestPlugin.class.getName());
+        assertTrue(result);
+        verify(mMockPm).setComponentEnabledSetting(
+                ArgumentCaptor.forClass(ComponentName.class).capture(),
+                ArgumentCaptor.forClass(int.class).capture(),
+                ArgumentCaptor.forClass(int.class).capture());
+    }
+
+    @Test
+    public void testDisableAll() {
+        createPlugin(); // Get into valid created state.
+
+        mPluginInstanceManager.disableAll();
+
+        verify(mMockPm).setComponentEnabledSetting(
+                ArgumentCaptor.forClass(ComponentName.class).capture(),
+                ArgumentCaptor.forClass(int.class).capture(),
+                ArgumentCaptor.forClass(int.class).capture());
+    }
+
+    private void setupFakePmQuery() {
+        List<ResolveInfo> list = new ArrayList<>();
+        ResolveInfo info = new ResolveInfo();
+        info.serviceInfo = new ServiceInfo();
+        info.serviceInfo.packageName = "com.android.systemui";
+        info.serviceInfo.name = TestPlugin.class.getName();
+        list.add(info);
+        when(mMockPm.queryIntentServices(Mockito.any(), Mockito.anyInt())).thenReturn(list);
+
+        when(mMockPm.checkPermission(Mockito.anyString(), Mockito.anyString())).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+
+        try {
+            ApplicationInfo appInfo = getContext().getApplicationInfo();
+            when(mMockPm.getApplicationInfo(Mockito.anyString(), Mockito.anyInt())).thenReturn(
+                    appInfo);
+        } catch (NameNotFoundException e) {
+            // Shouldn't be possible, but if it is, we want to fail.
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void createPlugin() {
+        setupFakePmQuery();
+
+        mPluginInstanceManager.startListening();
+
+        waitForIdleSync(mPluginInstanceManager.mPluginHandler);
+        waitForIdleSync(mPluginInstanceManager.mMainHandler);
+    }
+
+    private static class TestClassLoaderFactory extends ClassLoaderFactory {
+        @Override
+        public ClassLoader createClassLoader(String path, ClassLoader base) {
+            return base;
+        }
+    }
+
+    // Real context with no registering/unregistering of receivers.
+    private static class MyContextWrapper extends ContextWrapper {
+        public MyContextWrapper(Context base) {
+            super(base);
+        }
+
+        @Override
+        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+            return null;
+        }
+
+        @Override
+        public void unregisterReceiver(BroadcastReceiver receiver) {
+        }
+    }
+
+    public static class TestPlugin implements Plugin {
+        @Override
+        public int getVersion() {
+            return sMockPlugin.getVersion();
+        }
+
+        @Override
+        public void onCreate(Context sysuiContext, Context pluginContext) {
+            sMockPlugin.onCreate(sysuiContext, pluginContext);
+        }
+
+        @Override
+        public void onDestroy() {
+            sMockPlugin.onDestroy();
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
new file mode 100644
index 0000000..56e742a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.plugins;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.PluginManager.PluginInstanceManagerFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PluginManagerTest extends SysuiTestCase {
+
+    private PluginInstanceManagerFactory mMockFactory;
+    private PluginInstanceManager mMockPluginInstance;
+    private PluginManager mPluginManager;
+    private PluginListener mMockListener;
+
+    private UncaughtExceptionHandler mRealExceptionHandler;
+    private UncaughtExceptionHandler mMockExceptionHandler;
+    private UncaughtExceptionHandler mPluginExceptionHandler;
+
+    @Before
+    public void setup() throws Exception {
+        mRealExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
+        mMockExceptionHandler = mock(UncaughtExceptionHandler.class);
+        mMockFactory = mock(PluginInstanceManagerFactory.class);
+        mMockPluginInstance = mock(PluginInstanceManager.class);
+        when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(),
+                Mockito.anyBoolean(), Mockito.any(), Mockito.anyInt()))
+                .thenReturn(mMockPluginInstance);
+        mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler);
+        resetExceptionHandler();
+        mMockListener = mock(PluginListener.class);
+    }
+
+    @Test
+    public void testAddListener() {
+        mPluginManager.addPluginListener("myAction", mMockListener, 1);
+
+        verify(mMockPluginInstance).startListening();
+    }
+
+    @Test
+    public void testRemoveListener() {
+        mPluginManager.addPluginListener("myAction", mMockListener, 1);
+
+        mPluginManager.removePluginListener(mMockListener);
+        verify(mMockPluginInstance).stopListening();
+    }
+
+    @Test
+    public void testNonDebuggable() {
+        mPluginManager = new PluginManager(getContext(), mMockFactory, false,
+                mMockExceptionHandler);
+        resetExceptionHandler();
+        mPluginManager.addPluginListener("myAction", mMockListener, 1);
+
+        verify(mMockPluginInstance, Mockito.never()).startListening();
+    }
+
+    @Test
+    public void testExceptionHandler_foundPlugin() {
+        mPluginManager.addPluginListener("myAction", mMockListener, 1);
+        when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(true);
+
+        mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());
+
+        verify(mMockPluginInstance, Mockito.atLeastOnce()).checkAndDisable(
+                ArgumentCaptor.forClass(String.class).capture());
+        verify(mMockPluginInstance, Mockito.never()).disableAll();
+        verify(mMockExceptionHandler).uncaughtException(
+                ArgumentCaptor.forClass(Thread.class).capture(),
+                ArgumentCaptor.forClass(Throwable.class).capture());
+    }
+
+    @Test
+    public void testExceptionHandler_noFoundPlugin() {
+        mPluginManager.addPluginListener("myAction", mMockListener, 1);
+        when(mMockPluginInstance.checkAndDisable(Mockito.any())).thenReturn(false);
+
+        mPluginExceptionHandler.uncaughtException(Thread.currentThread(), new Throwable());
+
+        verify(mMockPluginInstance, Mockito.atLeastOnce()).checkAndDisable(
+                ArgumentCaptor.forClass(String.class).capture());
+        verify(mMockPluginInstance).disableAll();
+        verify(mMockExceptionHandler).uncaughtException(
+                ArgumentCaptor.forClass(Thread.class).capture(),
+                ArgumentCaptor.forClass(Throwable.class).capture());
+    }
+
+    private void resetExceptionHandler() {
+        mPluginExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
+        // Set back the real exception handler so the test can crash if it wants to.
+        Thread.setDefaultUncaughtExceptionHandler(mRealExceptionHandler);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f740b4b..aa53676 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1715,6 +1715,7 @@
                     }
                     if (ar != null && mCompatModePackages.getPackageNotifyUnsupportedZoomLocked(
                             ar.packageName)) {
+                        // TODO(multi-display): Show dialog on appropriate display.
                         mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
                                 ActivityManagerService.this, mContext, ar.info.applicationInfo);
                         mUnsupportedDisplaySizeDialog.show();
@@ -3778,16 +3779,19 @@
             app.killed = false;
             app.killedByAm = false;
             checkTime(startTime, "startProcess: starting to update pids map");
+            ProcessRecord oldApp;
             synchronized (mPidsSelfLocked) {
-                ProcessRecord oldApp;
-                // If there is already an app occupying that pid that hasn't been cleaned up
-                if ((oldApp = mPidsSelfLocked.get(startResult.pid)) != null && !app.isolated) {
-                    // Clean up anything relating to this pid first
-                    Slog.w(TAG, "Reusing pid " + startResult.pid
-                            + " while app is still mapped to it");
-                    cleanUpApplicationRecordLocked(oldApp, false, false, -1,
-                            true /*replacingPid*/);
-                }
+                oldApp = mPidsSelfLocked.get(startResult.pid);
+            }
+            // If there is already an app occupying that pid that hasn't been cleaned up
+            if (oldApp != null && !app.isolated) {
+                // Clean up anything relating to this pid first
+                Slog.w(TAG, "Reusing pid " + startResult.pid
+                        + " while app is still mapped to it");
+                cleanUpApplicationRecordLocked(oldApp, false, false, -1,
+                        true /*replacingPid*/);
+            }
+            synchronized (mPidsSelfLocked) {
                 this.mPidsSelfLocked.put(startResult.pid, app);
                 if (isActivityProcess) {
                     Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
@@ -19037,8 +19041,8 @@
     }
 
     /**
-     * Decide based on the configuration whether we should shouw the ANR,
-     * crash, etc dialogs.  The idea is that if there is no affordence to
+     * Decide based on the configuration whether we should show the ANR,
+     * crash, etc dialogs.  The idea is that if there is no affordance to
      * press the on-screen buttons, or the user experience would be more
      * greatly impacted than the crash itself, we shouldn't show the dialog.
      *
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 06eeb2c..b82e28d 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -118,6 +118,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.Display;
 
 import com.android.internal.app.IVoiceInteractor;
@@ -228,6 +229,13 @@
     // stack and the new stack will be on top of all stacks.
     static final int REMOVE_TASK_MODE_MOVING_TO_TOP = 2;
 
+    // The height/width divide used when fitting a task within a bounds with method
+    // {@link #fitWithinBounds}.
+    // We always want the task to to be visible in the bounds without affecting its size when
+    // fitting. To make sure this is the case, we don't adjust the task left or top side pass
+    // the input bounds right or bottom side minus the width or height divided by this value.
+    private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3;
+
     final ActivityManagerService mService;
     final WindowManagerService mWindowManager;
     private final RecentTasks mRecentTasks;
@@ -321,6 +329,12 @@
     /** The attached Display's unique identifier, or -1 if detached */
     int mDisplayId;
 
+    /** Temp variables used during override configuration update. */
+    private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
+    private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
+    private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
+    private final Rect tempRect2 = new Rect();
+
     /** Run all ActivityStacks through this */
     final ActivityStackSupervisor mStackSupervisor;
 
@@ -4524,6 +4538,86 @@
         }
     }
 
+    /** Update override configurations of all tasks in the stack. */
+    void updateOverrideConfiguration(Rect stackBounds, Rect tempTaskBounds,
+            Rect tempTaskInsetBounds) {
+
+        final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : stackBounds;
+        final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds;
+
+        mTmpBounds.clear();
+        mTmpConfigs.clear();
+        mTmpInsetBounds.clear();
+
+        for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
+            final TaskRecord task = mTaskHistory.get(i);
+            if (task.isResizeable()) {
+                if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
+                    // For freeform stack we don't adjust the size of the tasks to match that
+                    // of the stack, but we do try to make sure the tasks are still contained
+                    // with the bounds of the stack.
+                    tempRect2.set(task.mBounds);
+                    fitWithinBounds(tempRect2, stackBounds);
+                    task.updateOverrideConfiguration(tempRect2);
+                } else {
+                    task.updateOverrideConfiguration(taskBounds, insetBounds);
+                }
+            }
+
+            mTmpConfigs.put(task.taskId, task.mOverrideConfig);
+            mTmpBounds.put(task.taskId, task.mBounds);
+            if (tempTaskInsetBounds != null) {
+                mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
+            }
+        }
+
+        // We might trigger a configuration change. Save the current task bounds for freezing.
+        mWindowManager.prepareFreezingTaskBounds(mStackId);
+        mFullscreen = mWindowManager.resizeStack(mStackId, stackBounds, mTmpConfigs, mTmpBounds,
+                mTmpInsetBounds);
+        setBounds(stackBounds);
+    }
+
+
+    /**
+     * Adjust bounds to stay within stack bounds.
+     *
+     * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way
+     * that keep them unchanged, but be contained within the stack bounds.
+     *
+     * @param bounds Bounds to be adjusted.
+     * @param stackBounds Bounds within which the other bounds should remain.
+     */
+    private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
+        if (stackBounds == null || stackBounds.contains(bounds)) {
+            return;
+        }
+
+        if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) {
+            final int maxRight = stackBounds.right
+                    - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER);
+            int horizontalDiff = stackBounds.left - bounds.left;
+            if ((horizontalDiff < 0 && bounds.left >= maxRight)
+                    || (bounds.left + horizontalDiff >= maxRight)) {
+                horizontalDiff = maxRight - bounds.left;
+            }
+            bounds.left += horizontalDiff;
+            bounds.right += horizontalDiff;
+        }
+
+        if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) {
+            final int maxBottom = stackBounds.bottom
+                    - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER);
+            int verticalDiff = stackBounds.top - bounds.top;
+            if ((verticalDiff < 0 && bounds.top >= maxBottom)
+                    || (bounds.top + verticalDiff >= maxBottom)) {
+                verticalDiff = maxBottom - bounds.top;
+            }
+            bounds.top += verticalDiff;
+            bounds.bottom += verticalDiff;
+        }
+    }
+
     /**
      * Make sure the given activity matches the current configuration. Returns false if the activity
      * had to be destroyed.  Returns true if the configuration is the same, or the activity will
@@ -4673,7 +4767,7 @@
         // that has come back from the app after going idle.  In that case
         // we just want to leave the official config object now in the
         // activity and do nothing else.
-        int taskChanges = oldTaskOverride.diff(taskConfig);
+        int taskChanges = oldTaskOverride.diff(taskConfig, true /* skipUndefined */);
         // We don't want to use size changes if they don't cross boundaries that are important to
         // the app.
         if ((taskChanges & CONFIG_SCREEN_SIZE) != 0) {
@@ -4692,48 +4786,6 @@
                 taskChanges &= ~CONFIG_SMALLEST_SCREEN_SIZE;
             }
         }
-        return catchConfigChangesFromUnset(taskConfig, oldTaskOverride, taskChanges);
-    }
-
-    private static int catchConfigChangesFromUnset(Configuration taskConfig,
-            Configuration oldTaskOverride, int taskChanges) {
-        if (taskChanges == 0) {
-            // {@link Configuration#diff} doesn't catch changes from unset values.
-            // Check for changes we care about.
-            if (oldTaskOverride.orientation != taskConfig.orientation) {
-                taskChanges |= CONFIG_ORIENTATION;
-            }
-            // We want to explicitly track situations where the size configuration goes from
-            // undefined to defined. We don't care about crossing the threshold in that case,
-            // because there is no threshold.
-            final int oldHeight = oldTaskOverride.screenHeightDp;
-            final int newHeight = taskConfig.screenHeightDp;
-            final int undefinedHeight = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
-            if ((oldHeight == undefinedHeight && newHeight != undefinedHeight)
-                    || (oldHeight != undefinedHeight && newHeight == undefinedHeight)) {
-                taskChanges |= CONFIG_SCREEN_SIZE;
-            }
-            final int oldWidth = oldTaskOverride.screenWidthDp;
-            final int newWidth = taskConfig.screenWidthDp;
-            final int undefinedWidth = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-            if ((oldWidth == undefinedWidth && newWidth != undefinedWidth)
-                    || (oldWidth != undefinedWidth && newWidth == undefinedWidth)) {
-                taskChanges |= CONFIG_SCREEN_SIZE;
-            }
-            final int oldSmallest = oldTaskOverride.smallestScreenWidthDp;
-            final int newSmallest = taskConfig.smallestScreenWidthDp;
-            final int undefinedSmallest = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
-            if ((oldSmallest == undefinedSmallest && newSmallest != undefinedSmallest)
-                    || (oldSmallest != undefinedSmallest && newSmallest == undefinedSmallest)) {
-                taskChanges |= CONFIG_SMALLEST_SCREEN_SIZE;
-            }
-            final int oldLayout = oldTaskOverride.screenLayout;
-            final int newLayout = taskConfig.screenLayout;
-            if ((oldLayout == SCREENLAYOUT_UNDEFINED && newLayout != SCREENLAYOUT_UNDEFINED)
-                || (oldLayout != SCREENLAYOUT_UNDEFINED && newLayout == SCREENLAYOUT_UNDEFINED)) {
-                taskChanges |= CONFIG_SCREEN_LAYOUT;
-            }
-        }
         return taskChanges;
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 80d51e5..8602bb6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -267,13 +267,6 @@
     /** Action restriction: launching the activity is restricted by an app op. */
     private static final int ACTIVITY_RESTRICTION_APPOP = 2;
 
-    // The height/width divide used when fitting a task within a bounds with method
-    // {@link #fitWithinBounds}.
-    // We always want the task to to be visible in the bounds without affecting its size when
-    // fitting. To make sure this is the case, we don't adjust the task left or top side pass
-    // the input bounds right or bottom side minus the width or height divided by this value.
-    private static final int FIT_WITHIN_BOUNDS_DIVIDER = 3;
-
     /** Status Bar Service **/
     private IBinder mToken = new Binder();
     private IStatusBarService mStatusBarService;
@@ -402,13 +395,11 @@
     /** Used to keep resumeTopActivityUncheckedLocked() from being entered recursively */
     boolean inResumeTopActivity;
 
-    // temp. rects used during resize calculation so we don't need to create a new object each time.
+    /**
+     * Temporary rect used during docked stack resize calculation so we don't need to create a new
+     * object each time.
+     */
     private final Rect tempRect = new Rect();
-    private final Rect tempRect2 = new Rect();
-
-    private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
-    private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
-    private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
 
     // The default minimal size that will be used if the activity doesn't specify its minimal size.
     // It will be calculated when the default display gets added.
@@ -1912,6 +1903,7 @@
         if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
             return null;
         }
+        // TODO(multi-display): Allow creating stacks on secondary displays.
         return createStackOnDisplay(stackId, Display.DEFAULT_DISPLAY, createOnTop);
     }
 
@@ -1923,14 +1915,6 @@
         return allStacks;
     }
 
-    IBinder getHomeActivityToken() {
-        ActivityRecord homeActivity = getHomeActivity();
-        if (homeActivity != null) {
-            return homeActivity.appToken;
-        }
-        return null;
-    }
-
     ActivityRecord getHomeActivity() {
         return getHomeActivityForUser(mCurrentUser);
     }
@@ -2060,39 +2044,7 @@
             return;
         }
 
-        mTmpBounds.clear();
-        mTmpConfigs.clear();
-        mTmpInsetBounds.clear();
-        final ArrayList<TaskRecord> tasks = stack.getAllTasks();
-        final Rect taskBounds = tempTaskBounds != null ? tempTaskBounds : bounds;
-        final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds;
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            final TaskRecord task = tasks.get(i);
-            if (task.isResizeable()) {
-                if (stack.mStackId == FREEFORM_WORKSPACE_STACK_ID) {
-                    // For freeform stack we don't adjust the size of the tasks to match that
-                    // of the stack, but we do try to make sure the tasks are still contained
-                    // with the bounds of the stack.
-                    tempRect2.set(task.mBounds);
-                    fitWithinBounds(tempRect2, bounds);
-                    task.updateOverrideConfiguration(tempRect2);
-                } else {
-                    task.updateOverrideConfiguration(taskBounds, insetBounds);
-                }
-            }
-
-            mTmpConfigs.put(task.taskId, task.mOverrideConfig);
-            mTmpBounds.put(task.taskId, task.mBounds);
-            if (tempTaskInsetBounds != null) {
-                mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
-            }
-        }
-
-        // We might trigger a configuration change. Save the current task bounds for freezing.
-        mWindowManager.prepareFreezingTaskBounds(stack.mStackId);
-        stack.mFullscreen = mWindowManager.resizeStack(stack.mStackId, bounds, mTmpConfigs,
-                mTmpBounds, mTmpInsetBounds);
-        stack.setBounds(bounds);
+        stack.updateOverrideConfiguration(bounds, tempTaskBounds, tempTaskInsetBounds);
     }
 
     void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
@@ -4371,45 +4323,6 @@
         }
     }
 
-    /**
-     * Adjust bounds to stay within stack bounds.
-     *
-     * Since bounds might be outside of stack bounds, this method tries to move the bounds in a way
-     * that keep them unchanged, but be contained within the stack bounds.
-     *
-     * @param bounds Bounds to be adjusted.
-     * @param stackBounds Bounds within which the other bounds should remain.
-     */
-    private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
-        if (stackBounds == null || stackBounds.contains(bounds)) {
-            return;
-        }
-
-        if (bounds.left < stackBounds.left || bounds.right > stackBounds.right) {
-            final int maxRight = stackBounds.right
-                    - (stackBounds.width() / FIT_WITHIN_BOUNDS_DIVIDER);
-            int horizontalDiff = stackBounds.left - bounds.left;
-            if ((horizontalDiff < 0 && bounds.left >= maxRight)
-                    || (bounds.left + horizontalDiff >= maxRight)) {
-                horizontalDiff = maxRight - bounds.left;
-            }
-            bounds.left += horizontalDiff;
-            bounds.right += horizontalDiff;
-        }
-
-        if (bounds.top < stackBounds.top || bounds.bottom > stackBounds.bottom) {
-            final int maxBottom = stackBounds.bottom
-                    - (stackBounds.height() / FIT_WITHIN_BOUNDS_DIVIDER);
-            int verticalDiff = stackBounds.top - bounds.top;
-            if ((verticalDiff < 0 && bounds.top >= maxBottom)
-                    || (bounds.top + verticalDiff >= maxBottom)) {
-                verticalDiff = maxBottom - bounds.top;
-            }
-            bounds.top += verticalDiff;
-            bounds.bottom += verticalDiff;
-        }
-    }
-
     ActivityStack findStackBehind(ActivityStack stack) {
         // TODO(multi-display): We are only looking for stacks on the default display.
         final ActivityDisplay display = mActivityDisplays.get(Display.DEFAULT_DISPLAY);
@@ -4525,6 +4438,8 @@
      * entry will be the focused activity.
      */
     public List<IBinder> getTopVisibleActivities() {
+        // TODO(multi-display): Get rid of DEFAULT_DISPLAY here. Used in
+        // VoiceInteractionManagerServiceImpl#showSessionLocked.
         final ActivityDisplay display = mActivityDisplays.get(Display.DEFAULT_DISPLAY);
         if (display == null) {
             return Collections.EMPTY_LIST;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 475b155..2b00f86 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -220,6 +220,7 @@
     void applyDisplaySize(WindowManagerService wm) {
         if (!mHaveDisplaySize) {
             Point p = new Point();
+            // TODO(multi-display): Compute based on sum of all connected displays' resolutions.
             wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p);
             if (p.x != 0 && p.y != 0) {
                 updateOomLevels(p.x, p.y, true);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 013d61b..6cc4d73 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1580,7 +1580,6 @@
         extracted.smallestScreenWidthDp = config.smallestScreenWidthDp;
         extracted.orientation = config.orientation;
         extracted.screenLayout = config.screenLayout;
-        extracted.fontScale = config.fontScale;
         return extracted;
     }
 
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index bfa02bb..f9aa66b 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -76,6 +76,7 @@
 
     final boolean voiceInteraction;
 
+    // TODO: Use getParent instead?
     Task mTask;
     /** @see WindowContainer#fillsParent() */
     private boolean mFillsParent;
@@ -324,6 +325,8 @@
                 // The token is not closing nor opening, so even if there is an animation set, that
                 // doesn't mean that it goes through the normal app transition cycle so we have
                 // to inform the docked controller about visibility change.
+                // TODO(multi-display): notify docked divider on all displays where visibility was
+                // affected.
                 mService.getDefaultDisplayContentLocked().getDockedDividerController()
                         .notifyAppVisibilityChanged();
             }
@@ -373,7 +376,8 @@
         mIsExiting = false;
         removeAllWindows();
         if (mTask != null) {
-            mTask.detachChild(this);
+            mTask.mStack.mExitingAppTokens.remove(this);
+            mTask.removeChild(this);
         }
     }
 
@@ -734,7 +738,8 @@
         } else {
             mFrozenMergedConfig.offer(new Configuration(mTask.mPreparedFrozenMergedConfig));
         }
-        mTask.mPreparedFrozenMergedConfig.setToDefaults();
+        // Calling unset() to make it equal to Configuration.EMPTY.
+        mTask.mPreparedFrozenMergedConfig.unset();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index 052b2f5..b99dda1 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -95,7 +95,7 @@
     }
 
     private void constructSurface(WindowManagerService service) {
-        SurfaceControl.openTransaction();
+        service.openSurfaceTransaction();
         try {
             if (DEBUG_SURFACE_TRACE) {
                 mDimSurface = new WindowSurfaceController.SurfaceTrace(service.mFxSession,
@@ -116,7 +116,7 @@
         } catch (Exception e) {
             Slog.e(TAG_WM, "Exception creating Dim surface", e);
         } finally {
-            SurfaceControl.closeTransaction();
+            service.closeSurfaceTransaction();
         }
     }
 
@@ -227,12 +227,12 @@
         mBounds.set(bounds);
         if (isDimming() && !mLastBounds.equals(bounds)) {
             try {
-                SurfaceControl.openTransaction();
+                mService.openSurfaceTransaction();
                 adjustBounds();
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Failure setting size", e);
             } finally {
-                SurfaceControl.closeTransaction();
+                mService.closeSurfaceTransaction();
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ee73dee..e1aa98d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -55,6 +55,7 @@
 import android.view.animation.Animation;
 import com.android.internal.util.FastPrintWriter;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -371,7 +372,7 @@
         mStacks.add(addIndex, stack);
     }
 
-    // TODO: Don't forget to switch to WC.detachChild
+    // TODO: Don't forget to switch to WC.removeChild
     void detachChild(TaskStack stack) {
         detachStack(stack);
         if (stack.detachFromDisplay()) {
@@ -772,8 +773,7 @@
                 final TaskStack appStack = wtoken.mTask.mStack;
 
                 // TODO: Use WindowContainer.compareTo() once everything is using WindowContainer
-                if ((focusedAppStack == appStack
-                        && appStack.isFirstGreaterThanSecond(focusedApp, wtoken))
+                if ((focusedAppStack == appStack && focusedApp.compareTo(wtoken) > 0)
                         || mStacks.indexOf(focusedAppStack) > mStacks.indexOf(appStack)) {
                     // App stack below focused app stack. No focus for you!!!
                     if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
@@ -1179,4 +1179,18 @@
             taskForResize = null;
         }
     }
+
+    void enableSurfaceTrace(FileDescriptor fd) {
+        for (int i = mWindows.size()  - 1; i >= 0; i--) {
+            final WindowState win = mWindows.get(i);
+            win.mWinAnimator.enableSurfaceTrace(fd);
+        }
+    }
+
+    void disableSurfaceTrace() {
+        for (int i = mWindows.size()  - 1; i >= 0; i--) {
+            final WindowState win = mWindows.get(i);
+            win.mWinAnimator.disableSurfaceTrace();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 573eca1..ff676e9 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -203,7 +203,7 @@
                     ? mDisplayContent.mBaseDisplayWidth
                     : mDisplayContent.mBaseDisplayHeight;
             mService.mPolicy.getStableInsetsLw(rotation, dw, dh, mTmpRect);
-            config.setToDefaults();
+            config.unset();
             config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
             config.screenWidthDp = (int)
                     (mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, baseConfig.uiMode) /
@@ -465,7 +465,7 @@
     }
 
     void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         final TaskStack stack = mDisplayContent.mService.mStackIdToStack.get(targetStackId);
         final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
@@ -482,7 +482,7 @@
         if (!visibleAndValid) {
             mDimLayer.hide();
         }
-        SurfaceControl.closeTransaction();
+        mService.closeSurfaceTransaction();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index d0167bb..588bb76 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -432,14 +432,14 @@
         // Move the surface to the given touch
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
                 TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw");
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         try {
             mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
             if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  DRAG "
                     + mSurfaceControl + ": pos=(" +
                     (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
         } finally {
-            SurfaceControl.closeTransaction();
+            mService.closeSurfaceTransaction();
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
                     TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw");
         }
diff --git a/services/core/java/com/android/server/wm/RemoteEventTrace.java b/services/core/java/com/android/server/wm/RemoteEventTrace.java
new file mode 100644
index 0000000..9f65ba3
--- /dev/null
+++ b/services/core/java/com/android/server/wm/RemoteEventTrace.java
@@ -0,0 +1,69 @@
+/*
+ * 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 java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.DataOutputStream;
+
+import android.util.Slog;
+import android.os.Debug;
+
+// Counterpart to remote surface trace for events which are not tied to a particular surface.
+class RemoteEventTrace {
+    private static final String TAG = "RemoteEventTrace";
+
+    // We terminate all our messages with a recognizable marker, to avoid issues
+    // with partial reads (which ADB makes impossible to avoid).
+    static final byte[] sigil = {(byte)0xfc, (byte)0xfc, (byte)0xfc, (byte)0xfc};
+
+    private final WindowManagerService mService;
+    private final DataOutputStream mOut;
+
+    RemoteEventTrace(WindowManagerService service, FileDescriptor fd) {
+        mService = service;
+        mOut = new DataOutputStream(new FileOutputStream(fd, false));
+    }
+
+    void openSurfaceTransaction() {
+        try {
+            mOut.writeUTF("OpenTransaction");
+            writeSigil();
+        } catch (Exception e) {
+            logException(e);
+            mService.disableSurfaceTrace();
+        }
+    }
+
+    void closeSurfaceTransaction() {
+        try {
+            mOut.writeUTF("CloseTransaction");
+            writeSigil();
+        } catch (Exception e) {
+            logException(e);
+            mService.disableSurfaceTrace();
+        }
+    }
+
+    private void writeSigil() throws Exception {
+        mOut.write(RemoteEventTrace.sigil, 0, 4);
+    }
+
+    static void logException(Exception e) {
+        Slog.i(TAG, "Exception writing to SurfaceTrace (client vanished?): " + e.toString());
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
new file mode 100644
index 0000000..0508fdf
--- /dev/null
+++ b/services/core/java/com/android/server/wm/RemoteSurfaceTrace.java
@@ -0,0 +1,166 @@
+/*
+ * 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 android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.util.Slog;
+import android.view.SurfaceControl;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.DataOutputStream;
+
+// A surface control subclass which logs events to a FD in binary format.
+// This can be used in our CTS tests to enable a pattern similar to mocking
+// the surface control.
+//
+// See cts/hostsidetests/../../SurfaceTraceReceiver.java for parsing side.
+class RemoteSurfaceTrace extends SurfaceControl {
+    static final String TAG = "RemoteSurfaceTrace";
+
+    final FileDescriptor mWriteFd;
+    final DataOutputStream mOut;
+
+    final WindowManagerService mService;
+    final WindowState mWindow;
+
+    RemoteSurfaceTrace(FileDescriptor fd, SurfaceControl wrapped, WindowState window) {
+        super(wrapped);
+
+        mWriteFd = fd;
+        mOut = new DataOutputStream(new FileOutputStream(fd, false));
+
+        mWindow = window;
+        mService = mWindow.mService;
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        writeFloatEvent("Alpha", alpha);
+        super.setAlpha(alpha);
+    }
+
+    @Override
+    public void setLayer(int zorder) {
+        writeIntEvent("Layer", zorder);
+        super.setLayer(zorder);
+    }
+
+    @Override
+    public void setPosition(float x, float y) {
+        writeFloatEvent("Position", x, y);
+        super.setPosition(x, y);
+    }
+
+    @Override
+    public void setGeometryAppliesWithResize() {
+        writeEvent("GeometryAppliesWithResize");
+        super.setGeometryAppliesWithResize();
+    }
+
+    @Override
+    public void setSize(int w, int h) {
+        writeIntEvent("Size", w, h);
+        super.setSize(w, h);
+    }
+
+    @Override
+    public void setWindowCrop(Rect crop) {
+        writeRectEvent("Crop", crop);
+        super.setWindowCrop(crop);
+    }
+
+    @Override
+    public void setFinalCrop(Rect crop) {
+        writeRectEvent("FinalCrop", crop);
+        super.setFinalCrop(crop);
+    }
+
+    @Override
+    public void setLayerStack(int layerStack) {
+        writeIntEvent("LayerStack", layerStack);
+        super.setLayerStack(layerStack);
+    }
+
+    @Override
+    public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+        writeFloatEvent("Matrix", dsdx, dtdx, dsdy, dtdy);
+        super.setMatrix(dsdx, dtdx, dsdy, dtdy);
+    }
+
+    @Override
+    public void hide() {
+        writeEvent("Hide");
+        super.hide();
+    }
+
+    @Override
+    public void show() {
+        writeEvent("Show");
+        super.show();
+    }
+
+    private void writeEvent(String tag) {
+        try {
+            mOut.writeUTF(tag);
+            mOut.writeUTF(mWindow.getWindowTag().toString());
+            writeSigil();
+        } catch (Exception e) {
+            RemoteEventTrace.logException(e);
+            mService.disableSurfaceTrace();
+        }
+    }
+
+    private void writeIntEvent(String tag, int... values) {
+        try {
+            mOut.writeUTF(tag);
+            mOut.writeUTF(mWindow.getWindowTag().toString());
+            for (int value: values) {
+                mOut.writeInt(value);
+            }
+            writeSigil();
+        } catch (Exception e) {
+            RemoteEventTrace.logException(e);
+            mService.disableSurfaceTrace();
+        }
+    }
+
+    private void writeFloatEvent(String tag, float... values) {
+        try {
+            mOut.writeUTF(tag);
+            mOut.writeUTF(mWindow.getWindowTag().toString());
+            for (float value: values) {
+                mOut.writeFloat(value);
+            }
+            writeSigil();
+        } catch (Exception e) {
+            RemoteEventTrace.logException(e);
+            mService.disableSurfaceTrace();
+        }
+    }
+
+    private void writeRectEvent(String tag, Rect value) {
+        writeFloatEvent(tag, value.left, value.top, value.right, value.bottom);
+    }
+
+    private void writeSigil() throws Exception {
+        mOut.write(RemoteEventTrace.sigil, 0, 4);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index c118a21..83337ca 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -147,6 +147,8 @@
     private boolean mMoreStartFrame;
     long mHalfwayPoint;
 
+    private final WindowManagerService mService;
+
     public void printTo(String prefix, PrintWriter pw) {
         pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl);
                 pw.print(" mWidth="); pw.print(mWidth);
@@ -216,7 +218,8 @@
 
     public ScreenRotationAnimation(Context context, DisplayContent displayContent,
             SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation,
-            boolean isSecure) {
+            boolean isSecure, WindowManagerService service) {
+        mService = service;
         mContext = context;
         mDisplayContent = displayContent;
         displayContent.getLogicalDisplayRect(mOriginalDisplayRect);
@@ -253,7 +256,7 @@
         if (!inTransaction) {
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                     ">>> OPEN TRANSACTION ScreenRotationAnimation");
-            SurfaceControl.openTransaction();
+            mService.openSurfaceTransaction();
         }
 
         try {
@@ -295,7 +298,7 @@
             setRotationInTransaction(originalRotation);
         } finally {
             if (!inTransaction) {
-                SurfaceControl.closeTransaction();
+                mService.closeSurfaceTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation");
             }
@@ -546,7 +549,7 @@
             if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                     TAG_WM,
                     ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
-            SurfaceControl.openTransaction();
+            mService.openSurfaceTransaction();
 
             // Compute the transformation matrix that must be applied
             // the the black frame to make it stay in the initial position
@@ -566,7 +569,7 @@
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
-                SurfaceControl.closeTransaction();
+                mService.closeSurfaceTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                         TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -577,7 +580,7 @@
             if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                     TAG_WM,
                     ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
-            SurfaceControl.openTransaction();
+            mService.openSurfaceTransaction();
             try {
                 // Compute the transformation matrix that must be applied
                 // the the black frame to make it stay in the initial position
@@ -606,7 +609,7 @@
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
-                SurfaceControl.closeTransaction();
+                mService.closeSurfaceTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                         TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
@@ -617,7 +620,7 @@
             if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                     TAG_WM,
                     ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
-            SurfaceControl.openTransaction();
+            mService.openSurfaceTransaction();
 
             try {
                 Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
@@ -628,7 +631,7 @@
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
-                SurfaceControl.closeTransaction();
+                mService.closeSurfaceTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
                         TAG_WM,
                         "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index e9ce0ea..edd3a3b 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -346,7 +346,7 @@
             final SurfaceControl surfaceControl = mService.mDragState.mSurfaceControl;
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
                     TAG_WM, ">>> OPEN TRANSACTION performDrag");
-            SurfaceControl.openTransaction();
+            mService.openSurfaceTransaction();
             try {
                 surfaceControl.setPosition(touchX - thumbCenterX,
                         touchY - thumbCenterY);
@@ -354,7 +354,7 @@
                 surfaceControl.setLayerStack(display.getLayerStack());
                 surfaceControl.show();
             } finally {
-                SurfaceControl.closeTransaction();
+                mService.closeSurfaceTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
                         TAG_WM, "<<< CLOSE TRANSACTION performDrag");
             }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f730fda..982f12e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -21,9 +21,6 @@
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_CROP_WINDOWS;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 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;
@@ -37,14 +34,13 @@
 import android.util.Slog;
 import android.view.DisplayInfo;
 import android.view.Surface;
-import android.view.animation.Animation;
 
 import android.view.SurfaceControl;
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
 
-class Task implements DimLayer.DimLayerUser {
+class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
     static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
     // Return value from {@link setBounds} indicating no change was made to the Task bounds.
     static final int BOUNDS_CHANGE_NONE = 0;
@@ -53,8 +49,8 @@
     // Return value from {@link setBounds} indicating the size of the Task bounds changed.
     static final int BOUNDS_CHANGE_SIZE = 1 << 1;
 
+    // TODO: Track parent marks like this in WindowContainer.
     TaskStack mStack;
-    private final AppTokenList mAppTokens = new AppTokenList();
     final int mTaskId;
     final int mUserId;
     boolean mDeferRemoval = false;
@@ -112,18 +108,22 @@
     }
 
     void addAppToken(int addPos, AppWindowToken wtoken, int resizeMode, boolean homeTask) {
-        final int lastPos = mAppTokens.size();
+        final int lastPos = mChildren.size();
         if (addPos >= lastPos) {
             addPos = lastPos;
         } else {
             for (int pos = 0; pos < lastPos && pos < addPos; ++pos) {
-                if (mAppTokens.get(pos).removed) {
+                if (mChildren.get(pos).removed) {
                     // addPos assumes removed tokens are actually gone.
                     ++addPos;
                 }
             }
         }
-        mAppTokens.add(addPos, wtoken);
+
+        if (wtoken.mParent != null) {
+            wtoken.mParent.removeChild(wtoken);
+        }
+        addChild(wtoken, addPos);
         wtoken.mTask = this;
         mDeferRemoval = false;
         mResizeMode = resizeMode;
@@ -131,15 +131,16 @@
     }
 
     private boolean hasWindowsAlive() {
-        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
-            if (mAppTokens.get(i).hasWindowsAlive()) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            if (mChildren.get(i).hasWindowsAlive()) {
                 return true;
             }
         }
         return false;
     }
 
-    void removeLocked() {
+    @Override
+    void removeIfPossible() {
         if (hasWindowsAlive() && mStack.isAnimating()) {
             if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
             mDeferRemoval = true;
@@ -152,10 +153,11 @@
         if (content != null) {
             content.mDimLayerController.removeDimLayerUser(this);
         }
-        mStack.removeTask(this);
+        mParent.removeChild(this);
         mService.mTaskIdToTask.delete(mTaskId);
     }
 
+    // Change to use reparenting in WC when TaskStack is switched to use WC.
     void moveTaskToStack(TaskStack stack, boolean toTop) {
         if (stack == mStack) {
             return;
@@ -163,9 +165,7 @@
         if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: removing taskId=" + mTaskId
                 + " from stack=" + mStack);
         EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
-        if (mStack != null) {
-            mStack.removeTask(this);
-        }
+        mParent.removeChild(this);
         stack.addTask(this, toTop);
     }
 
@@ -174,53 +174,37 @@
             if (DEBUG_STACK) Slog.i(TAG, "positionTaskInStack: removing taskId=" + mTaskId
                     + " from stack=" + mStack);
             EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "moveTask");
-            mStack.removeTask(this);
+            mStack.removeChild(this);
         }
         stack.positionTask(this, position, showForAllUsers());
         resizeLocked(bounds, config, false /* force */);
 
-        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
-            mAppTokens.get(activityNdx).notifyMovedInStack();
+        for (int activityNdx = mChildren.size() - 1; activityNdx >= 0; --activityNdx) {
+            mChildren.get(activityNdx).notifyMovedInStack();
         }
     }
 
-    boolean checkCompleteDeferredRemoval() {
-        boolean stillDeferringRemoval = false;
-
-        for (int tokenNdx = mAppTokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
-            final AppWindowToken token = mAppTokens.get(tokenNdx);
-            stillDeferringRemoval |= token.checkCompleteDeferredRemoval();
+    @Override
+    void removeChild(AppWindowToken token) {
+        if (!mChildren.contains(token)) {
+            Slog.e(TAG, "removeChild: token=" + this + " not found.");
+            return;
         }
 
-        return stillDeferringRemoval;
-    }
+        super.removeChild(token);
 
-    // TODO: Don't forget to switch to WC.detachChild
-    void detachChild(AppWindowToken wtoken) {
-        if (!removeAppToken(wtoken)) {
-            Slog.e(TAG, "detachChild: token=" + this + " not found.");
-        }
-        mStack.mExitingAppTokens.remove(wtoken);
-    }
-
-    boolean removeAppToken(AppWindowToken wtoken) {
-        boolean removed = mAppTokens.remove(wtoken);
-        if (mAppTokens.size() == 0) {
+        if (mChildren.isEmpty()) {
             EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeAppToken: last token");
             if (mDeferRemoval) {
-                removeLocked();
+                removeIfPossible();
             }
         }
-        wtoken.mTask = null;
-        /* Leave mTaskId for now, it might be useful for debug
-        wtoken.mTaskId = -1;
-         */
-        return removed;
+        token.mTask = null;
     }
 
     void setSendingToBottom(boolean toBottom) {
-        for (int appTokenNdx = 0; appTokenNdx < mAppTokens.size(); appTokenNdx++) {
-            mAppTokens.get(appTokenNdx).sendingToBottom = toBottom;
+        for (int appTokenNdx = 0; appTokenNdx < mChildren.size(); appTokenNdx++) {
+            mChildren.get(appTokenNdx).sendingToBottom = toBottom;
         }
     }
 
@@ -370,13 +354,10 @@
     /** Return true if the current bound can get outputted to the rest of the system as-is. */
     private boolean useCurrentBounds() {
         final DisplayContent displayContent = mStack.getDisplayContent();
-        if (mFullscreen
+        return mFullscreen
                 || !StackId.isTaskResizeableByDockedStack(mStack.mStackId)
                 || displayContent == null
-                || displayContent.getDockedStackVisibleForUserLocked() != null) {
-            return true;
-        }
-        return false;
+                || displayContent.getDockedStackVisibleForUserLocked() != null;
     }
 
     /** Original bounds of the task if applicable, otherwise fullscreen rect. */
@@ -407,8 +388,8 @@
      */
     boolean getMaxVisibleBounds(Rect out) {
         boolean foundTop = false;
-        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
-            final AppWindowToken token = mAppTokens.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = mChildren.get(i);
             // skip hidden (or about to hide) apps
             if (token.mIsExiting || token.clientHidden || token.hiddenRequested) {
                 continue;
@@ -444,8 +425,8 @@
         final DisplayContent displayContent = mStack.getDisplayContent();
         // It doesn't matter if we in particular are part of the resize, since we couldn't have
         // a DimLayer anyway if we weren't visible.
-        final boolean dockedResizing = displayContent != null ?
-                displayContent.mDividerControllerLocked.isResizing() : false;
+        final boolean dockedResizing = displayContent != null
+                && displayContent.mDividerControllerLocked.isResizing();
         if (useCurrentBounds()) {
             if (inFreeformWorkspace() && getMaxVisibleBounds(out)) {
                 return;
@@ -471,10 +452,11 @@
             return;
         }
 
-        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
-        // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
-        // system.
-        displayContent.getLogicalDisplayRect(out);
+        // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
+        // not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
+        if (displayContent != null) {
+            displayContent.getLogicalDisplayRect(out);
+        }
     }
 
     void setDragResizing(boolean dragResizing, int dragResizeMode) {
@@ -489,12 +471,6 @@
         }
     }
 
-    private void resetDragResizingChangeReported() {
-        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
-            mAppTokens.get(activityNdx).resetDragResizingChangeReported();
-        }
-    }
-
     boolean isDragResizing() {
         return mDragResizing;
     }
@@ -503,24 +479,6 @@
         return mDragResizeMode;
     }
 
-    /**
-     * Adds all of the tasks windows to {@link WindowManagerService#mWaitingForDrawn} if drag
-     * resizing state of the window has been changed.
-     */
-    void setWaitingForDrawnIfResizingChanged() {
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            mAppTokens.get(i).setWaitingForDrawnIfResizingChanged();
-        }
-    }
-
-    boolean detachFromDisplay() {
-        boolean didSomething = false;
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            didSomething |= mAppTokens.get(i).detachFromDisplay();
-        }
-        return didSomething;
-    }
-
     void updateDisplayInfo(final DisplayContent displayContent) {
         if (displayContent == null) {
             return;
@@ -556,59 +514,23 @@
         }
     }
 
-    private void onResize() {
-        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
-            mAppTokens.get(activityNdx).onResize();
-        }
-    }
-
-    private void onMovedByResize() {
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            mAppTokens.get(i).onMovedByResize();
-        }
-    }
-
-    /**
-     * Cancels any running app transitions associated with the task.
-     */
+    /** Cancels any running app transitions associated with the task. */
     void cancelTaskWindowTransition() {
-        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
-            mAppTokens.get(activityNdx).mAppAnimator.clearAnimation();
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).mAppAnimator.clearAnimation();
         }
     }
 
-    /**
-     * Cancels any running thumbnail transitions associated with the task.
-     */
+    /** Cancels any running thumbnail transitions associated with the task. */
     void cancelTaskThumbnailTransition() {
-        for (int activityNdx = mAppTokens.size() - 1; activityNdx >= 0; --activityNdx) {
-            mAppTokens.get(activityNdx).mAppAnimator.clearThumbnail();
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            mChildren.get(i).mAppAnimator.clearThumbnail();
         }
     }
 
     boolean showForAllUsers() {
-        final int tokensCount = mAppTokens.size();
-        return (tokensCount != 0) && mAppTokens.get(tokensCount - 1).showForAllUsers;
-    }
-
-    boolean hasContentToDisplay() {
-        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
-            final AppWindowToken appToken = mAppTokens.get(i);
-            if (appToken.hasContentToDisplay()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    boolean isVisible() {
-        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
-            final AppWindowToken appToken = mAppTokens.get(i);
-            if (appToken.isVisible()) {
-                return true;
-            }
-        }
-        return false;
+        final int tokensCount = mChildren.size();
+        return (tokensCount != 0) && mChildren.get(tokensCount - 1).showForAllUsers;
     }
 
     boolean inHomeStack() {
@@ -633,8 +555,8 @@
     }
 
     AppWindowToken getTopVisibleAppToken() {
-        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
-            final AppWindowToken token = mAppTokens.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final AppWindowToken token = mChildren.get(i);
             // skip hidden (or about to hide) apps
             if (!token.mIsExiting && !token.clientHidden && !token.hiddenRequested) {
                 return token;
@@ -663,82 +585,21 @@
         return mStack.getDisplayContent().getDisplayInfo();
     }
 
-    /**
-     * See {@link WindowManagerService#overridePlayingAppAnimationsLw}
-     */
-    void overridePlayingAppAnimations(Animation a) {
-        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
-            mAppTokens.get(i).overridePlayingAppAnimations(a);
-        }
-    }
-
     void forceWindowsScaleable(boolean force) {
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         try {
-            for (int i = mAppTokens.size() - 1; i >= 0; i--) {
-                mAppTokens.get(i).forceWindowsScaleableInTransaction(force);
+            for (int i = mChildren.size() - 1; i >= 0; i--) {
+                mChildren.get(i).forceWindowsScaleableInTransaction(force);
             }
         } finally {
-            SurfaceControl.closeTransaction();
+            mService.closeSurfaceTransaction();
         }
     }
 
-    boolean isAnimating() {
-        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
-            final AppWindowToken aToken = mAppTokens.get(i);
-            if (aToken.isAnimating()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void checkAppWindowsReadyToShow(int displayId) {
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            final AppWindowToken aToken = mAppTokens.get(i);
-            aToken.checkAppWindowsReadyToShow(displayId);
-        }
-    }
-
-    void updateAllDrawn(int displayId) {
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            final AppWindowToken aToken = mAppTokens.get(i);
-            aToken.updateAllDrawn(displayId);
-        }
-    }
-
-    void stepAppWindowsAnimation(long currentTime, int displayId) {
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            final AppWindowToken aToken = mAppTokens.get(i);
-            aToken.stepAppWindowsAnimation(currentTime, displayId);
-        }
-    }
-
-    void onAppTransitionDone() {
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            final AppWindowToken token = mAppTokens.get(i);
-            token.onAppTransitionDone();
-        }
-    }
-
-    // TODO: Use WindowContainer.compareTo() once everything is using WindowContainer
-    boolean isFirstGreaterThanSecond(AppWindowToken first, AppWindowToken second) {
-        return mAppTokens.indexOf(first) > mAppTokens.indexOf(second);
-    }
-
-    int rebuildWindowList(DisplayContent dc, int addIndex) {
-        final int count = mAppTokens.size();
-        for (int i = 0; i < count; i++) {
-            final AppWindowToken token = mAppTokens.get(i);
-            addIndex = token.rebuildWindowList(dc, addIndex);
-        }
-        return addIndex;
-    }
-
     void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
             DisplayContent.GetWindowOnDisplaySearchResult result) {
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            final AppWindowToken current = mAppTokens.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final AppWindowToken current = mChildren.get(i);
             if (current == token) {
                 // We have reach the token we are interested in. End search.
                 result.reachedToken = true;
@@ -756,8 +617,8 @@
 
     void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
             DisplayContent.GetWindowOnDisplaySearchResult result) {
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            final AppWindowToken current = mAppTokens.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final AppWindowToken current = mChildren.get(i);
             if (!result.reachedToken) {
                 if (current == token) {
                     // We have reached the token we are interested in. Get whichever window occurs
@@ -775,36 +636,14 @@
         }
     }
 
+    @Override
     boolean fillsParent() {
         return mFullscreen || !StackId.isTaskResizeAllowed(mStack.mStackId);
     }
 
-    // TODO: Remove once switched to use WindowContainer
-    int getOrientation() {
-        int candidate = SCREEN_ORIENTATION_UNSET;
-
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            final AppWindowToken token = mAppTokens.get(i);
-            final int orientation = token.getOrientation();
-
-            if (orientation == SCREEN_ORIENTATION_BEHIND) {
-                candidate = orientation;
-                continue;
-            }
-
-            if (orientation != SCREEN_ORIENTATION_UNSET) {
-                if (token.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
-                    return orientation;
-                }
-            }
-        }
-
-        return candidate;
-    }
-
     @Override
     public String toString() {
-        return "{taskId=" + mTaskId + " appTokens=" + mAppTokens + " mdr=" + mDeferRemoval + "}";
+        return "{taskId=" + mTaskId + " appTokens=" + mChildren + " mdr=" + mDeferRemoval + "}";
     }
 
     String getName() {
@@ -816,15 +655,6 @@
         return "Task=" + mTaskId;
     }
 
-    void dumpChildrenNames(PrintWriter pw, String prefix) {
-        final String childPrefix = prefix + prefix;
-        for (int i = mAppTokens.size() - 1; i >= 0; --i) {
-            final AppWindowToken token = mAppTokens.get(i);
-            pw.println("#" + i + " " + getName());
-            token.dumpChildrenNames(pw, childPrefix);
-        }
-    }
-
     public void dump(String prefix, PrintWriter pw) {
         final String doublePrefix = prefix + "  ";
 
@@ -832,13 +662,13 @@
         pw.println(doublePrefix + "mFullscreen=" + mFullscreen);
         pw.println(doublePrefix + "mBounds=" + mBounds.toShortString());
         pw.println(doublePrefix + "mdr=" + mDeferRemoval);
-        pw.println(doublePrefix + "appTokens=" + mAppTokens);
+        pw.println(doublePrefix + "appTokens=" + mChildren);
         pw.println(doublePrefix + "mTempInsetBounds=" + mTempInsetBounds.toShortString());
 
         final String triplePrefix = doublePrefix + "  ";
 
-        for (int i = mAppTokens.size() - 1; i >= 0; i--) {
-            final AppWindowToken wtoken = mAppTokens.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final AppWindowToken wtoken = mChildren.get(i);
             pw.println(triplePrefix + "Activity #" + i + " " + wtoken);
             wtoken.dump(pw, triplePrefix);
         }
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index c097128..21db840 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -459,13 +459,13 @@
         mCurrentDimSide = dimSide;
 
         if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         if (mCurrentDimSide == CTRL_NONE) {
             mDimLayer.hide();
         } else {
             showDimLayer();
         }
-        SurfaceControl.closeTransaction();
+        mService.closeSurfaceTransaction();
     }
 
     /**
@@ -477,7 +477,7 @@
      */
     private int getDimSide(int x) {
         if (mTask.mStack.mStackId != FREEFORM_WORKSPACE_STACK_ID
-                || !mTask.mStack.isFullscreen()
+                || !mTask.mStack.fillsParent()
                 || mService.mGlobalConfiguration.orientation != ORIENTATION_LANDSCAPE) {
             return CTRL_NONE;
         }
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index dc54b3fd..b24a06a 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -21,9 +21,7 @@
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.DOCKED_BOTTOM;
@@ -48,7 +46,6 @@
 import android.util.SparseArray;
 import android.view.DisplayInfo;
 import android.view.Surface;
-import android.view.animation.Animation;
 
 import android.view.WindowManagerPolicy;
 import com.android.internal.policy.DividerSnapAlgorithm;
@@ -57,9 +54,8 @@
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 
-public class TaskStack implements DimLayer.DimLayerUser,
+public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser,
         BoundsAnimationController.AnimateBoundsUser {
     /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
      * restrict IME adjustment so that a min portion of top stack remains visible.*/
@@ -75,12 +71,9 @@
     private final WindowManagerService mService;
 
     /** The display this stack sits under. */
+    // TODO: Track parent marks like this in WindowContainer.
     private DisplayContent mDisplayContent;
 
-    /** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match
-     * mTaskHistory in the ActivityStack with the same mStackId */
-    private final ArrayList<Task> mTasks = new ArrayList<>();
-
     /** For comparison with DisplayContent bounds. */
     private Rect mTmpRect = new Rect();
     private Rect mTmpRect2 = new Rect();
@@ -98,7 +91,7 @@
     private final Rect mFullyAdjustedImeBounds = new Rect();
 
     /** Whether mBounds is fullscreen */
-    private boolean mFullscreen = true;
+    private boolean mFillsParent = true;
 
     // Device rotation as of the last time {@link #mBounds} was set.
     int mRotation;
@@ -116,7 +109,7 @@
     final AppTokenList mExitingAppTokens = new AppTokenList();
 
     /** Detach this stack from its display when animation completes. */
-    // TODO: maybe tie this to WindowContainer#detachChild some how...
+    // TODO: maybe tie this to WindowContainer#removeChild some how...
     boolean mDeferDetach;
 
     private final Rect mTmpAdjustedBounds = new Rect();
@@ -128,11 +121,9 @@
     private float mAdjustDividerAmount;
     private final int mDockedStackMinimizeThickness;
 
-    // If this is true, we are in the bounds animating mode.
-    // The task will be down or upscaled to perfectly fit the
-    // region it would have been cropped to. We may also avoid
-    // certain logic we would otherwise apply while resizing,
-    // while resizing in the bounds animating mode.
+    // If this is true, we are in the bounds animating mode. The task will be down or upscaled to
+    // perfectly fit the region it would have been cropped to. We may also avoid certain logic we
+    // would otherwise apply while resizing, while resizing in the bounds animating mode.
     private boolean mBoundsAnimating = false;
 
     // Temporary storage for the new bounds that should be used after the configuration change.
@@ -156,20 +147,20 @@
             return null;
         }
 
-        for (int i = mTasks.size() - 1; i >= 0; i--) {
-            if (mTasks.get(i).isHomeTask()) {
-                return mTasks.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            if (mChildren.get(i).isHomeTask()) {
+                return mChildren.get(i);
             }
         }
         return null;
     }
 
     boolean hasMultipleTaskWithHomeTaskNotTop() {
-        return mTasks.size() > 1 && !mTasks.get(mTasks.size() - 1).isHomeTask();
+        return mChildren.size() > 1 && !mChildren.get(mChildren.size() - 1).isHomeTask();
     }
 
     boolean topTaskIsOnTopLauncher() {
-        return mTasks.get(mTasks.size() - 1).isOnTopLauncher();
+        return mChildren.get(mChildren.size() - 1).isOnTopLauncher();
     }
 
     /**
@@ -185,8 +176,8 @@
         setBounds(stackBounds);
 
         // Update bounds of containing tasks.
-        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-            final Task task = mTasks.get(taskNdx);
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+            final Task task = mChildren.get(taskNdx);
             Configuration config = configs.get(task.mTaskId);
             if (config != null) {
                 Rect bounds = taskBounds.get(task.mTaskId);
@@ -201,8 +192,8 @@
     }
 
     void prepareFreezingTaskBounds() {
-        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-            final Task task = mTasks.get(taskNdx);
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+            final Task task = mChildren.get(taskNdx);
             task.prepareFreezingBounds();
         }
     }
@@ -243,29 +234,29 @@
     }
 
     private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
-        if (mFullscreen) {
+        if (mFillsParent) {
             return;
         }
 
         final boolean alignBottom = mAdjustedForIme && getDockSide() == DOCKED_TOP;
 
         // Update bounds of containing tasks.
-        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-            final Task task = mTasks.get(taskNdx);
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+            final Task task = mChildren.get(taskNdx);
             task.alignToAdjustedBounds(adjustedBounds, tempInsetBounds, alignBottom);
         }
     }
 
     private boolean setBounds(Rect bounds) {
-        boolean oldFullscreen = mFullscreen;
+        boolean oldFullscreen = mFillsParent;
         int rotation = Surface.ROTATION_0;
         int density = DENSITY_DPI_UNDEFINED;
         if (mDisplayContent != null) {
             mDisplayContent.getLogicalDisplayRect(mTmpRect);
             rotation = mDisplayContent.getDisplayInfo().rotation;
             density = mDisplayContent.getDisplayInfo().logicalDensityDpi;
-            mFullscreen = bounds == null;
-            if (mFullscreen) {
+            mFillsParent = bounds == null;
+            if (mFillsParent) {
                 bounds = mTmpRect;
             }
         }
@@ -274,7 +265,7 @@
             // Can't set to fullscreen if we don't have a display to get bounds from...
             return false;
         }
-        if (mBounds.equals(bounds) && oldFullscreen == mFullscreen && mRotation == rotation) {
+        if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
             return false;
         }
 
@@ -302,7 +293,7 @@
 
     /** Return true if the current bound can get outputted to the rest of the system as-is. */
     private boolean useCurrentBounds() {
-        if (mFullscreen
+        if (mFillsParent
                 || !StackId.isResizeableByDockedStack(mStackId)
                 || mDisplayContent == null
                 || mDisplayContent.getDockedStackLocked() != null) {
@@ -342,13 +333,13 @@
             return;
         }
 
-        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-            mTasks.get(taskNdx).updateDisplayInfo(mDisplayContent);
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+            mChildren.get(taskNdx).updateDisplayInfo(mDisplayContent);
         }
         if (bounds != null) {
             setBounds(bounds);
             return;
-        } else if (mFullscreen) {
+        } else if (mFillsParent) {
             setBounds(null);
             return;
         }
@@ -381,7 +372,7 @@
             return false;
         }
 
-        if (mFullscreen) {
+        if (mFillsParent) {
             // Update stack bounds again since rotation changed since updateDisplayInfo().
             setBounds(null);
             // Return false since we don't need the client to resize.
@@ -481,16 +472,7 @@
                 dividerSize);
     }
 
-    boolean isAnimating() {
-        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-            final Task task = mTasks.get(taskNdx);
-            if (task.isAnimating()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
+    // TODO: Checkout the call points of this method and the ones below to see how they can fit in WC.
     void addTask(Task task, boolean toTop) {
         addTask(task, toTop, task.showForAllUsers());
     }
@@ -502,14 +484,17 @@
      * @param showForAllUsers Whether to show the task regardless of the current user.
      */
     void addTask(Task task, boolean toTop, boolean showForAllUsers) {
-        positionTask(task, toTop ? mTasks.size() : 0, showForAllUsers);
+        positionTask(task, toTop ? mChildren.size() : 0, showForAllUsers);
     }
 
+    // TODO: We should really have users as a window container in the hierarchy so that we don't
+    // have to do complicated things like we are doing in this method and also also the method to
+    // just be WindowContainer#addChild
     void positionTask(Task task, int position, boolean showForAllUsers) {
         final boolean canShowTask =
                 showForAllUsers || mService.isCurrentProfileLocked(task.mUserId);
-        mTasks.remove(task);
-        int stackSize = mTasks.size();
+        removeChild(task);
+        int stackSize = mChildren.size();
         int minPosition = 0;
         int maxPosition = stackSize;
 
@@ -523,11 +508,13 @@
 
         if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM,
                 "positionTask: task=" + task + " position=" + position);
-        mTasks.add(position, task);
+        addChild(task, position);
         task.mStack = this;
         task.updateDisplayInfo(mDisplayContent);
-        boolean toTop = position == mTasks.size() - 1;
+        boolean toTop = position == mChildren.size() - 1;
         if (toTop) {
+            // TODO: Have a WidnowContainer method that moves all parents of a container to the
+            // front for cases like this.
             mDisplayContent.moveStack(this, true);
         }
 
@@ -549,7 +536,7 @@
      */
     private int computeMinPosition(int minPosition, int size) {
         while (minPosition < size) {
-            final Task tmpTask = mTasks.get(minPosition);
+            final Task tmpTask = mChildren.get(minPosition);
             final boolean canShowTmpTask =
                     tmpTask.showForAllUsers()
                             || mService.isCurrentProfileLocked(tmpTask.mUserId);
@@ -568,7 +555,7 @@
      */
     private int computeMaxPosition(int maxPosition) {
         while (maxPosition > 0) {
-            final Task tmpTask = mTasks.get(maxPosition - 1);
+            final Task tmpTask = mChildren.get(maxPosition - 1);
             final boolean canShowTmpTask =
                     tmpTask.showForAllUsers()
                             || mService.isCurrentProfileLocked(tmpTask.mUserId);
@@ -580,16 +567,17 @@
         return maxPosition;
     }
 
+    // TODO: Have functionality in WC to move things to the bottom or top. Also, look at the call
+    // points for this methods to see if we need functionality to move something to the front/bottom
+    // with its parents.
     void moveTaskToTop(Task task) {
         if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "moveTaskToTop: task=" + task + " Callers="
                 + Debug.getCallers(6));
-        mTasks.remove(task);
         addTask(task, true);
     }
 
     void moveTaskToBottom(Task task) {
         if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "moveTaskToBottom: task=" + task);
-        mTasks.remove(task);
         addTask(task, false);
     }
 
@@ -598,11 +586,18 @@
      * back.
      * @param task The Task to delete.
      */
-    void removeTask(Task task) {
-        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "removeTask: task=" + task);
-        mTasks.remove(task);
+    @Override
+    void removeChild(Task task) {
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG_WM, "removeChild: task=" + task);
+        if (!mChildren.contains(task)) {
+            Slog.e(TAG_WM, "removeChild: task=" + this + " not found.");
+            return;
+        }
+
+        super.removeChild(task);
+
         if (mDisplayContent != null) {
-            if (mTasks.isEmpty()) {
+            if (mChildren.isEmpty()) {
                 mDisplayContent.moveStack(this, false);
             }
             mDisplayContent.layoutNeeded = true;
@@ -629,7 +624,7 @@
         final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
         if (mStackId == DOCKED_STACK_ID
                 || (dockedStack != null && StackId.isResizeableByDockedStack(mStackId)
-                        && !dockedStack.isFullscreen())) {
+                        && !dockedStack.fillsParent())) {
             // The existence of a docked stack affects the size of other static stack created since
             // the docked stack occupies a dedicated region on screen, but only if the dock stack is
             // not fullscreen. If it's fullscreen, it means that we are in the transition of
@@ -769,14 +764,10 @@
                 1 /*allowResizeInDockedMode*/, bounds).sendToTarget();
     }
 
+    // TODO: Should this really be removeImmidiately or removeChild?
     boolean detachFromDisplay() {
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
-
-        boolean didSomething = false;
-        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-            didSomething |= mTasks.get(taskNdx).detachFromDisplay();
-        }
-
+        boolean didSomething = super.detachFromDisplay();
         close();
         return didSomething;
     }
@@ -797,13 +788,14 @@
         }
     }
 
+    // TODO: Should each user have there own stacks?
     void switchUser() {
-        int top = mTasks.size();
+        int top = mChildren.size();
         for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
-            Task task = mTasks.get(taskNdx);
+            Task task = mChildren.get(taskNdx);
             if (mService.isCurrentProfileLocked(task.mUserId) || task.showForAllUsers()) {
-                mTasks.remove(taskNdx);
-                mTasks.add(task);
+                mChildren.remove(taskNdx);
+                mChildren.add(task);
                 --top;
             }
         }
@@ -906,8 +898,8 @@
      * to the list of to be drawn windows the service is waiting for.
      */
     void beginImeAdjustAnimation() {
-        for (int j = mTasks.size() - 1; j >= 0; j--) {
-            final Task task = mTasks.get(j);
+        for (int j = mChildren.size() - 1; j >= 0; j--) {
+            final Task task = mChildren.get(j);
             if (task.hasContentToDisplay()) {
                 task.setDragResizing(true, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
                 task.setWaitingForDrawnIfResizingChanged();
@@ -919,8 +911,8 @@
      * Resets the resizing state of all windows.
      */
     void endImeAdjustAnimation() {
-        for (int j = mTasks.size() - 1; j >= 0; j--) {
-            mTasks.get(j).setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
+        for (int j = mChildren.size() - 1; j >= 0; j--) {
+            mChildren.get(j).setDragResizing(false, DRAG_RESIZE_MODE_DOCKED_DIVIDER);
         }
     }
 
@@ -1083,22 +1075,13 @@
         return mMinimizeAmount != 0f;
     }
 
-    void dumpChildrenNames(PrintWriter pw, String prefix) {
-        final String childPrefix = prefix + prefix;
-        for (int j = mTasks.size() - 1; j >= 0; j--) {
-            final Task task = mTasks.get(j);
-            pw.println("#" + j + " " + getName());
-            task.dumpChildrenNames(pw, childPrefix);
-        }
-    }
-
     public void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "mStackId=" + mStackId);
         pw.println(prefix + "mDeferDetach=" + mDeferDetach);
-        pw.println(prefix + "mFullscreen=" + mFullscreen);
+        pw.println(prefix + "mFillsParent=" + mFillsParent);
         pw.println(prefix + "mBounds=" + mBounds.toShortString());
         if (mMinimizeAmount != 0f) {
-            pw.println(prefix + "mMinimizeAmout=" + mMinimizeAmount);
+            pw.println(prefix + "mMinimizeAmount=" + mMinimizeAmount);
         }
         if (mAdjustedForIme) {
             pw.println(prefix + "mAdjustedForIme=true");
@@ -1108,8 +1091,8 @@
         if (!mAdjustedBounds.isEmpty()) {
             pw.println(prefix + "mAdjustedBounds=" + mAdjustedBounds.toShortString());
         }
-        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; taskNdx--) {
-            mTasks.get(taskNdx).dump(prefix + "  ", pw);
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
+            mChildren.get(taskNdx).dump(prefix + "  ", pw);
         }
         if (mAnimationBackgroundSurface.isDimming()) {
             pw.println(prefix + "mWindowAnimationBackgroundSurface:");
@@ -1130,20 +1113,21 @@
 
     /** Fullscreen status of the stack without adjusting for other factors in the system like
      * visibility of docked stack.
-     * Most callers should be using {@link #isFullscreen} as it take into consideration other
+     * Most callers should be using {@link #fillsParent} as it take into consideration other
      * system factors. */
     boolean getRawFullscreen() {
-        return mFullscreen;
+        return mFillsParent;
     }
 
     @Override
     public boolean dimFullscreen() {
-        return mStackId == HOME_STACK_ID || isFullscreen();
+        return mStackId == HOME_STACK_ID || fillsParent();
     }
 
-    boolean isFullscreen() {
+    @Override
+    boolean fillsParent() {
         if (useCurrentBounds()) {
-            return mFullscreen;
+            return mFillsParent;
         }
         // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
         // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
@@ -1158,7 +1142,7 @@
 
     @Override
     public String toString() {
-        return "{stackId=" + mStackId + " tasks=" + mTasks + "}";
+        return "{stackId=" + mStackId + " tasks=" + mChildren + "}";
     }
 
     String getName() {
@@ -1222,19 +1206,12 @@
             return false;
         }
 
-        for (int i = mTasks.size() - 1; i >= 0; i--) {
-            final Task task = mTasks.get(i);
-            if (task.isVisible()) {
-                return true;
-            }
-        }
-
-        return false;
+        return super.isVisible();
     }
 
     boolean hasTaskForUser(int userId) {
-        for (int i = mTasks.size() - 1; i >= 0; i--) {
-            final Task task = mTasks.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final Task task = mChildren.get(i);
             if (task.mUserId == userId) {
                 return true;
             }
@@ -1248,8 +1225,8 @@
             return -1;
         }
 
-        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
-            final Task task = mTasks.get(taskNdx);
+        for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
+            final Task task = mChildren.get(taskNdx);
             final WindowState win = task.getTopVisibleAppMainWindow();
             if (win == null) {
                 continue;
@@ -1274,8 +1251,8 @@
             return;
         }
 
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            final Task task = mTasks.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final Task task = mChildren.get(i);
             if (task.isFullscreen()) {
                 results.searchDone = true;
                 return;
@@ -1306,8 +1283,8 @@
 
     void setTouchExcludeRegion(Task focusedTask, int delta, Region touchExcludeRegion,
             Rect contentRect, Rect postExclude) {
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            final Task task = mTasks.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final Task task = mChildren.get(i);
             AppWindowToken token = task.getTopVisibleAppToken();
             if (token == null || !token.hasContentToDisplay()) {
                 continue;
@@ -1429,15 +1406,6 @@
         return mBoundsAnimating;
     }
 
-    /**
-     * See {@link WindowManagerService#overridePlayingAppAnimationsLw}
-     */
-    void overridePlayingAppAnimations(Animation a) {
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            mTasks.get(i).overridePlayingAppAnimations(a);
-        }
-    }
-
     /** Returns true if a removal action is still being deferred. */
     boolean checkCompleteDeferredRemoval() {
         if (isAnimating()) {
@@ -1447,33 +1415,11 @@
             mDisplayContent.detachChild(this);
         }
 
-        boolean stillDeferringRemoval = false;
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            final Task task = mTasks.get(i);
-            stillDeferringRemoval |= task.checkCompleteDeferredRemoval();
-        }
-        return stillDeferringRemoval;
-    }
-
-    void checkAppWindowsReadyToShow(int displayId) {
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            final Task task = mTasks.get(i);
-            task.checkAppWindowsReadyToShow(displayId);
-        }
-    }
-
-    void updateAllDrawn(int displayId) {
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            final Task task = mTasks.get(i);
-            task.updateAllDrawn(displayId);
-        }
+        return super.checkCompleteDeferredRemoval();
     }
 
     void stepAppWindowsAnimation(long currentTime, int displayId) {
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            final Task task = mTasks.get(i);
-            task.stepAppWindowsAnimation(currentTime, displayId);
-        }
+        super.stepAppWindowsAnimation(currentTime, displayId);
 
         // TODO: Why aren't we just using the loop above for this? mAppAnimator.animating isn't set
         // below but is set in the loop above. See if it really matters...
@@ -1495,37 +1441,10 @@
         }
     }
 
-    void onAppTransitionDone() {
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            final Task task = mTasks.get(i);
-            task.onAppTransitionDone();
-        }
-    }
-
-    // TODO: Use WindowContainer.compareTo() once everything is using WindowContainer
-    boolean isFirstGreaterThanSecond(AppWindowToken first, AppWindowToken second) {
-        final Task firstTask = first.mTask;
-        final Task secondTask = second.mTask;
-
-        if (firstTask == secondTask) {
-            return firstTask.isFirstGreaterThanSecond(first, second);
-        }
-        return mTasks.indexOf(first) > mTasks.indexOf(second);
-    }
-
-    int rebuildWindowList(DisplayContent dc, int addIndex) {
-        final int count = mTasks.size();
-        for (int i = 0; i < count; i++) {
-            final Task task = mTasks.get(i);
-            addIndex = task.rebuildWindowList(dc, addIndex);
-        }
-        return addIndex;
-    }
-
     void getWindowOnDisplayBeforeToken(DisplayContent dc, WindowToken token,
             DisplayContent.GetWindowOnDisplaySearchResult result) {
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            final Task task = mTasks.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final Task task = mChildren.get(i);
             task.getWindowOnDisplayBeforeToken(dc, token, result);
             if (result.reachedToken) {
                 // We have reach the token we are interested in. End search.
@@ -1536,8 +1455,8 @@
 
     void getWindowOnDisplayAfterToken(DisplayContent dc, WindowToken token,
             DisplayContent.GetWindowOnDisplaySearchResult result) {
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            final Task task = mTasks.get(i);
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final Task task = mChildren.get(i);
             task.getWindowOnDisplayAfterToken(dc, token, result);
             if (result.foundWindow != null) {
                 // We have found a window after the token. End search.
@@ -1546,39 +1465,9 @@
         }
     }
 
-    // TODO: Remove once switched to use WindowContainer
+    @Override
     int getOrientation() {
-        if (!StackId.canSpecifyOrientation(mStackId)) {
-            return SCREEN_ORIENTATION_UNSET;
-        }
-
-        int candidate = SCREEN_ORIENTATION_UNSET;
-
-        for (int i = mTasks.size() - 1; i >= 0; --i) {
-            final Task task = mTasks.get(i);
-
-            if (!task.isVisible()) {
-                continue;
-            }
-
-            final int orientation = task.getOrientation();
-            if (orientation == SCREEN_ORIENTATION_BEHIND) {
-                candidate = orientation;
-                continue;
-            }
-
-            if (orientation != SCREEN_ORIENTATION_UNSET) {
-                if (task.fillsParent() || orientation != SCREEN_ORIENTATION_UNSPECIFIED) {
-                    return orientation;
-                }
-            }
-        }
-
-        return candidate;
-    }
-
-    // TODO: Remove once switched to use WindowContainer
-    boolean fillsParent() {
-        return mFullscreen;
+        return (StackId.canSpecifyOrientation(mStackId))
+                ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 6665a93..a13b6a1 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -609,7 +609,7 @@
 
         if (SHOW_TRANSACTIONS) Slog.i(
                 TAG, ">>> OPEN TRANSACTION animateLocked");
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         SurfaceControl.setAnimationTransaction();
         try {
             final int numDisplays = mDisplayContentsAnimators.size();
@@ -686,7 +686,7 @@
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
-            SurfaceControl.closeTransaction();
+            mService.closeSurfaceTransaction();
             if (SHOW_TRANSACTIONS) Slog.i(
                     TAG, "<<< CLOSE TRANSACTION animateLocked");
         }
@@ -742,7 +742,7 @@
 
     private void removeReplacedWindowsLocked() {
         if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION removeReplacedWindows");
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         try {
             for (int i = mService.mDisplayContents.size() - 1; i >= 0; i--) {
                 DisplayContent display = mService.mDisplayContents.valueAt(i);
@@ -756,7 +756,7 @@
                 }
             }
         } finally {
-            SurfaceControl.closeTransaction();
+            mService.closeSurfaceTransaction();
             if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION removeReplacedWindows");
         }
         mRemoveReplacedWindows = false;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ef05ad2..106284a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import android.annotation.CallSuper;
+import android.view.animation.Animation;
 
 import java.io.PrintWriter;
 import java.util.Comparator;
@@ -61,6 +62,11 @@
      */
     @CallSuper
     protected void addChild(E child, Comparator<E> comparator) {
+        if (child.mParent != null) {
+            throw new IllegalArgumentException("addChild: container=" + child
+                    + " is already a child of container=" + child.mParent
+                    + " can't add to container=" + this);
+        }
         child.mParent = this;
 
         if (mChildren.isEmpty() || comparator == null) {
@@ -79,6 +85,33 @@
         mChildren.add(child);
     }
 
+    /** Adds the input window container has a child of this container at the input index. */
+    @CallSuper
+    protected void addChild(E child, int index) {
+        if (child.mParent != null) {
+            throw new IllegalArgumentException("addChild: container=" + child
+                    + " is already a child of container=" + child.mParent
+                    + " can't add to container=" + this);
+        }
+        child.mParent = this;
+        mChildren.add(index, child);
+    }
+
+    /**
+     * Removes the input child container from this container which is its parent.
+     *
+     * @return True if the container did contain the input child and it was detached.
+     */
+    @CallSuper
+    void removeChild(E child) {
+        if (mChildren.remove(child)) {
+            child.mParent = null;
+        } else {
+            throw new IllegalArgumentException("removeChild: container=" + child
+                    + " is not a child of container=" + this);
+        }
+    }
+
     /**
      * Removes this window container and its children with no regard for what else might be going on
      * in the system. For example, the container will be removed during animation if this method is
@@ -97,7 +130,7 @@
         }
 
         if (mParent != null) {
-            mParent.detachChild(this);
+            mParent.removeChild(this);
         }
     }
 
@@ -115,17 +148,6 @@
         }
     }
 
-    /** Detaches the input child container from this container which is its parent. */
-    @CallSuper
-    void detachChild(WindowContainer child) {
-        if (mChildren.remove(child)) {
-            child.mParent = null;
-        } else {
-            throw new IllegalArgumentException("detachChild: container=" + child
-                    + " is not a child of container=" + this);
-        }
-    }
-
     /** Returns true if this window container has the input child. */
     boolean hasChild(WindowContainer child) {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
@@ -233,10 +255,10 @@
      * the container has any content to display.
      */
     boolean isVisible() {
-        // TODO: Will this be more correct if it checks the visibility of its parents? Yes.
-        // That is this container is only visible if its parents are visible vs visible if it has a
-        // visible child. In that case all overrides will need to call super and return false if
-        // this returns false.
+        // TODO: Will this be more correct if it checks the visibility of its parents?
+        // It depends...For example, Tasks and Stacks are only visible if there children are visible
+        // but, WindowState are not visible if there parent are not visible. Maybe have the
+        // container specify which direction to treverse for for visibility?
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowContainer wc = mChildren.get(i);
             if (wc.isVisible()) {
@@ -303,6 +325,12 @@
         }
     }
 
+    void overridePlayingAppAnimations(Animation a) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            mChildren.get(i).overridePlayingAppAnimations(a);
+        }
+    }
+
     void setOrientation(int orientation) {
         mOrientation = orientation;
     }
@@ -386,8 +414,8 @@
     }
 
     /**
-     * Returns -1, 0, or 1 depending on if the input container is greater than, equal to, or lesser
-     * than the input container in terms of z-order.
+     * Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than
+     * the input container in terms of z-order.
      */
     @Override
     public int compareTo(WindowContainer other) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a0b74f8..0e0cf02 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -893,6 +893,31 @@
     // current configuration.
     private final DisplayContentList mReconfigureOnConfigurationChanged = new DisplayContentList();
 
+    // State for the RemoteSurfaceTrace system used in testing. If this is enabled SurfaceControl
+    // instances will be replaced with an instance that writes a binary representation of all
+    // commands to mSurfaceTraceFd.
+    boolean mSurfaceTraceEnabled;
+    ParcelFileDescriptor mSurfaceTraceFd;
+    RemoteEventTrace mRemoteEventTrace;
+
+    void openSurfaceTransaction() {
+        synchronized (mWindowMap) {
+            if (mSurfaceTraceEnabled) {
+                mRemoteEventTrace.openSurfaceTransaction();
+            }
+            SurfaceControl.openTransaction();
+        }
+    }
+
+    void closeSurfaceTransaction() {
+        synchronized (mWindowMap) {
+            if (mSurfaceTraceEnabled) {
+                mRemoteEventTrace.closeSurfaceTransaction();
+            }
+            SurfaceControl.closeTransaction();
+        }
+    }
+
     /** Listener to notify activity manager about app transitions. */
     final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
             = new WindowManagerInternal.AppTransitionListener() {
@@ -1065,11 +1090,11 @@
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
 
-        SurfaceControl.openTransaction();
+        openSurfaceTransaction();
         try {
             createWatermarkInTransaction();
         } finally {
-            SurfaceControl.closeTransaction();
+            closeSurfaceTransaction();
         }
 
         showEmulatorDisplayOverlayIfNeeded();
@@ -1935,6 +1960,44 @@
         return false;
     }
 
+    @Override
+    public void enableSurfaceTrace(ParcelFileDescriptor pfd) {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
+            throw new SecurityException("Only shell can call enableSurfaceTrace");
+        }
+        final FileDescriptor fd = pfd.getFileDescriptor();
+
+        synchronized (mWindowMap) {
+            if (mSurfaceTraceEnabled) {
+                disableSurfaceTrace();
+            }
+            mSurfaceTraceEnabled = true;
+            mRemoteEventTrace = new RemoteEventTrace(this, fd);
+            mSurfaceTraceFd = pfd;
+            for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+                DisplayContent dc = mDisplayContents.valueAt(displayNdx);
+                dc.enableSurfaceTrace(fd);
+            }
+        }
+    }
+
+    @Override
+    public void disableSurfaceTrace() {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID &&
+            callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException("Only shell can call disableSurfaceTrace");
+        }
+        mSurfaceTraceEnabled = false;
+        mRemoteEventTrace = null;
+        mSurfaceTraceFd = null;
+        for (int displayNdx = mDisplayContents.size() - 1; displayNdx >= 0; --displayNdx) {
+            DisplayContent dc = mDisplayContents.valueAt(displayNdx);
+            dc.disableSurfaceTrace();
+        }
+    }
+
     /**
      * Set mScreenCaptureDisabled for specific user
      */
@@ -1973,7 +2036,7 @@
     /**
      * Performs some centralized bookkeeping clean-up on the window that is being removed.
      * NOTE: Should only be called from {@link WindowState#removeImmediately()}
-     * TODO: Maybe better handled with a method {@link WindowContainer#detachChild} if we can
+     * TODO: Maybe better handled with a method {@link WindowContainer#removeChild} if we can
      * figure-out a good way to have all parents of a WindowState doing the same thing without
      * forgetting to add the wiring when a new parent of WindowState is added.
      */
@@ -2196,7 +2259,7 @@
                         Slog.i(TAG_WM, ">>> OPEN TRANSACTION repositionChild");
                     }
 
-                    SurfaceControl.openTransaction();
+                    openSurfaceTransaction();
 
                     try {
 
@@ -2210,7 +2273,7 @@
                         }
 
                     } finally {
-                        SurfaceControl.closeTransaction();
+                        closeSurfaceTransaction();
                         if (SHOW_TRANSACTIONS) {
                             Slog.i(TAG_WM, "<<< CLOSE TRANSACTION repositionChild");
                         }
@@ -2881,14 +2944,11 @@
                 Slog.w(TAG_WM, "Attempted to set task id of non-existing app token: " + token);
                 return;
             }
-            final Task oldTask = atoken.mTask;
-            oldTask.removeAppToken(atoken);
 
             Task newTask = mTaskIdToTask.get(taskId);
             if (newTask == null) {
-                newTask = createTaskLocked(
-                        taskId, stackId, oldTask.mUserId, atoken, taskBounds, overrideConfig,
-                        isOnTopLauncher);
+                newTask = createTaskLocked(taskId, stackId, atoken.mTask.mUserId, atoken,
+                        taskBounds, overrideConfig, isOnTopLauncher);
             }
             newTask.addAppToken(Integer.MAX_VALUE /* at top */, atoken, taskResizeMode, homeTask);
         }
@@ -3005,11 +3065,12 @@
             config = computeNewConfigurationLocked();
 
         } else if (currentConfig != null) {
-            // No obvious action we need to take, but if our current
-            // state mismatches the activity manager's, update it,
-            // disregarding font scale, which should remain set to
-            // the value of the previous configuration.
-            mTempConfiguration.setToDefaults();
+            // No obvious action we need to take, but if our current state mismatches the activity
+            // manager's, update it, disregarding font scale, which should remain set to the value
+            // of the previous configuration.
+            // Here we're calling Configuration#unset() instead of setToDefaults() because we need
+            // to keep override configs clear of non-empty values (e.g. fontSize).
+            mTempConfiguration.unset();
             mTempConfiguration.updateFrom(currentConfig);
             computeScreenConfigurationLocked(mTempConfiguration);
             if (currentConfig.diff(mTempConfiguration) != 0) {
@@ -4005,7 +4066,7 @@
                 if (DEBUG_STACK) Slog.i(TAG_WM, "removeTask: could not find taskId=" + taskId);
                 return;
             }
-            task.removeLocked();
+            task.removeIfPossible();
         }
     }
 
@@ -4973,7 +5034,7 @@
 
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                     ">>> OPEN TRANSACTION showCircularMask(visible=" + visible + ")");
-            SurfaceControl.openTransaction();
+            openSurfaceTransaction();
             try {
                 if (visible) {
                     // TODO(multi-display): support multiple displays
@@ -4996,7 +5057,7 @@
                     mCircularDisplayMask = null;
                 }
             } finally {
-                SurfaceControl.closeTransaction();
+                closeSurfaceTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                         "<<< CLOSE TRANSACTION showCircularMask(visible=" + visible + ")");
             }
@@ -5008,7 +5069,7 @@
 
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                     ">>> OPEN TRANSACTION showEmulatorDisplayOverlay");
-            SurfaceControl.openTransaction();
+            openSurfaceTransaction();
             try {
                 if (mEmulatorDisplayOverlay == null) {
                     mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
@@ -5021,7 +5082,7 @@
                 }
                 mEmulatorDisplayOverlay.setVisibility(true);
             } finally {
-                SurfaceControl.closeTransaction();
+                closeSurfaceTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
                         "<<< CLOSE TRANSACTION showEmulatorDisplayOverlay");
             }
@@ -5063,6 +5124,8 @@
 
             if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG_WM,
                     ">>> OPEN TRANSACTION showStrictModeViolation");
+            // TODO: Modify this to use the surface trace once it is not going crazy.
+            // b/31532461
             SurfaceControl.openTransaction();
             try {
                 // TODO(multi-display): support multiple displays
@@ -5703,7 +5766,7 @@
             if (SHOW_TRANSACTIONS) {
                 Slog.i(TAG_WM, ">>> OPEN TRANSACTION setRotationUnchecked");
             }
-            SurfaceControl.openTransaction();
+            openSurfaceTransaction();
         }
         try {
             // NOTE: We disable the rotation in the emulator because
@@ -5728,7 +5791,7 @@
             mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();
         } finally {
             if (!inTransaction) {
-                SurfaceControl.closeTransaction();
+                closeSurfaceTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS) {
                     Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked");
                 }
@@ -8077,8 +8140,7 @@
         displayContent.layoutNeeded = true;
 
         boolean configChanged = updateOrientationFromAppTokensLocked(false);
-        mTempConfiguration.setToDefaults();
-        mTempConfiguration.updateFrom(mGlobalConfiguration);
+        mTempConfiguration.setTo(mGlobalConfiguration);
         computeScreenConfigurationLocked(mTempConfiguration);
         configChanged |= mGlobalConfiguration.diff(mTempConfiguration) != 0;
 
@@ -8669,7 +8731,7 @@
             // TODO(multidisplay): rotation on main screen only.
             displayContent.updateDisplayInfo();
             screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
-                    mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure);
+                    mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure, this);
             mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation);
         }
     }
@@ -8807,6 +8869,7 @@
             if (line != null) {
                 String[] toks = line.split("%");
                 if (toks != null && toks.length > 0) {
+                    // TODO(multi-display): Show watermarks on secondary displays.
                     mWatermark = new Watermark(getDefaultDisplayContentLocked().getDisplay(),
                             mRealDisplayMetrics, mFxSession, toks);
                 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bbb1fbb..60b0d03 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1569,7 +1569,7 @@
 
     boolean isObscuringFullscreen(final DisplayInfo displayInfo) {
         Task task = getTask();
-        if (task != null && task.mStack != null && !task.mStack.isFullscreen()) {
+        if (task != null && task.mStack != null && !task.mStack.fillsParent()) {
             return false;
         }
         if (!isOpaqueDrawn() || !isFrameFullscreen(displayInfo)) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 788f28d..125368c 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -70,6 +70,7 @@
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
+import java.io.FileDescriptor;
 
 /**
  * Keep track of animations and surface operations for a single WindowState.
@@ -1580,7 +1581,7 @@
 
         try {
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setWallpaperOffset");
-            SurfaceControl.openTransaction();
+            mService.openSurfaceTransaction();
             mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left,
                     mWin.mFrame.top + top, false);
             calculateSurfaceWindowCrop(mTmpClipRect, mTmpFinalClipRect);
@@ -1589,7 +1590,7 @@
             Slog.w(TAG, "Error positioning surface of " + mWin
                     + " pos=(" + left + "," + top + ")", e);
         } finally {
-            SurfaceControl.closeTransaction();
+            mService.closeSurfaceTransaction();
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION setWallpaperOffset");
         }
@@ -2006,4 +2007,20 @@
                     DtDy * w.mVScale, false);
         }
     }
+
+    void enableSurfaceTrace(FileDescriptor fd) {
+        if (mSurfaceController != null) {
+            mSurfaceController.installRemoteTrace(fd);
+        }
+    }
+
+    void disableSurfaceTrace() {
+        if (mSurfaceController != null) {
+            try {
+                mSurfaceController.removeRemoteTrace();
+            } catch (ClassCastException e) {
+                Slog.e(TAG, "Disable surface trace for " + this + " but its not enabled");
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index f5ed9d1..cc72352 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -41,6 +41,7 @@
 
 import android.util.Slog;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
@@ -70,6 +71,8 @@
     private boolean mHiddenForOtherReasons = true;
     private final String title;
 
+    private final WindowManagerService mService;
+
     public WindowSurfaceController(SurfaceSession s,
             String name, int w, int h, int format, int flags, WindowStateAnimator animator) {
         mAnimator = animator;
@@ -79,6 +82,8 @@
 
         title = name;
 
+        mService = animator.mService;
+
         // For opaque child windows placed under parent windows,
         // we use a special SurfaceControl which mirrors commands
         // to a black-out layer placed one Z-layer below the surface.
@@ -95,6 +100,19 @@
             mSurfaceControl = new SurfaceControl(
                     s, name, w, h, format, flags);
         }
+
+        if (mService.mSurfaceTraceEnabled) {
+            mSurfaceControl = new RemoteSurfaceTrace(mService.mSurfaceTraceFd.getFileDescriptor(),
+                    mSurfaceControl, animator.mWin);
+        }
+    }
+
+    void installRemoteTrace(FileDescriptor fd) {
+        mSurfaceControl = new RemoteSurfaceTrace(fd, mSurfaceControl, mAnimator.mWin);
+    }
+
+    void removeRemoteTrace() {
+        mSurfaceControl = new SurfaceControl(mSurfaceControl);
     }
 
 
@@ -127,7 +145,7 @@
     }
 
     void setPositionAndLayer(float left, float top, int layerStack, int layer) {
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         try {
             mSurfaceX = left;
             mSurfaceY = top;
@@ -146,7 +164,7 @@
                 mAnimator.reclaimSomeSurfaceMemory("create-init", true);
             }
         } finally {
-            SurfaceControl.closeTransaction();
+            mService.closeSurfaceTransaction();
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION setPositionAndLayer");
         }
@@ -230,11 +248,11 @@
 
     void setLayer(int layer) {
         if (mSurfaceControl != null) {
-            SurfaceControl.openTransaction();
+            mService.openSurfaceTransaction();
             try {
                 mSurfaceControl.setLayer(layer);
             } finally {
-                SurfaceControl.closeTransaction();
+                mService.closeSurfaceTransaction();
             }
         }
     }
@@ -338,11 +356,11 @@
             return;
         }
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setTransparentRegion");
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         try {
             mSurfaceControl.setTransparentRegionHint(region);
         } finally {
-            SurfaceControl.closeTransaction();
+            mService.closeSurfaceTransaction();
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION setTransparentRegion");
         }
@@ -356,11 +374,11 @@
             return;
         }
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setOpaqueLocked");
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         try {
             mSurfaceControl.setOpaque(isOpaque);
         } finally {
-            SurfaceControl.closeTransaction();
+            mService.closeSurfaceTransaction();
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setOpaqueLocked");
         }
     }
@@ -373,11 +391,11 @@
             return;
         }
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setSecureLocked");
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         try {
             mSurfaceControl.setSecure(isSecure);
         } finally {
-            SurfaceControl.closeTransaction();
+            mService.closeSurfaceTransaction();
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION setSecureLocked");
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 5025df6..13d3501 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -303,6 +303,7 @@
         mSustainedPerformanceModeCurrent = false;
         mService.mTransactionSequence++;
 
+        // TODO(multi-display): Perform same actions on all displays.
         final DisplayContent defaultDisplay = mService.getDefaultDisplayContentLocked();
         final DisplayInfo defaultInfo = defaultDisplay.getDisplayInfo();
         final int defaultDw = defaultInfo.logicalWidth;
@@ -310,13 +311,13 @@
 
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                 ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
-        SurfaceControl.openTransaction();
+        mService.openSurfaceTransaction();
         try {
             applySurfaceChangesTransaction(recoveringMemory, numDisplays, defaultDw, defaultDh);
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
         } finally {
-            SurfaceControl.closeTransaction();
+            mService.closeSurfaceTransaction();
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
         }
@@ -1256,11 +1257,11 @@
 
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     ">>> OPEN TRANSACTION handleAppTransitionReadyLocked()");
-            SurfaceControl.openTransaction();
+            mService.openSurfaceTransaction();
             try {
                 mService.mAnimator.orAnimating(appAnimator.showAllWindowsLocked());
             } finally {
-                SurfaceControl.closeTransaction();
+                mService.closeSurfaceTransaction();
                 if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                         "<<< CLOSE TRANSACTION handleAppTransitionReadyLocked()");
             }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 9b92ecda..735efa9 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -180,26 +180,13 @@
             }
             if (!mChildren.contains(win)) {
                 if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
-                mChildren.add(tokenWindowsPos, win);
+                addChild(win, tokenWindowsPos);
             }
         } else if (dc != null) {
             dc.addChildWindowToWindowList(win);
         }
     }
 
-    int reAddAppWindows(DisplayContent displayContent, int index) {
-        final int count = mChildren.size();
-        for (int i = 0; i < count; i++) {
-            final WindowState win = mChildren.get(i);
-            final DisplayContent winDisplayContent = win.getDisplayContent();
-            if (winDisplayContent == displayContent || winDisplayContent == null) {
-                win.mDisplayContent = displayContent;
-                index = win.reAddWindow(index);
-            }
-        }
-        return index;
-    }
-
     /** Return the first window in the token window list that isn't a starting window or null. */
     WindowState getFirstNonStartingWindow() {
         final int count = mChildren.size();
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index 19898e3..973f1b9 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -88,6 +88,31 @@
     }
 
     @Test
+    public void testAdd_AlreadyHasParent() throws Exception {
+        final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
+        final TestWindowContainer root = builder.setLayer(0).build();
+
+        final TestWindowContainer child1 = root.addChildWindow();
+        final TestWindowContainer child2 = root.addChildWindow();
+
+        boolean gotException = false;
+        try {
+            child1.addChildWindow(child2);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+
+        gotException = false;
+        try {
+            root.addChildWindow(child2);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    @Test
     public void testHasChild() throws Exception {
         final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
         final TestWindowContainer root = builder.setLayer(0).build();
@@ -210,7 +235,7 @@
     }
 
     @Test
-    public void testDetachChild() throws Exception {
+    public void testRemoveChild() throws Exception {
         final TestWindowContainerBuilder builder = new TestWindowContainerBuilder();
         final TestWindowContainer root = builder.setLayer(0).build();
         final TestWindowContainer child1 = root.addChildWindow();
@@ -220,7 +245,7 @@
 
         assertTrue(root.hasChild(child2));
         assertTrue(root.hasChild(child21));
-        root.detachChild(child2);
+        root.removeChild(child2);
         assertFalse(root.hasChild(child2));
         assertFalse(root.hasChild(child21));
         assertNull(child2.getParentWindow());
@@ -229,7 +254,7 @@
         assertTrue(root.hasChild(child11));
         try {
             // Can only detach our direct children.
-            root.detachChild(child11);
+            root.removeChild(child11);
         } catch (IllegalArgumentException e) {
             gotException = true;
         }
@@ -410,6 +435,11 @@
             return mChildren.size();
         }
 
+        TestWindowContainer addChildWindow(TestWindowContainer child) {
+            addChild(child, mWindowSubLayerComparator);
+            return child;
+        }
+
         TestWindowContainer addChildWindow(TestWindowContainerBuilder childBuilder) {
             TestWindowContainer child = childBuilder.build();
             addChild(child, mWindowSubLayerComparator);
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 0c75630..7b68a4c 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1082,7 +1082,7 @@
      *
      * @param connectionManagerPhoneAccount See description at
      *         {@link #onCreateOutgoingConnection(PhoneAccountHandle, ConnectionRequest)}.
-     * @param request Details about the incoming call.
+     * @param request Details about the outgoing call.
      * @return The {@code Connection} object to satisfy this call, or {@code null} to
      *         not handle the call.
      */
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index e2b6b54..bdc9c45 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -702,6 +702,13 @@
     public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
 
     /**
+     * APN types that user is not allowed to modify
+     * @hide
+     */
+    public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY =
+            "read_only_apn_types_string_array";
+
+    /**
      * Boolean indicating if intent for emergency call state changes should be broadcast
      * @hide
      */
@@ -1068,6 +1075,7 @@
         sDefaults.putString(KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING, "");
         sDefaults.putBoolean(KEY_CSP_ENABLED_BOOL, false);
         sDefaults.putBoolean(KEY_ALLOW_ADDING_APNS_BOOL, true);
+        sDefaults.putStringArray(KEY_READ_ONLY_APN_TYPES_STRING_ARRAY, null);
         sDefaults.putBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL, false);
         sDefaults.putBoolean(KEY_DISABLE_SEVERE_WHEN_EXTREME_DISABLED_BOOL, true);
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index b417a1c..fdc68b9 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -140,6 +140,18 @@
     /** APN type for Emergency PDN. This is not an IA apn, but is used
      * for access to carrier services in an emergency call situation. */
     public static final String APN_TYPE_EMERGENCY = "emergency";
+    /** Array of all APN types */
+    public static final String[] APN_TYPES = {APN_TYPE_DEFAULT,
+            APN_TYPE_MMS,
+            APN_TYPE_SUPL,
+            APN_TYPE_DUN,
+            APN_TYPE_HIPRI,
+            APN_TYPE_FOTA,
+            APN_TYPE_IMS,
+            APN_TYPE_CBS,
+            APN_TYPE_IA,
+            APN_TYPE_EMERGENCY
+    };
 
     public static final int RIL_CARD_MAX_APPS    = 8;
 
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index e7edcc5..45f5acd 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -194,7 +194,10 @@
     manifestAction["uses-configuration"];
     manifestAction["uses-feature"];
     manifestAction["supports-screens"];
+
     manifestAction["compatible-screens"];
+    manifestAction["compatible-screens"]["screen"];
+
     manifestAction["supports-gl-texture"];
 
     // Application actions.
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 6ca13d6..4a70060 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -30,6 +30,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.util.DisplayMetrics;
 import android.view.AppTransitionAnimationSpec;
@@ -610,4 +611,12 @@
     public Bitmap screenshotWallpaper() throws RemoteException {
         return null;
     }
+
+    @Override
+    public void enableSurfaceTrace(ParcelFileDescriptor fd) throws RemoteException {
+    }
+
+    @Override
+    public void disableSurfaceTrace() throws RemoteException {
+    }
 }
diff --git a/wifi/java/android/net/wifi/nan/ConfigRequest.java b/wifi/java/android/net/wifi/nan/ConfigRequest.java
index 44544de..78e4052 100644
--- a/wifi/java/android/net/wifi/nan/ConfigRequest.java
+++ b/wifi/java/android/net/wifi/nan/ConfigRequest.java
@@ -23,14 +23,14 @@
 /**
  * Defines a request object to configure a Wi-Fi NAN network. Built using
  * {@link ConfigRequest.Builder}. Configuration is requested using
- * {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}.
+ * {@link WifiNanManager#connect(android.os.Handler, ConfigRequest, WifiNanEventCallback)}.
  * Note that the actual achieved configuration may be different from the
  * requested configuration - since different applications may request different
  * configurations.
  *
  * @hide PROPOSED_NAN_API
  */
-public class ConfigRequest implements Parcelable {
+public final class ConfigRequest implements Parcelable {
     /**
      * Lower range of possible cluster ID.
      *
diff --git a/wifi/java/android/net/wifi/nan/PublishConfig.java b/wifi/java/android/net/wifi/nan/PublishConfig.java
index 71f99d9..6203f95 100644
--- a/wifi/java/android/net/wifi/nan/PublishConfig.java
+++ b/wifi/java/android/net/wifi/nan/PublishConfig.java
@@ -37,7 +37,7 @@
  *
  * @hide PROPOSED_NAN_API
  */
-public class PublishConfig implements Parcelable {
+public final class PublishConfig implements Parcelable {
     /** @hide */
     @IntDef({
             PUBLISH_TYPE_UNSOLICITED, PUBLISH_TYPE_SOLICITED })
diff --git a/wifi/java/android/net/wifi/nan/SubscribeConfig.java b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
index 7904875..5a2c762 100644
--- a/wifi/java/android/net/wifi/nan/SubscribeConfig.java
+++ b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
@@ -37,7 +37,7 @@
  *
  * @hide PROPOSED_NAN_API
  */
-public class SubscribeConfig implements Parcelable {
+public final class SubscribeConfig implements Parcelable {
     /** @hide */
     @IntDef({
             SUBSCRIBE_TYPE_PASSIVE, SUBSCRIBE_TYPE_ACTIVE })
diff --git a/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java b/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java
index 6e714f1..029e36a 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java
@@ -23,7 +23,7 @@
 
 /**
  * Base class for NAN events callbacks. Should be extended by applications and set when calling
- * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)}. These are callbacks
+ * {@link WifiNanManager#connect(android.os.Handler, WifiNanEventCallback)}. These are callbacks
  * applying to the NAN connection as a whole - not to specific publish or subscribe sessions -
  * for that see {@link WifiNanSessionCallback}.
  *
@@ -46,7 +46,7 @@
 
     /**
      * Indicates that a {@link ConfigRequest} passed in
-     * {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}
+     * {@link WifiNanManager#connect(android.os.Handler, ConfigRequest, WifiNanEventCallback)}
      * couldn't be applied since other connections already exist with an incompatible
      * configurations. Failure reason flag for {@link WifiNanEventCallback#onConnectFail(int)}.
      */
@@ -60,7 +60,7 @@
 
     /**
      * Called when NAN connect operation
-     * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)}
+     * {@link WifiNanManager#connect(android.os.Handler, WifiNanEventCallback)}
      * is completed and that we can now start discovery sessions or connections.
      */
     public void onConnectSuccess() {
@@ -69,7 +69,7 @@
 
     /**
      * Called when NAN connect operation
-     * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)} failed.
+     * {@link WifiNanManager#connect(android.os.Handler, WifiNanEventCallback)} failed.
      *
      * @param reason Failure reason code, see
      *            {@code WifiNanEventCallback.REASON_*}.
@@ -91,7 +91,7 @@
      * <p>
      *     This callback is only called if the NAN connection enables it using
      *     {@link ConfigRequest.Builder#setEnableIdentityChangeCallback(boolean)} in
-     *     {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}
+     *     {@link WifiNanManager#connect(android.os.Handler, ConfigRequest, WifiNanEventCallback)}
      *     . It is disabled by default since it may result in additional wake-ups of the host -
      *     increasing power.
      *
diff --git a/wifi/java/android/net/wifi/nan/WifiNanManager.java b/wifi/java/android/net/wifi/nan/WifiNanManager.java
index 82d22bc..24ee640 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanManager.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanManager.java
@@ -60,7 +60,7 @@
  * The class provides access to:
  * <ul>
  * <li>Initialize a NAN cluster (peer-to-peer synchronization). Refer to
- * {@link #connect(Looper, WifiNanEventCallback)}.
+ * {@link #connect(Handler, WifiNanEventCallback)}.
  * <li>Create discovery sessions (publish or subscribe sessions).
  * Refer to {@link #publish(PublishConfig, WifiNanSessionCallback)} and
  * {@link #subscribe(SubscribeConfig, WifiNanSessionCallback)}.
@@ -77,7 +77,7 @@
  *     Note that this broadcast is not sticky - you should register for it and then check the
  *     above API to avoid a race condition.
  * <p>
- *     An application must use {@link #connect(Looper, WifiNanEventCallback)} to initialize a NAN
+ *     An application must use {@link #connect(Handler, WifiNanEventCallback)} to initialize a NAN
  *     cluster - before making any other NAN operation. NAN cluster membership is a device-wide
  *     operation - the API guarantees that the device is in a cluster or joins a NAN cluster (or
  *     starts one if none can be found). Information about connection success (or failure) are
@@ -343,13 +343,13 @@
      * Note: a NAN cluster is a shared resource - if the device is already connected to a cluster
      * than this function will simply indicate success immediately.
      *
-     * @param looper The Looper on which to execute all callbacks related to the
+     * @param handler The Handler on whose thread to execute all callbacks related to the
      *            connection - including all sessions opened as part of this
-     *            connection.
+     *            connection. If a null is provided then the application's main thread will be used.
      * @param callback A callback extended from {@link WifiNanEventCallback}.
      */
-    public void connect(@NonNull Looper looper, @NonNull WifiNanEventCallback callback) {
-        connect(looper, null, callback);
+    public void connect(@Nullable Handler handler, @NonNull WifiNanEventCallback callback) {
+        connect(handler, null, callback);
     }
 
     /**
@@ -360,30 +360,31 @@
      * An application <b>must</b> call {@link #disconnect()} when done with the Wi-Fi NAN
      * connection. Allows requesting a specific configuration using {@link ConfigRequest}. If not
      * necessary (default configuration should usually work) use the
-     * {@link #connect(Looper, WifiNanEventCallback)} method instead.
+     * {@link #connect(Handler, WifiNanEventCallback)} method instead.
      * <p>
      * Note: a NAN cluster is a shared resource - if the device is already connected to a cluster
      * than this function will simply indicate success immediately.
      *
-     * @param looper The Looper on which to execute all callbacks related to the
+     * @param handler The Handler on whose thread to execute all callbacks related to the
      *            connection - including all sessions opened as part of this
-     *            connection.
+     *            connection. If a null is provided then the application's main thread will be used.
      * @param configRequest The requested NAN configuration.
      * @param callback A callback extended from {@link WifiNanEventCallback}.
      */
-    public void connect(@NonNull Looper looper, @Nullable ConfigRequest configRequest,
+    public void connect(@Nullable Handler handler, @Nullable ConfigRequest configRequest,
             @NonNull WifiNanEventCallback callback) {
         if (VDBG) {
-            Log.v(TAG, "connect(): looper=" + looper + ", callback=" + callback + ", configRequest="
-                    + configRequest);
+            Log.v(TAG,
+                    "connect(): handler=" + handler + ", callback=" + callback + ", configRequest="
+                            + configRequest);
         }
 
         synchronized (mLock) {
-            mLooper = looper;
+            mLooper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
 
             try {
                 mClientId = mService.connect(mBinder, mContext.getOpPackageName(),
-                        new WifiNanEventCallbackProxy(this, looper, callback), configRequest);
+                        new WifiNanEventCallbackProxy(this, mLooper, callback), configRequest);
             } catch (RemoteException e) {
                 mClientId = INVALID_CLIENT_ID;
                 mLooper = null;
@@ -402,7 +403,7 @@
      * connections explicitly before a disconnect.
      * <p>
      * An application may re-connect after a disconnect using
-     * {@link WifiNanManager#connect(Looper, WifiNanEventCallback)} .
+     * {@link WifiNanManager#connect(Handler, WifiNanEventCallback)} .
      */
     public void disconnect() {
         if (VDBG) Log.v(TAG, "disconnect()");