Merge "Rendering the window frame with a second thread"
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index da9a8c8..5af6504 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -669,6 +669,10 @@
      * The frame delay may be ignored when the animation system uses an external timing
      * source, such as the display refresh rate (vsync), to govern animations.
      *
+     * Note that this method should be called from the same thread that {@link #start()} is
+     * called in order to check the frame delay for that animation. A runtime exception will be
+     * thrown if the calling thread does not have a Looper.
+     *
      * @return the requested time between frames, in milliseconds
      */
     public static long getFrameDelay() {
@@ -685,6 +689,10 @@
      * The frame delay may be ignored when the animation system uses an external timing
      * source, such as the display refresh rate (vsync), to govern animations.
      *
+     * Note that this method should be called from the same thread that {@link #start()} is
+     * called in order to have the new frame delay take effect on that animation. A runtime
+     * exception will be thrown if the calling thread does not have a Looper.
+     *
      * @param frameDelay the requested time between frames, in milliseconds
      */
     public static void setFrameDelay(long frameDelay) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 6606f9b..61a9a84 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -468,36 +468,51 @@
      */
     public static final int DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
 
-
     /**
      * Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
-     * that the resize is from the window manager (instead of the user).
+     * that the resize doesn't need to preserve the window, and can be skipped if bounds
+     * is unchanged. This mode is used by window manager in most cases.
      * @hide
      */
     public static final int RESIZE_MODE_SYSTEM = 0;
 
     /**
      * Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
-     * that the resize is from the window manager (instead of the user) due to a screen
-     * rotation change.
+     * that the resize should preserve the window if possible.
      * @hide
      */
-    public static final int RESIZE_MODE_SYSTEM_SCREEN_ROTATION = 1;
-
-    /**
-     * Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
-     * that the resize is initiated by the user (most likely via a drag action on the
-     * window's edge or corner).
-     * @hide
-     */
-    public static final int RESIZE_MODE_USER   = 2;
+    public static final int RESIZE_MODE_PRESERVE_WINDOW   = (0x1 << 0);
 
     /**
      * Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
      * that the resize should be performed even if the bounds appears unchanged.
      * @hide
      */
-    public static final int RESIZE_MODE_FORCED = 3;
+    public static final int RESIZE_MODE_FORCED = (0x1 << 1);
+
+    /**
+     * Input parameter to {@link android.app.IActivityManager#resizeTask} used by window
+     * manager during a screen rotation.
+     * @hide
+     */
+    public static final int RESIZE_MODE_SYSTEM_SCREEN_ROTATION = RESIZE_MODE_PRESERVE_WINDOW;
+
+    /**
+     * Input parameter to {@link android.app.IActivityManager#resizeTask} used when the
+     * resize is due to a drag action.
+     * @hide
+     */
+    public static final int RESIZE_MODE_USER = RESIZE_MODE_PRESERVE_WINDOW;
+
+    /**
+     * Input parameter to {@link android.app.IActivityManager#resizeTask} which indicates
+     * that the resize should preserve the window if possible, and should not be skipped
+     * even if the bounds is unchanged. Usually used to force a resizing when a drag action
+     * is ending.
+     * @hide
+     */
+    public static final int RESIZE_MODE_USER_FORCED =
+            RESIZE_MODE_PRESERVE_WINDOW | RESIZE_MODE_FORCED;
 
     /** @hide */
     public int getFrontActivityScreenCompatMode() {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 40eb799..373a23f 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -55,4 +55,13 @@
      * @param userId ID of the user or {@link android.os.UserHandle#USER_ALL}
      */
     public abstract ComponentName getHomeActivityForUser(int userId);
+
+    /**
+     * Called when a user has been deleted. This can happen during normal device usage
+     * or just at startup, when partially removed users are purged. Any state persisted by the
+     * ActivityManager should be purged now.
+     *
+     * @param userId The user being cleaned up.
+     */
+    public abstract void onUserRemoved(int userId);
 }
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index e965d65..3f566eb 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1545,7 +1545,7 @@
     /**
      * <p>Whether video stabilization is
      * active.</p>
-     * <p>Video stabilization automatically translates and scales images from
+     * <p>Video stabilization automatically warps images from
      * the camera in order to stabilize motion between consecutive frames.</p>
      * <p>If enabled, video stabilization can modify the
      * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream stabilized.</p>
@@ -1555,6 +1555,15 @@
      * the video stabilization modes in the first several capture results may
      * still be "OFF", and it will become "ON" when the initialization is
      * done.</p>
+     * <p>In addition, not all recording sizes or frame rates may be supported for
+     * stabilization by a device that reports stabilization support. It is guaranteed
+     * that an output targeting a MediaRecorder or MediaCodec will be stabilized if
+     * the recording resolution is less than or equal to 1920 x 1080 (width less than
+     * or equal to 1920, height less than or equal to 1080), and the recording
+     * frame rate is less than or equal to 30fps.  At other sizes, the CaptureResult
+     * {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return
+     * OFF if the recording output is not stabilized, or if there are no output
+     * Surface types that can be stabilized.</p>
      * <p>If a camera device supports both this mode and OIS
      * ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may
      * produce undesirable interaction, so it is recommended not to enable
@@ -1566,6 +1575,7 @@
      * </ul></p>
      * <p>This key is available on all devices.</p>
      *
+     * @see CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
      * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see #CONTROL_VIDEO_STABILIZATION_MODE_OFF
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 46eddb3..b3acf2b 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2056,7 +2056,7 @@
     /**
      * <p>Whether video stabilization is
      * active.</p>
-     * <p>Video stabilization automatically translates and scales images from
+     * <p>Video stabilization automatically warps images from
      * the camera in order to stabilize motion between consecutive frames.</p>
      * <p>If enabled, video stabilization can modify the
      * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to keep the video stream stabilized.</p>
@@ -2066,6 +2066,15 @@
      * the video stabilization modes in the first several capture results may
      * still be "OFF", and it will become "ON" when the initialization is
      * done.</p>
+     * <p>In addition, not all recording sizes or frame rates may be supported for
+     * stabilization by a device that reports stabilization support. It is guaranteed
+     * that an output targeting a MediaRecorder or MediaCodec will be stabilized if
+     * the recording resolution is less than or equal to 1920 x 1080 (width less than
+     * or equal to 1920, height less than or equal to 1080), and the recording
+     * frame rate is less than or equal to 30fps.  At other sizes, the CaptureResult
+     * {@link CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE android.control.videoStabilizationMode} field will return
+     * OFF if the recording output is not stabilized, or if there are no output
+     * Surface types that can be stabilized.</p>
      * <p>If a camera device supports both this mode and OIS
      * ({@link CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE android.lens.opticalStabilizationMode}), turning both modes on may
      * produce undesirable interaction, so it is recommended not to enable
@@ -2077,6 +2086,7 @@
      * </ul></p>
      * <p>This key is available on all devices.</p>
      *
+     * @see CaptureRequest#CONTROL_VIDEO_STABILIZATION_MODE
      * @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
      * @see CaptureRequest#SCALER_CROP_REGION
      * @see #CONTROL_VIDEO_STABILIZATION_MODE_OFF
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index c373308..cd483b1 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.MathUtils;
 
 import java.io.Serializable;
 import java.util.ArrayList;
@@ -1345,18 +1346,19 @@
      */
     void readFromParcelInner(Parcel parcel) {
         int length = parcel.readInt();
-        if (length < 0) {
-            throw new RuntimeException("Bad length in parcel: " + length);
-        }
         readFromParcelInner(parcel, length);
     }
 
     private void readFromParcelInner(Parcel parcel, int length) {
-        if (length == 0) {
+        if (length < 0) {
+            throw new RuntimeException("Bad length in parcel: " + length);
+
+        } else if (length == 0) {
             // Empty Bundle or end of data.
             mParcelledData = EMPTY_PARCEL;
             return;
         }
+
         int magic = parcel.readInt();
         if (magic != BUNDLE_MAGIC) {
             //noinspection ThrowableInstanceNeverThrown
@@ -1366,7 +1368,7 @@
 
         // Advance within this Parcel
         int offset = parcel.dataPosition();
-        parcel.setDataPosition(offset + length);
+        parcel.setDataPosition(MathUtils.addOrThrow(offset, length));
 
         Parcel p = Parcel.obtain();
         p.setDataPosition(0);
diff --git a/core/java/android/os/ParcelableParcel.java b/core/java/android/os/ParcelableParcel.java
index 11785f1..5bbe6488 100644
--- a/core/java/android/os/ParcelableParcel.java
+++ b/core/java/android/os/ParcelableParcel.java
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.util.MathUtils;
+
 /**
  * Parcelable containing a raw Parcel of data.
  * @hide
@@ -33,9 +35,13 @@
         mParcel = Parcel.obtain();
         mClassLoader = loader;
         int size = src.readInt();
+        if (size < 0) {
+            throw new IllegalArgumentException("Negative size read from parcel");
+        }
+
         int pos = src.dataPosition();
-        mParcel.appendFrom(src, src.dataPosition(), size);
-        src.setDataPosition(pos + size);
+        src.setDataPosition(MathUtils.addOrThrow(pos, size));
+        mParcel.appendFrom(src, pos, size);
     }
 
     public Parcel getParcel() {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 1b104e3..241e6db 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -19,6 +19,7 @@
 import static android.net.TrafficStats.KB_IN_BYTES;
 import static android.system.OsConstants.SEEK_SET;
 
+import android.annotation.Nullable;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -764,19 +765,33 @@
      * @see #buildDocumentUri(String, String)
      * @see #buildDocumentUriUsingTree(Uri, String)
      */
-    public static boolean isDocumentUri(Context context, Uri uri) {
-        final List<String> paths = uri.getPathSegments();
-        if (paths.size() == 2 && PATH_DOCUMENT.equals(paths.get(0))) {
-            return isDocumentsProvider(context, uri.getAuthority());
-        }
-        if (paths.size() == 4 && PATH_TREE.equals(paths.get(0))
-                && PATH_DOCUMENT.equals(paths.get(2))) {
-            return isDocumentsProvider(context, uri.getAuthority());
+    public static boolean isDocumentUri(Context context, @Nullable Uri uri) {
+        if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
+            final List<String> paths = uri.getPathSegments();
+            if (paths.size() == 2) {
+                return PATH_DOCUMENT.equals(paths.get(0));
+            } else if (paths.size() == 4) {
+                return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2));
+            }
         }
         return false;
     }
 
     /** {@hide} */
+    public static boolean isRootUri(Context context, @Nullable Uri uri) {
+        if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) {
+            final List<String> paths = uri.getPathSegments();
+            return (paths.size() == 2 && PATH_ROOT.equals(paths.get(0)));
+        }
+        return false;
+    }
+
+    /** {@hide} */
+    public static boolean isContentUri(@Nullable Uri uri) {
+        return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme());
+    }
+
+    /** {@hide} */
     public static boolean isTreeUri(Uri uri) {
         final List<String> paths = uri.getPathSegments();
         return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0)));
diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java
index 8b57d3d..acca3ed 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -185,4 +185,24 @@
     public static void randomSeed(long seed) {
         sRandom.setSeed(seed);
     }
+
+    /**
+     * Returns the sum of the two parameters, or throws an exception if the resulting sum would
+     * cause an overflow or underflow.
+     * @throws IllegalArgumentException when overflow or underflow would occur.
+     */
+    public static int addOrThrow(int a, int b) throws IllegalArgumentException {
+        if (b == 0) {
+            return a;
+        }
+
+        if (b > 0 && a <= (Integer.MAX_VALUE - b)) {
+            return a + b;
+        }
+
+        if (b < 0 && a >= (Integer.MIN_VALUE - b)) {
+            return a + b;
+        }
+        throw new IllegalArgumentException("Addition overflow: " + a + " + " + b);
+    }
 }
diff --git a/graphics/tests/graphicstests/src/android/graphics/PaintTest.java b/graphics/tests/graphicstests/src/android/graphics/PaintTest.java
index b67aa7d..6763dd1 100644
--- a/graphics/tests/graphicstests/src/android/graphics/PaintTest.java
+++ b/graphics/tests/graphicstests/src/android/graphics/PaintTest.java
@@ -162,4 +162,60 @@
         } catch (IndexOutOfBoundsException e) {
         }
     }
+
+    public void testMeasureTextBidi() {
+        Paint p = new Paint();
+        {
+            String bidiText = "abc \u0644\u063A\u0629 def";
+            p.setBidiFlags(Paint.BIDI_LTR);
+            float width = p.measureText(bidiText, 0, 4);
+            p.setBidiFlags(Paint.BIDI_RTL);
+            width += p.measureText(bidiText, 4, 7);
+            p.setBidiFlags(Paint.BIDI_LTR);
+            width += p.measureText(bidiText, 7, bidiText.length());
+            assertEquals(width, p.measureText(bidiText), 1.0f);
+        }
+        {
+            String bidiText = "abc \u0644\u063A\u0629 def";
+            p.setBidiFlags(Paint.BIDI_DEFAULT_LTR);
+            float width = p.measureText(bidiText, 0, 4);
+            width += p.measureText(bidiText, 4, 7);
+            width += p.measureText(bidiText, 7, bidiText.length());
+            assertEquals(width, p.measureText(bidiText), 1.0f);
+        }
+        {
+            String bidiText = "abc \u0644\u063A\u0629 def";
+            p.setBidiFlags(Paint.BIDI_FORCE_LTR);
+            float width = p.measureText(bidiText, 0, 4);
+            width += p.measureText(bidiText, 4, 7);
+            width += p.measureText(bidiText, 7, bidiText.length());
+            assertEquals(width, p.measureText(bidiText), 1.0f);
+        }
+        {
+            String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629";
+            p.setBidiFlags(Paint.BIDI_RTL);
+            float width = p.measureText(bidiText, 0, 4);
+            p.setBidiFlags(Paint.BIDI_LTR);
+            width += p.measureText(bidiText, 4, 7);
+            p.setBidiFlags(Paint.BIDI_RTL);
+            width += p.measureText(bidiText, 7, bidiText.length());
+            assertEquals(width, p.measureText(bidiText), 1.0f);
+        }
+        {
+            String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629";
+            p.setBidiFlags(Paint.BIDI_DEFAULT_RTL);
+            float width = p.measureText(bidiText, 0, 4);
+            width += p.measureText(bidiText, 4, 7);
+            width += p.measureText(bidiText, 7, bidiText.length());
+            assertEquals(width, p.measureText(bidiText), 1.0f);
+        }
+        {
+            String bidiText = "\u0644\u063A\u0629 abc \u0644\u063A\u0629";
+            p.setBidiFlags(Paint.BIDI_FORCE_RTL);
+            float width = p.measureText(bidiText, 0, 4);
+            width += p.measureText(bidiText, 4, 7);
+            width += p.measureText(bidiText, 7, bidiText.length());
+            assertEquals(width, p.measureText(bidiText), 1.0f);
+        }
+    }
 }
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index e0bf26d..018572c 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -44,6 +44,12 @@
 #define DEBUG_COLOR_MERGEDBATCH      0x5f7f7fff
 #define DEBUG_COLOR_MERGEDBATCH_SOLO 0x5f7fff7f
 
+static bool avoidOverdraw() {
+    // Don't avoid overdraw when visualizing it, since that makes it harder to
+    // debug where it's coming from, and when the problem occurs.
+    return !Properties::debugOverdraw;
+};
+
 /////////////////////////////////////////////////////////////////////////////////
 // Operation Batches
 /////////////////////////////////////////////////////////////////////////////////
@@ -495,7 +501,7 @@
             && mSaveStack.empty()
             && !state->mRoundRectClipState;
 
-    if (CC_LIKELY(mAvoidOverdraw) && mBatches.size() &&
+    if (CC_LIKELY(avoidOverdraw()) && mBatches.size() &&
             state->mClipSideFlags != kClipSide_ConservativeFull &&
             deferInfo.opaqueOverBounds && state->mBounds.contains(mBounds)) {
         // avoid overdraw by resetting drawing state + discarding drawing ops
@@ -647,7 +653,7 @@
     // save and restore so that reordering doesn't affect final state
     renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
 
-    if (CC_LIKELY(mAvoidOverdraw)) {
+    if (CC_LIKELY(avoidOverdraw())) {
         for (unsigned int i = 1; i < mBatches.size(); i++) {
             if (mBatches[i] && mBatches[i]->coversBounds(mBounds)) {
                 discardDrawingBatches(i - 1);
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index 748bff6..7873fbd 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -83,8 +83,8 @@
 class DeferredDisplayList {
     friend struct DeferStateStruct; // used to give access to allocator
 public:
-    DeferredDisplayList(const Rect& bounds, bool avoidOverdraw = true) :
-            mBounds(bounds), mAvoidOverdraw(avoidOverdraw) {
+    DeferredDisplayList(const Rect& bounds)
+            : mBounds(bounds) {
         clear();
     }
     ~DeferredDisplayList() { clear(); }
@@ -152,7 +152,6 @@
 
     // layer space bounds of rendering
     Rect mBounds;
-    const bool mAvoidOverdraw;
 
     /**
      * At defer time, stores the *defer time* savecount of save/saveLayer ops that were deferred, so
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index a401ce1..0c1af5f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1431,10 +1431,7 @@
             return;
         }
 
-        // Don't avoid overdraw when visualizing, since that makes it harder to
-        // debug where it's coming from, and when the problem occurs.
-        bool avoidOverdraw = !Properties::debugOverdraw;
-        DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
+        DeferredDisplayList deferredList(mState.currentClipRect());
         DeferStateStruct deferStruct(deferredList, *this, replayFlags);
         renderNode->defer(deferStruct, 0);
 
diff --git a/media/java/android/media/Metadata.java b/media/java/android/media/Metadata.java
index 54ad60e..4b8f81e 100644
--- a/media/java/android/media/Metadata.java
+++ b/media/java/android/media/Metadata.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.util.Log;
+import android.util.MathUtils;
 
 import java.util.Calendar;
 import java.util.Collections;
@@ -332,7 +333,14 @@
             }
 
             // Skip to the next one.
-            parcel.setDataPosition(start + size);
+            try {
+                parcel.setDataPosition(MathUtils.addOrThrow(start, size));
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Invalid size: " + e.getMessage());
+                error = true;
+                break;
+            }
+
             bytesLeft -= size;
             ++recCount;
         }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index a6a45e5..ab0f666 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -20,6 +20,7 @@
 import static com.android.documentsui.DirectoryFragment.ANIM_NONE;
 import static com.android.documentsui.DirectoryFragment.ANIM_SIDE;
 import static com.android.documentsui.DirectoryFragment.ANIM_UP;
+import static com.android.documentsui.Shared.DEBUG;
 import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.app.Activity;
@@ -150,12 +151,10 @@
         fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
                 ? R.string.menu_file_size_hide : R.string.menu_file_size_show);
 
-        State state = getDisplayState();
-
-        sortSize.setVisible(state.showSize); // Only sort by size when visible
-        fileSize.setVisible(!state.showSize);
-        grid.setVisible(state.derivedMode != State.MODE_GRID);
-        list.setVisible(state.derivedMode != State.MODE_LIST);
+        sortSize.setVisible(mState.showSize); // Only sort by size when visible
+        fileSize.setVisible(!mState.showSize);
+        grid.setVisible(mState.derivedMode != State.MODE_GRID);
+        list.setVisible(mState.derivedMode != State.MODE_LIST);
         advanced.setVisible(!mState.showAdvanced);
         settings.setVisible((root.flags & Root.FLAG_HAS_SETTINGS) != 0);
 
@@ -185,12 +184,10 @@
     void onStackRestored(boolean restored, boolean external) {}
 
     void onRootPicked(RootInfo root) {
-        State state = getDisplayState();
-
         // Clear entire backstack and start in new root
-        state.stack.root = root;
-        state.stack.clear();
-        state.stackTouched = true;
+        mState.stack.root = root;
+        mState.stack.clear();
+        mState.stackTouched = true;
 
         mSearchManager.update(root);
 
@@ -289,8 +286,8 @@
     }
 
     void openDirectory(DocumentInfo doc) {
-        getDisplayState().stack.push(doc);
-        getDisplayState().stackTouched = true;
+        mState.stack.push(doc);
+        mState.stackTouched = true;
         onCurrentDirectoryChanged(ANIM_DOWN);
     }
 
@@ -367,16 +364,15 @@
     }
 
     void setDisplayAdvancedDevices(boolean display) {
-        State state = getDisplayState();
         LocalPreferences.setDisplayAdvancedDevices(this, display);
-        state.showAdvanced = state.forceAdvanced | display;
+        mState.showAdvanced = mState.forceAdvanced | display;
         RootsFragment.get(getFragmentManager()).onDisplayStateChanged();
         invalidateOptionsMenu();
     }
 
     void setDisplayFileSize(boolean display) {
         LocalPreferences.setDisplayFileSize(this, display);
-        getDisplayState().showSize = display;
+        mState.showSize = display;
         DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
         invalidateOptionsMenu();
     }
@@ -389,7 +385,7 @@
      * Set state sort order based on explicit user action.
      */
     void setUserSortOrder(int sortOrder) {
-        getDisplayState().userSortOrder = sortOrder;
+        mState.userSortOrder = sortOrder;
         DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
     }
 
@@ -397,7 +393,7 @@
      * Set state mode based on explicit user action.
      */
     void setUserMode(int mode) {
-        getDisplayState().userMode = mode;
+        mState.userMode = mode;
         DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
     }
 
@@ -411,7 +407,7 @@
     @Override
     protected void onSaveInstanceState(Bundle state) {
         super.onSaveInstanceState(state);
-        state.putParcelable(EXTRA_STATE, getDisplayState());
+        state.putParcelable(EXTRA_STATE, mState);
     }
 
     @Override
@@ -420,16 +416,15 @@
     }
 
     RootInfo getCurrentRoot() {
-        State state = getDisplayState();
-        if (state.stack.root != null) {
-            return state.stack.root;
+        if (mState.stack.root != null) {
+            return mState.stack.root;
         } else {
             return mRoots.getRecentsRoot();
         }
     }
 
     public DocumentInfo getCurrentDirectory() {
-        return getDisplayState().stack.peek();
+        return mState.stack.peek();
     }
 
     public Executor getExecutorForCurrentDirectory() {
@@ -470,9 +465,8 @@
             // Update the restored stack to ensure we have freshest data
             stack.updateDocuments(getContentResolver());
 
-            State state = getDisplayState();
-            state.stack = stack;
-            state.stackTouched = true;
+            mState.stack = stack;
+            mState.stackTouched = true;
             onCurrentDirectoryChanged(ANIM_SIDE);
 
         } catch (FileNotFoundException e) {
@@ -502,9 +496,8 @@
         @Override
         protected void onPostExecute(DocumentInfo result) {
             if (result != null) {
-                State state = getDisplayState();
-                state.stack.push(result);
-                state.stackTouched = true;
+                mState.stack.push(result);
+                mState.stackTouched = true;
                 onCurrentDirectoryChanged(ANIM_SIDE);
             }
         }
@@ -516,7 +509,9 @@
 
         @Override
         protected Void doInBackground(Void... params) {
-            State state = getDisplayState();
+            if (DEBUG && !mState.stack.isEmpty()) {
+                Log.w(mTag, "Overwriting existing stack.");
+            }
             RootsCache roots = DocumentsApplication.getRootsCache(BaseActivity.this);
 
             // Restore last stack for calling package
@@ -528,7 +523,7 @@
                     mExternal = cursor.getInt(cursor.getColumnIndex(ResumeColumns.EXTERNAL)) != 0;
                     final byte[] rawStack = cursor.getBlob(
                             cursor.getColumnIndex(ResumeColumns.STACK));
-                    DurableUtils.readFromArray(rawStack, state.stack);
+                    DurableUtils.readFromArray(rawStack, mState.stack);
                     mRestoredStack = true;
                 }
             } catch (IOException e) {
@@ -539,13 +534,13 @@
 
             if (mRestoredStack) {
                 // Update the restored stack to ensure we have freshest data
-                final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(state);
+                final Collection<RootInfo> matchingRoots = roots.getMatchingRootsBlocking(mState);
                 try {
-                    state.stack.updateRoot(matchingRoots);
-                    state.stack.updateDocuments(getContentResolver());
+                    mState.stack.updateRoot(matchingRoots);
+                    mState.stack.updateDocuments(getContentResolver());
                 } catch (FileNotFoundException e) {
                     Log.w(mTag, "Failed to restore stack: " + e);
-                    state.stack.reset();
+                    mState.stack.reset();
                     mRestoredStack = false;
                 }
             }
@@ -556,7 +551,7 @@
         @Override
         protected void onPostExecute(Void result) {
             if (isDestroyed()) return;
-            getDisplayState().restored = true;
+            mState.restored = true;
             onCurrentDirectoryChanged(ANIM_NONE);
             onStackRestored(mRestoredStack, mExternal);
         }
@@ -600,10 +595,9 @@
                 return;
             }
 
-            State state = getDisplayState();
-            while (state.stack.size() > position + 1) {
-                state.stackTouched = true;
-                state.stack.pop();
+            while (mState.stack.size() > position + 1) {
+                mState.stackTouched = true;
+                mState.stack.pop();
             }
             onCurrentDirectoryChanged(ANIM_UP);
         }
@@ -620,13 +614,12 @@
     final class StackAdapter extends BaseAdapter {
         @Override
         public int getCount() {
-            return getDisplayState().stack.size();
+            return mState.stack.size();
         }
 
         @Override
         public DocumentInfo getItem(int position) {
-            State state = getDisplayState();
-            return state.stack.get(state.stack.size() - position - 1);
+            return mState.stack.get(mState.stack.size() - position - 1);
         }
 
         @Override
@@ -714,13 +707,12 @@
                 return;
             }
 
-            State state = getDisplayState();
-            if (state.currentSearch != null) {
+            if (mState.currentSearch != null) {
                 mMenu.expandActionView();
 
                 mView.setIconified(false);
                 mView.clearFocus();
-                mView.setQuery(state.currentSearch, false);
+                mView.setQuery(mState.currentSearch, false);
             } else {
                 mView.clearFocus();
                 if (!mView.isIconified()) {
@@ -746,7 +738,7 @@
 
             mMenu.setVisible(visible);
             if (!visible) {
-                getDisplayState().currentSearch = null;
+                mState.currentSearch = null;
             }
         }
 
@@ -764,7 +756,7 @@
         }
 
         boolean isSearching() {
-            return getDisplayState().currentSearch != null;
+            return mState.currentSearch != null;
         }
 
         boolean isExpanded() {
@@ -779,7 +771,7 @@
                 return false;
             }
 
-            getDisplayState().currentSearch = null;
+            mState.currentSearch = null;
             onCurrentDirectoryChanged(ANIM_NONE);
             return false;
         }
@@ -798,7 +790,7 @@
                 mIgnoreNextCollapse = false;
                 return true;
             }
-            getDisplayState().currentSearch = null;
+            mState.currentSearch = null;
             onCurrentDirectoryChanged(ANIM_NONE);
             return true;
         }
@@ -806,7 +798,7 @@
         @Override
         public boolean onQueryTextSubmit(String query) {
             mSearchExpanded = true;
-            getDisplayState().currentSearch = query;
+            mState.currentSearch = query;
             mView.clearFocus();
             onCurrentDirectoryChanged(ANIM_NONE);
             return true;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 815fbfe..362052c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -62,7 +62,6 @@
 
     private static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL";
     public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
-    public static final String EXTRA_STACK = "com.android.documentsui.STACK";
     public static final String EXTRA_FAILURE = "com.android.documentsui.FAILURE";
     public static final String EXTRA_TRANSFER_MODE = "com.android.documentsui.TRANSFER_MODE";
 
@@ -115,7 +114,7 @@
         final Intent copyIntent = new Intent(activity, CopyService.class);
         copyIntent.putParcelableArrayListExtra(
                 EXTRA_SRC_LIST, new ArrayList<DocumentInfo>(srcDocs));
-        copyIntent.putExtra(EXTRA_STACK, (Parcelable) dstStack);
+        copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) dstStack);
         copyIntent.putExtra(EXTRA_TRANSFER_MODE, mode);
 
         int toastMessage = (mode == TRANSFER_MODE_COPY) ? R.plurals.copy_begin
@@ -142,7 +141,7 @@
         }
 
         final ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
-        final DocumentStack stack = intent.getParcelableExtra(EXTRA_STACK);
+        final DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
         // Copy by default.
         final int transferMode = intent.getIntExtra(EXTRA_TRANSFER_MODE, TRANSFER_MODE_COPY);
 
@@ -173,7 +172,7 @@
                 Log.e(TAG, mFailedFiles.size() + " files failed to copy");
                 final Context context = getApplicationContext();
                 final Intent navigateIntent = new Intent(context, FilesActivity.class);
-                navigateIntent.putExtra(EXTRA_STACK, (Parcelable) stack);
+                navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
                 navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY);
                 navigateIntent.putExtra(EXTRA_TRANSFER_MODE, transferMode);
                 navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles);
@@ -221,7 +220,7 @@
 
         final Context context = getApplicationContext();
         final Intent navigateIntent = new Intent(context, FilesActivity.class);
-        navigateIntent.putExtra(EXTRA_STACK, (Parcelable) stack);
+        navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
 
         final String contentTitle = getString(copying ? R.string.copy_notification_title
                 : R.string.move_notification_title);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 3c6be6e..b3ce103 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -423,7 +423,7 @@
         }
 
         CopyService.start(getActivity(), getDisplayState(this).selectedDocumentsForCopy,
-                (DocumentStack) data.getParcelableExtra(CopyService.EXTRA_STACK),
+                (DocumentStack) data.getParcelableExtra(Shared.EXTRA_STACK),
                 data.getIntExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_NONE));
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index f6ded4b..ced03cc 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -510,7 +510,7 @@
         } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
             // Picking a copy destination is only used internally by us, so we
             // don't need to extend permissions to the caller.
-            intent.putExtra(CopyService.EXTRA_STACK, (Parcelable) mState.stack);
+            intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
             intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mState.transferMode);
         } else {
             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
index ea0c18a..120f610 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FailureDialogFragment.java
@@ -66,7 +66,7 @@
         if (whichButton == DialogInterface.BUTTON_POSITIVE) {
             CopyService.start(getActivity(), mFailedSrcList,
                     (DocumentStack) getActivity().getIntent().getParcelableExtra(
-                            CopyService.EXTRA_STACK),
+                            Shared.EXTRA_STACK),
                             mTransferMode);
         }
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index df803fb..f6a5131 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -30,6 +30,8 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Parcelable;
+import android.provider.DocumentsContract;
 import android.support.annotation.Nullable;
 import android.support.design.widget.Snackbar;
 import android.util.Log;
@@ -85,32 +87,49 @@
         mDrawer = DrawerController.create(this);
 
         RootsFragment.show(getFragmentManager(), null);
-        if (!mState.restored) {
-            Intent intent = getIntent();
-            Uri rootUri = intent.getData();
 
-            // If we've got a specific root to display, restore that root using a dedicated
-            // authority. That way a misbehaving provider won't result in an ANR.
-            if (rootUri != null && !LauncherActivity.isLaunchUri(rootUri)) {
-                new RestoreRootTask(rootUri).executeOnExecutor(
-                        ProviderExecutor.forAuthority(rootUri.getAuthority()));
+        if (mState.restored) {
+            onCurrentDirectoryChanged(ANIM_NONE);
+        } else {
+            Intent intent = getIntent();
+            Uri uri = intent.getData();
+
+            // If a non-empty stack is present in our state it was read (presumably)
+            // from EXTRA_STACK intent extra. In this case, we'll skip other means of
+            // loading or restoring the stack.
+            if (!mState.stack.isEmpty()) {
+                // When restoring from a stack, if a URI is present, it should only ever
+                // be a launch URI. Launch URIs support sensible activity management, but
+                // don't specify an real content target.
+                if (uri != null) {
+                    checkState(LauncherActivity.isLaunchUri(uri));
+                }
+                onCurrentDirectoryChanged(ANIM_NONE);
+            } else if (DocumentsContract.isRootUri(this, uri)) {
+                // If we've got a specific root to display, restore that root using a dedicated
+                // authority. That way a misbehaving provider won't result in an ANR.
+                new RestoreRootTask(uri).executeOnExecutor(
+                        ProviderExecutor.forAuthority(uri.getAuthority()));
             } else {
+                // Finally, we try to restore a stack from recents.
                 new RestoreStackTask().execute();
             }
 
+            // TODO: Ensure we're handling CopyService errors correctly across all activities.
             // Show a failure dialog if there was a failed operation.
-            final DocumentStack dstStack = intent.getParcelableExtra(CopyService.EXTRA_STACK);
             final int failure = intent.getIntExtra(CopyService.EXTRA_FAILURE, 0);
             final int transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE,
                     CopyService.TRANSFER_MODE_NONE);
             if (failure != 0) {
                 final ArrayList<DocumentInfo> failedSrcList =
                         intent.getParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST);
-                FailureDialogFragment.show(getFragmentManager(), failure, failedSrcList, dstStack,
+                FailureDialogFragment.show(
+                        getFragmentManager(),
+                        failure,
+                        failedSrcList,
+                        mState.stack,
                         transferMode);
             }
-        } else {
-            onCurrentDirectoryChanged(ANIM_NONE);
         }
     }
 
@@ -126,7 +145,7 @@
         // Options specific to the DocumentsActivity.
         checkArgument(!intent.hasExtra(Intent.EXTRA_LOCAL_ONLY));
 
-        final DocumentStack stack = intent.getParcelableExtra(CopyService.EXTRA_STACK);
+        final DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
         if (stack != null) {
             state.stack = stack;
         }
@@ -240,7 +259,7 @@
                 showCreateDirectoryDialog();
                 return true;
             case R.id.menu_new_window:
-                startActivity(LauncherActivity.createLaunchIntent(this));
+                createNewWindow();
                 return true;
             case R.id.menu_paste_from_clipboard:
                 DirectoryFragment dir = DirectoryFragment.get(getFragmentManager());
@@ -252,6 +271,12 @@
         return super.onOptionsItemSelected(item);
     }
 
+    private void createNewWindow() {
+        Intent intent = LauncherActivity.createLaunchIntent(this);
+        intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
+        startActivity(intent);
+    }
+
     @Override
     void onDirectoryChanged(int anim) {
         final FragmentManager fm = getFragmentManager();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
index 29bcd24..e06d6a5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
@@ -23,12 +23,11 @@
 import android.support.design.widget.Snackbar;
 import android.view.View;
 
-/**
- * @hide
- */
+/** @hide */
 public final class Shared {
     public static final boolean DEBUG = true;
     public static final String TAG = "Documents";
+    public static final String EXTRA_STACK = "com.android.documentsui.STACK";
 
     /**
      * Generates a formatted quantity string.
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
index 568e9e4..fc42c3b 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyTest.java
@@ -425,7 +425,7 @@
         stack.push(DocumentInfo.fromUri(mResolver, dst));
         final Intent copyIntent = new Intent(mContext, CopyService.class);
         copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs);
-        copyIntent.putExtra(CopyService.EXTRA_STACK, (Parcelable) stack);
+        copyIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
 
         // startService(copyIntent);
         return copyIntent;
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 47e24e8..368f9f7 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -13,3 +13,11 @@
 -keep class com.android.systemui.statusbar.phone.PhoneStatusBar
 -keep class com.android.systemui.statusbar.tv.TvStatusBar
 -keep class com.android.systemui.recents.*
+
+-keepclassmembers class ** {
+    public void onBusEvent(**);
+    public void onInterprocessBusEvent(**);
+}
+-keepclassmembers class ** extends **.EventBus$InterprocessEvent {
+	public <init>(android.os.Bundle);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 23cc8f0..a78351a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -74,6 +74,8 @@
 public class Recents extends SystemUI
         implements ActivityOptions.OnAnimationStartedListener, RecentsComponent {
 
+    public final static int EVENT_BUS_PRIORITY = 1;
+
     final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab";
     final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey";
     final public static String EXTRA_RECENTS_VISIBILITY = "recentsVisibility";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 9ce6b2c..1a0eb24 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -33,9 +33,12 @@
 import android.view.ViewStub;
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
 import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsPackageMonitor;
 import com.android.systemui.recents.model.RecentsTaskLoadPlan;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
@@ -44,16 +47,17 @@
 import com.android.systemui.recents.views.SystemBarScrimViews;
 import com.android.systemui.recents.views.ViewAnimation;
 
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
 /**
  * The main Recents activity that is started from AlternateRecentsComponent.
  */
-public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
-        RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks {
+public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks {
+
+    public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
 
     RecentsConfiguration mConfig;
+    RecentsPackageMonitor mPackageMonitor;
     long mLastTabKeyEventTime;
 
     // Top level views
@@ -318,12 +322,17 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        // Register this activity with the event bus
+        EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
+
         // For the non-primary user, ensure that the SystemServicesProxy and configuration is
         // initialized
         RecentsTaskLoader.initialize(this);
         SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
         mConfig = RecentsConfiguration.initialize(this, ssp);
         mConfig.update(this, ssp, ssp.getWindowRect());
+        mPackageMonitor = new RecentsPackageMonitor();
 
         // Initialize the widget host (the host id is static and does not change)
         mAppWidgetHost = new RecentsAppWidgetHost(this, Constants.Values.App.AppWidgetHostId);
@@ -371,7 +380,7 @@
         registerReceiver(mServiceBroadcastReceiver, filter);
 
         // Register any broadcast receivers for the task loader
-        loader.registerReceivers(this, mRecentsView);
+        mPackageMonitor.register(this);
 
         // Update the recent tasks
         updateRecentsTasks();
@@ -415,7 +424,7 @@
         unregisterReceiver(mServiceBroadcastReceiver);
 
         // Unregister any broadcast receivers for the task loader
-        loader.unregisterReceivers();
+        mPackageMonitor.unregister();
 
         // Workaround for b/22542869, if the RecentsActivity is started again, but without going
         // through SystemUI, we need to reset the config launch flags to ensure that we do not
@@ -437,6 +446,7 @@
 
         // Stop listening for widget package changes if there was one bound
         mAppWidgetHost.stopListening();
+        EventBus.getDefault().unregister(this);
     }
 
     public void onEnterAnimationTriggered() {
@@ -446,16 +456,12 @@
         mRecentsView.startEnterRecentsAnimation(ctx);
 
         if (mSearchWidgetInfo != null) {
-            final WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks> cbRef =
-                    new WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks>(
-                            RecentsActivity.this);
             ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
                 @Override
                 public void run() {
                     // Start listening for widget package changes if there is one bound
-                    RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks cb = cbRef.get();
-                    if (cb != null) {
-                        mAppWidgetHost.startListening(cb);
+                    if (mAppWidgetHost != null) {
+                        mAppWidgetHost.startListening();
                     }
                 }
             });
@@ -572,10 +578,13 @@
         mAfterPauseRunnable = r;
     }
 
-    /**** RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks Implementation ****/
+    /**** EventBus events ****/
 
-    @Override
-    public void refreshSearchWidgetView() {
+    public final void onBusEvent(AppWidgetProviderChangedEvent event) {
+        refreshSearchWidgetView();
+    }
+
+    private void refreshSearchWidgetView() {
         if (mSearchWidgetInfo != null) {
             SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
             int searchWidgetId = ssp.getSearchAppWidgetId(this);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
index 0102332..fc96c11 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsAppWidgetHost.java
@@ -20,24 +20,19 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
 
 /** Our special app widget host for the Search widget */
 public class RecentsAppWidgetHost extends AppWidgetHost {
 
-    /* Callbacks to notify when an app package changes */
-    interface RecentsAppWidgetHostCallbacks {
-        void refreshSearchWidgetView();
-    }
-
-    RecentsAppWidgetHostCallbacks mCb;
     boolean mIsListening;
 
     public RecentsAppWidgetHost(Context context, int hostId) {
         super(context, hostId);
     }
 
-    public void startListening(RecentsAppWidgetHostCallbacks cb) {
-        mCb = cb;
+    public void startListening() {
         if (!mIsListening) {
             mIsListening = true;
             super.startListening();
@@ -47,11 +42,9 @@
     @Override
     public void stopListening() {
         if (mIsListening) {
+            mIsListening = false;
             super.stopListening();
         }
-        // Ensure that we release any references to the callbacks
-        mCb = null;
-        mIsListening = false;
     }
 
     @Override
@@ -66,8 +59,8 @@
     @Override
     protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidgetInfo) {
         super.onProviderChanged(appWidgetId, appWidgetInfo);
-        if (mIsListening && mCb != null) {
-            mCb.refreshSearchWidgetView();
+        if (mIsListening) {
+            EventBus.getDefault().send(new AppWidgetProviderChangedEvent());
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
new file mode 100644
index 0000000..4addfa5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -0,0 +1,844 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.MutableBoolean;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Represents a subscriber, which implements various event bus handler methods.
+ */
+class Subscriber {
+    private WeakReference<Object> mSubscriber;
+
+    long registrationTime;
+
+    Subscriber(Object subscriber, long registrationTime) {
+        mSubscriber = new WeakReference<>(subscriber);
+        this.registrationTime = registrationTime;
+    }
+
+    public String toString(int priority) {
+        Object sub = mSubscriber.get();
+        String id = Integer.toHexString(System.identityHashCode(sub));
+        return sub.getClass().getSimpleName() + " [0x" + id + ", P" + priority + "]";
+    }
+
+    public Object getReference() {
+        return mSubscriber.get();
+    }
+}
+
+/**
+ * Represents an event handler with a priority.
+ */
+class EventHandler {
+    int priority;
+    Subscriber subscriber;
+    EventHandlerMethod method;
+
+    EventHandler(Subscriber subscriber, EventHandlerMethod method, int priority) {
+        this.subscriber = subscriber;
+        this.method = method;
+        this.priority = priority;
+    }
+
+    @Override
+    public String toString() {
+        return subscriber.toString(priority) + " " + method.toString();
+    }
+}
+
+/**
+ * Represents the low level method handling a particular event.
+ */
+class EventHandlerMethod {
+    private Method mMethod;
+    Class<? extends EventBus.Event> eventType;
+
+    EventHandlerMethod(Method method, Class<? extends EventBus.Event> eventType) {
+        mMethod = method;
+        mMethod.setAccessible(true);
+        this.eventType = eventType;
+    }
+
+    public void invoke(Object target, EventBus.Event event)
+            throws InvocationTargetException, IllegalAccessException {
+        mMethod.invoke(target, event);
+    }
+
+    @Override
+    public String toString() {
+        return mMethod.getName() + "(" + eventType.getSimpleName() + ")";
+    }
+}
+
+/**
+ * A simple in-process event bus.  It is simple because we can make assumptions about the state of
+ * SystemUI and Recent's lifecycle.
+ *
+ * <p>
+ * Currently, there is a single EventBus that handles {@link EventBus.Event}s for each subscriber
+ * on the main application thread.  Publishers can send() events to synchronously call subscribers
+ * of that event, or post() events to be processed in the next run of the {@link Looper}.  In
+ * addition, the EventBus supports sending and handling {@link EventBus.InterprocessEvent}s
+ * (within the same package) implemented using standard {@link BroadcastReceiver} mechanism.
+ * Interprocess events must be posted using postInterprocess() to ensure that it is dispatched
+ * correctly across processes.
+ *
+ * <p>
+ * Subscribers must be registered with a particular EventBus before they will receive events, and
+ * handler methods must match a specific signature.
+ *
+ * <p>
+ * Event method signature:<ul>
+ * <li>Methods must be public final
+ * <li>Methods must return void
+ * <li>Methods must be called "onBusEvent"
+ * <li>Methods must take one parameter, of class type deriving from {@link EventBus.Event}
+ * </ul>
+ *
+ * <p>
+ * Interprocess-Event method signature:<ul>
+ * <li>Methods must be public final
+ * <li>Methods must return void
+ * <li>Methods must be called "onInterprocessBusEvent"
+ * <li>Methods must take one parameter, of class type deriving from {@link EventBus.InterprocessEvent}
+ * </ul>
+ * </p>
+ *
+ * </p>
+ * Each subscriber can be registered with a given priority (default 1), and events will be dispatch
+ * in decreasing order of priority.  For subscribers with the same priority, events will be
+ * dispatched by latest registration time to earliest.
+ *
+ * <p>
+ * Interprocess events must extend {@link EventBus.InterprocessEvent}, have a constructor which
+ * takes a {@link Bundle} and implement toBundle().  This allows us to serialize events to be sent
+ * across processes.
+ *
+ * <p>
+ * Caveats:<ul>
+ * <li>The EventBus keeps a {@link WeakReference} to the publisher to prevent memory leaks, so
+ * there must be another strong reference to the publisher for it to not get garbage-collected and
+ * continue receiving events.
+ * <li>Because the event handlers are called back using reflection, the EventBus is not intended
+ * for use in tight, performance criticial loops.  For most user input/system callback events, this
+ * is generally of low enough frequency to use the EventBus.
+ * <li>Because the event handlers are called back using reflection, there will often be no
+ * references to them from actual code.  The proguard configuration will be need to be updated to
+ * keep these extra methods:
+ *
+ * -keepclassmembers class ** {
+ * public void onBusEvent(**);
+ * public void onInterprocessBusEvent(**);
+ * }
+ * -keepclassmembers class ** extends **.EventBus$InterprocessEvent {
+ * public <init>(android.os.Bundle);
+ * }
+ *
+ * <li>Subscriber registration can be expensive depending on the subscriber's {@link Class}.  This
+ * is only done once per class type, but if possible, it is best to pre-register an instance of
+ * that class beforehand or when idle.
+ * <li>Each event should be sent once.  Events may hold internal information about the current
+ * dispatch, or may be queued to be dispatched on another thread (if posted from a non-main thread),
+ * so it may be unsafe to edit, change, or re-send the event again.
+ * <li>Events should follow a pattern of public-final POD (plain old data) objects, where they are
+ * initialized by the constructor and read by each subscriber of that event.  Subscribers should
+ * never alter events as they are processed, and this enforces that pattern.
+ * </ul>
+ *
+ * <p>
+ * Future optimizations:
+ * <li>throw exception/log when a subscriber loses the reference
+ * <li>trace cost per registration & invocation
+ * <li>trace cross-process invocation
+ * <li>register(subscriber, Class&lt;?&gt;...) -- pass in exact class types you want registered
+ * <li>setSubscriberEventHandlerPriority(subscriber, Class<Event>, priority)
+ * <li>allow subscribers to implement interface, ie. EventBus.Subscriber, which lets then test a
+ * message before invocation (ie. check if task id == this task id)
+ * <li>add postOnce() which automatically debounces
+ * <li>add postDelayed() which delays / postDelayedOnce() which delays and bounces
+ * <li>consolidate register() and registerInterprocess()
+ * <li>sendForResult&lt;ReturnType&gt;(Event) to send and get a result, but who will send the
+ * result?
+ * </p>
+ */
+public class EventBus extends BroadcastReceiver {
+
+    public static final String TAG = "EventBus";
+
+    /**
+     * An event super class that allows us to track internal event state across subscriber
+     * invocations.
+     *
+     * Events should not be edited by subscribers.
+     */
+    public static class Event {
+        // Indicates that this event's dispatch should be traced and logged to logcat
+        boolean trace;
+        // Indicates that this event must be posted on the EventBus's looper thread before invocation
+        boolean requiresPost;
+        // Not currently exposed, allows a subscriber to cancel further dispatch of this event
+        boolean cancelled;
+
+        // Only accessible from derived events
+        protected Event() {}
+    }
+
+    /**
+     * An inter-process event super class that allows us to track user state across subscriber
+     * invocations.
+     */
+    public static class InterprocessEvent extends Event {
+        private static final String EXTRA_USER = "_user";
+
+        // The user which this event originated from
+        public final int user;
+
+        // Only accessible from derived events
+        protected InterprocessEvent(int user) {
+            this.user = user;
+        }
+
+        /**
+         * Called from the event bus
+         */
+        protected InterprocessEvent(Bundle b) {
+            user = b.getInt(EXTRA_USER);
+        }
+
+        protected Bundle toBundle() {
+            Bundle b = new Bundle();
+            b.putInt(EXTRA_USER, user);
+            return b;
+        }
+    }
+
+    /**
+     * Proguard must also know, and keep, all methods matching this signature.
+     *
+     * -keepclassmembers class ** {
+     *     public void onBusEvent(**);
+     *     public void onInterprocessBusEvent(**);
+     * }
+     */
+    private static final String METHOD_PREFIX = "onBusEvent";
+    private static final String INTERPROCESS_METHOD_PREFIX = "onInterprocessBusEvent";
+
+    // Ensures that interprocess events can only be sent from a process holding this permission. */
+    private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+
+    // Used for passing event data across process boundaries
+    private static final String EXTRA_INTERPROCESS_EVENT_BUNDLE = "interprocess_event_bundle";
+
+    // The default priority of all subscribers
+    private static final int DEFAULT_SUBSCRIBER_PRIORITY = 1;
+
+    // Used for debugging everything
+    private static final boolean DEBUG_TRACE_ALL = false;
+
+    // Orders the handlers by priority and registration time
+    private static final Comparator<EventHandler> EVENT_HANDLER_COMPARATOR = new Comparator<EventHandler>() {
+        @Override
+        public int compare(EventHandler h1, EventHandler h2) {
+            // Rank the handlers by priority descending, followed by registration time descending.
+            // aka. the later registered
+            if (h1.priority != h2.priority) {
+                return h2.priority - h1.priority;
+            } else {
+                return Long.compare(h2.subscriber.registrationTime, h1.subscriber.registrationTime);
+            }
+        }
+    };
+
+    // Used for initializing the default bus
+    private static final Object sLock = new Object();
+    private static EventBus sDefaultBus;
+
+    // The handler to post all events
+    private Handler mHandler;
+
+    // Keep track of whether we have registered a broadcast receiver already, so that we can
+    // unregister ourselves before re-registering again with a new IntentFilter.
+    private boolean mHasRegisteredReceiver;
+
+    /**
+     * Map from event class -> event handler list.  Keeps track of the actual mapping from event
+     * to subscriber method.
+     */
+    private HashMap<Class<? extends Event>, ArrayList<EventHandler>> mEventTypeMap = new HashMap<>();
+
+    /**
+     * Map from subscriber class -> event handler method lists.  Used to determine upon registration
+     * of a new subscriber whether we need to read all the subscriber's methods again using
+     * reflection or whether we can just add the subscriber to the event type map.
+     */
+    private HashMap<Class<? extends Object>, ArrayList<EventHandlerMethod>> mSubscriberTypeMap = new HashMap<>();
+
+    /**
+     * Map from interprocess event name -> interprocess event class.  Used for mapping the event
+     * name after receiving the broadcast, to the event type.  After which a new instance is created
+     * and posted in the local process.
+     */
+    private HashMap<String, Class<? extends InterprocessEvent>> mInterprocessEventNameMap = new HashMap<>();
+
+    /**
+     * Set of all currently registered subscribers
+     */
+    private ArrayList<Subscriber> mSubscribers = new ArrayList<>();
+
+    // For tracing
+    private int mCallCount;
+    private long mCallDurationMicros;
+
+    /**
+     * Private constructor to create an event bus for a given looper.
+     */
+    private EventBus(Looper looper) {
+        mHandler = new Handler(looper);
+    }
+
+    /**
+     * @return the default event bus for the application's main thread.
+     */
+    public static EventBus getDefault() {
+        if (sDefaultBus == null)
+        synchronized (sLock) {
+            if (sDefaultBus == null) {
+                if (DEBUG_TRACE_ALL) {
+                    logWithPid("New EventBus");
+                }
+                sDefaultBus = new EventBus(Looper.getMainLooper());
+            }
+        }
+        return sDefaultBus;
+    }
+
+    /**
+     * Registers a subscriber to receive events with the default priority.
+     *
+     * @param subscriber the subscriber to handle events.  If this is the first instance of the
+     *                   subscriber's class type that has been registered, the class's methods will
+     *                   be scanned for appropriate event handler methods.
+     */
+    public void register(Object subscriber) {
+        registerSubscriber(subscriber, DEFAULT_SUBSCRIBER_PRIORITY, null);
+    }
+
+    /**
+     * Registers a subscriber to receive events with the given priority.
+     *
+     * @param subscriber the subscriber to handle events.  If this is the first instance of the
+     *                   subscriber's class type that has been registered, the class's methods will
+     *                   be scanned for appropriate event handler methods.
+     * @param priority the priority that this subscriber will receive events relative to other
+     *                 subscribers
+     */
+    public void register(Object subscriber, int priority) {
+        registerSubscriber(subscriber, priority, null);
+    }
+
+    /**
+     * Explicitly registers a subscriber to receive interprocess events with the default priority.
+     *
+     * @param subscriber the subscriber to handle events.  If this is the first instance of the
+     *                   subscriber's class type that has been registered, the class's methods will
+     *                   be scanned for appropriate event handler methods.
+     */
+    public void registerInterprocessAsCurrentUser(Context context, Object subscriber) {
+        registerInterprocessAsCurrentUser(context, subscriber, DEFAULT_SUBSCRIBER_PRIORITY);
+    }
+
+    /**
+     * Registers a subscriber to receive interprocess events with the given priority.
+     *
+     * @param subscriber the subscriber to handle events.  If this is the first instance of the
+     *                   subscriber's class type that has been registered, the class's methods will
+     *                   be scanned for appropriate event handler methods.
+     * @param priority the priority that this subscriber will receive events relative to other
+     *                 subscribers
+     */
+    public void registerInterprocessAsCurrentUser(Context context, Object subscriber, int priority) {
+        if (DEBUG_TRACE_ALL) {
+            logWithPid("registerInterprocessAsCurrentUser(" + subscriber.getClass().getSimpleName() + ")");
+        }
+
+        // Register the subscriber normally, and update the broadcast receiver filter if this is
+        // a new subscriber type with interprocess events
+        MutableBoolean hasInterprocessEventsChanged = new MutableBoolean(false);
+        registerSubscriber(subscriber, priority, hasInterprocessEventsChanged);
+        if (DEBUG_TRACE_ALL) {
+            logWithPid("hasInterprocessEventsChanged: " + hasInterprocessEventsChanged.value);
+        }
+        if (hasInterprocessEventsChanged.value) {
+            registerReceiverForInterprocessEvents(context);
+        }
+    }
+
+    /**
+     * Remove all EventHandlers pointing to the specified subscriber.  This does not remove the
+     * mapping of subscriber type to event handler method, in case new instances of this subscriber
+     * are registered.
+     */
+    public void unregister(Object subscriber) {
+        if (DEBUG_TRACE_ALL) {
+            logWithPid("unregister()");
+        }
+
+        // Fail immediately if we are being called from the non-main thread
+        long callingThreadId = Thread.currentThread().getId();
+        if (callingThreadId != mHandler.getLooper().getThread().getId()) {
+            throw new RuntimeException("Can not unregister() a subscriber from a non-main thread.");
+        }
+
+        // Return early if this is not a registered subscriber
+        if (!findRegisteredSubscriber(subscriber, true /* removeFoundSubscriber */)) {
+            return;
+        }
+
+        Class<?> subscriberType = subscriber.getClass();
+        ArrayList<EventHandlerMethod> subscriberMethods = mSubscriberTypeMap.get(subscriberType);
+        if (subscriberMethods != null) {
+            // For each of the event handlers the subscriber handles, remove all references of that
+            // handler
+            for (EventHandlerMethod method : subscriberMethods) {
+                ArrayList<EventHandler> eventHandlers = mEventTypeMap.get(method.eventType);
+                for (int i = eventHandlers.size() - 1; i >= 0; i--) {
+                    if (eventHandlers.get(i).subscriber.getReference() == subscriber) {
+                        eventHandlers.remove(i);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Explicit unregistration for interprocess event subscribers.  This actually behaves exactly
+     * the same as unregister() since we also do not want to stop listening for specific
+     * inter-process messages in case new instances of that subscriber is registered.
+     */
+    public void unregisterInterprocess(Context context, Object subscriber) {
+        if (DEBUG_TRACE_ALL) {
+            logWithPid("unregisterInterprocess()");
+        }
+        unregister(subscriber);
+    }
+
+    /**
+     * Sends an event to the subscribers of the given event type immediately.  This can only be
+     * called from the same thread as the EventBus's looper thread (for the default EventBus, this
+     * is the main application thread).
+     */
+    public void send(Event event) {
+        // Fail immediately if we are being called from the non-main thread
+        long callingThreadId = Thread.currentThread().getId();
+        if (callingThreadId != mHandler.getLooper().getThread().getId()) {
+            throw new RuntimeException("Can not send() a message from a non-main thread.");
+        }
+
+        if (DEBUG_TRACE_ALL) {
+            logWithPid("send(" + event.getClass().getSimpleName() + ")");
+        }
+
+        // Reset the event's cancelled state
+        event.requiresPost = false;
+        event.cancelled = false;
+        queueEvent(event);
+    }
+
+    /**
+     * Post a message to the subscribers of the given event type.  The messages will be posted on
+     * the EventBus's looper thread (for the default EventBus, this is the main application thread).
+     */
+    public void post(Event event) {
+        if (DEBUG_TRACE_ALL) {
+            logWithPid("post(" + event.getClass().getSimpleName() + ")");
+        }
+
+        // Reset the event's cancelled state
+        event.requiresPost = true;
+        event.cancelled = false;
+        queueEvent(event);
+    }
+
+    /** Prevent post()ing an InterprocessEvent */
+    @Deprecated
+    public void post(InterprocessEvent event) {
+        throw new RuntimeException("Not supported, use postInterprocess");
+    }
+
+    /** Prevent send()ing an InterprocessEvent */
+    @Deprecated
+    public void send(InterprocessEvent event) {
+        throw new RuntimeException("Not supported, use postInterprocess");
+    }
+
+    /**
+     * Posts an interprocess event.
+     */
+    public void postInterprocess(Context context, final InterprocessEvent event) {
+        if (DEBUG_TRACE_ALL) {
+            logWithPid("postInterprocess(" + event.getClass().getSimpleName() + ")");
+        }
+        String eventType = event.getClass().getName();
+        Bundle eventBundle = event.toBundle();
+        Intent intent = new Intent(eventType);
+        intent.setPackage(context.getPackageName());
+        intent.putExtra(EXTRA_INTERPROCESS_EVENT_BUNDLE, eventBundle);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
+                Intent.FLAG_RECEIVER_FOREGROUND);
+        context.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    /**
+     * Receiver for interprocess events.
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (DEBUG_TRACE_ALL) {
+            logWithPid("onReceive(" + intent.getAction() + ", user " + UserHandle.myUserId() + ")");
+        }
+
+        Bundle eventBundle = intent.getBundleExtra(EXTRA_INTERPROCESS_EVENT_BUNDLE);
+        Class<? extends InterprocessEvent> eventType = mInterprocessEventNameMap.get(intent.getAction());
+        try {
+            Constructor<? extends InterprocessEvent> ctor = eventType.getConstructor(Bundle.class);
+            send((Event) ctor.newInstance(eventBundle));
+        } catch (NoSuchMethodException|
+                InvocationTargetException|
+                InstantiationException|
+                IllegalAccessException e) {
+            Log.e(TAG, "Failed to create InterprocessEvent", e);
+        }
+    }
+
+    /**
+     * @return a dump of the current state of the EventBus
+     */
+    public String dump() {
+        StringBuilder output = new StringBuilder();
+        output.append("Registered class types:");
+        output.append("\n");
+        for (Class<?> clz : mSubscriberTypeMap.keySet()) {
+            output.append("\t");
+            output.append(clz.getSimpleName());
+            output.append("\n");
+        }
+        output.append("Event map:");
+        output.append("\n");
+        for (Class<?> clz : mEventTypeMap.keySet()) {
+            output.append("\t");
+            output.append(clz.getSimpleName());
+            output.append(" -> ");
+            output.append("\n");
+            ArrayList<EventHandler> handlers = mEventTypeMap.get(clz);
+            for (EventHandler handler : handlers) {
+                Object subscriber = handler.subscriber.getReference();
+                if (subscriber != null) {
+                    String id = Integer.toHexString(System.identityHashCode(subscriber));
+                    output.append("\t\t");
+                    output.append(subscriber.getClass().getSimpleName());
+                    output.append(" [0x" + id + ", #" + handler.priority + "]");
+                    output.append("\n");
+                }
+            }
+        }
+        return output.toString();
+    }
+
+    /**
+     * Registers a new subscriber.
+     *
+     * @return return whether or not this
+     */
+    private void registerSubscriber(Object subscriber, int priority,
+            MutableBoolean hasInterprocessEventsChangedOut) {
+        // Fail immediately if we are being called from the non-main thread
+        long callingThreadId = Thread.currentThread().getId();
+        if (callingThreadId != mHandler.getLooper().getThread().getId()) {
+            throw new RuntimeException("Can not register() a subscriber from a non-main thread.");
+        }
+
+        // Return immediately if this exact subscriber is already registered
+        if (findRegisteredSubscriber(subscriber, false /* removeFoundSubscriber */)) {
+            return;
+        }
+
+        long t1 = 0;
+        if (DEBUG_TRACE_ALL) {
+            t1 = SystemClock.currentTimeMicro();
+            logWithPid("registerSubscriber(" + subscriber.getClass().getSimpleName() + ")");
+        }
+        Subscriber sub = new Subscriber(subscriber, SystemClock.uptimeMillis());
+        Class<?> subscriberType = subscriber.getClass();
+        ArrayList<EventHandlerMethod> subscriberMethods = mSubscriberTypeMap.get(subscriberType);
+        if (subscriberMethods != null) {
+            if (DEBUG_TRACE_ALL) {
+                logWithPid("Subscriber class type already registered");
+            }
+
+            // If we've parsed this subscriber type before, just add to the set for all the known
+            // events
+            for (EventHandlerMethod method : subscriberMethods) {
+                ArrayList<EventHandler> eventTypeHandlers = mEventTypeMap.get(method.eventType);
+                eventTypeHandlers.add(new EventHandler(sub, method, priority));
+                sortEventHandlersByPriority(eventTypeHandlers);
+            }
+            mSubscribers.add(sub);
+            return;
+        } else {
+            if (DEBUG_TRACE_ALL) {
+                logWithPid("Subscriber class type requires registration");
+            }
+
+            // If we are parsing this type from scratch, ensure we add it to the subscriber type
+            // map, and pull out he handler methods below
+            subscriberMethods = new ArrayList<>();
+            mSubscriberTypeMap.put(subscriberType, subscriberMethods);
+            mSubscribers.add(sub);
+        }
+
+        // Find all the valid event bus handler methods of the subscriber
+        MutableBoolean isInterprocessEvent = new MutableBoolean(false);
+        Method[] methods = subscriberType.getMethods();
+        for (Method m : methods) {
+            Class<?>[] parameterTypes = m.getParameterTypes();
+            isInterprocessEvent.value = false;
+            if (isValidEventBusHandlerMethod(m, parameterTypes, isInterprocessEvent)) {
+                Class<? extends Event> eventType = (Class<? extends Event>) parameterTypes[0];
+                ArrayList<EventHandler> eventTypeHandlers = mEventTypeMap.get(eventType);
+                if (eventTypeHandlers == null) {
+                    eventTypeHandlers = new ArrayList<>();
+                    mEventTypeMap.put(eventType, eventTypeHandlers);
+                }
+                if (isInterprocessEvent.value) {
+                    try {
+                        // Enforce that the event must have a Bundle constructor
+                        eventType.getConstructor(Bundle.class);
+
+                        mInterprocessEventNameMap.put(eventType.getName(),
+                                (Class<? extends InterprocessEvent>) eventType);
+                        if (hasInterprocessEventsChangedOut != null) {
+                            hasInterprocessEventsChangedOut.value = true;
+                        }
+                    } catch (NoSuchMethodException e) {
+                        throw new RuntimeException("Expected InterprocessEvent to have a Bundle constructor");
+                    }
+                }
+                EventHandlerMethod method = new EventHandlerMethod(m, eventType);
+                EventHandler handler = new EventHandler(sub, method, priority);
+                eventTypeHandlers.add(handler);
+                subscriberMethods.add(method);
+                sortEventHandlersByPriority(eventTypeHandlers);
+
+                if (DEBUG_TRACE_ALL) {
+                    logWithPid("  * Method: " + m.getName() +
+                            " event: " + parameterTypes[0].getSimpleName() +
+                            " interprocess? " + isInterprocessEvent.value);
+                }
+            }
+        }
+        if (DEBUG_TRACE_ALL) {
+            logWithPid("Registered " + subscriber.getClass().getSimpleName() + " in " +
+                    (SystemClock.currentTimeMicro() - t1) + " microseconds");
+        }
+    }
+
+    /**
+     * Adds a new message.
+     */
+    private void queueEvent(final Event event) {
+        ArrayList<EventHandler> eventHandlers = mEventTypeMap.get(event.getClass());
+        if (eventHandlers == null) {
+            return;
+        }
+        // We need to clone the list in case a subscriber unregisters itself during traversal
+        eventHandlers = (ArrayList<EventHandler>) eventHandlers.clone();
+        for (final EventHandler eventHandler : eventHandlers) {
+            if (eventHandler.subscriber.getReference() != null) {
+                if (event.requiresPost) {
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            processEvent(eventHandler, event);
+                        }
+                    });
+                } else {
+                    processEvent(eventHandler, event);
+                }
+            }
+        }
+    }
+
+    /**
+     * Processes and dispatches the given event to the given event handler, on the thread of whoever
+     * calls this method.
+     */
+    private void processEvent(final EventHandler eventHandler, final Event event) {
+        // Skip if the event was already cancelled
+        if (event.cancelled) {
+            if (event.trace || DEBUG_TRACE_ALL) {
+                logWithPid("Event dispatch cancelled");
+            }
+            return;
+        }
+
+        try {
+            if (event.trace || DEBUG_TRACE_ALL) {
+                logWithPid(" -> " + eventHandler.toString());
+            }
+            Object sub = eventHandler.subscriber.getReference();
+            if (sub != null) {
+                long t1 = 0;
+                if (DEBUG_TRACE_ALL) {
+                    t1 = SystemClock.currentTimeMicro();
+                }
+                eventHandler.method.invoke(sub, event);
+                if (DEBUG_TRACE_ALL) {
+                    long duration = (SystemClock.currentTimeMicro() - t1);
+                    mCallDurationMicros += duration;
+                    mCallCount++;
+                    logWithPid(eventHandler.method.toString() + " duration: " + duration +
+                            " microseconds, avg: " + (mCallDurationMicros / mCallCount));
+                }
+            } else {
+                Log.e(TAG, "Failed to deliver event to null subscriber");
+            }
+        } catch (IllegalAccessException e) {
+            Log.e(TAG, "Failed to invoke method", e);
+        } catch (InvocationTargetException e) {
+            throw new RuntimeException(e.getCause());
+        }
+    }
+
+    /**
+     * Re-registers the broadcast receiver for any new messages that we want to listen for.
+     */
+    private void registerReceiverForInterprocessEvents(Context context) {
+        if (DEBUG_TRACE_ALL) {
+            logWithPid("registerReceiverForInterprocessEvents()");
+        }
+        // Rebuild the receiver filter with the new interprocess events
+        IntentFilter filter = new IntentFilter();
+        for (String eventName : mInterprocessEventNameMap.keySet()) {
+            filter.addAction(eventName);
+            if (DEBUG_TRACE_ALL) {
+                logWithPid("  filter: " + eventName);
+            }
+        }
+        // Re-register the receiver with the new filter
+        if (mHasRegisteredReceiver) {
+            context.unregisterReceiver(this);
+        }
+        context.registerReceiverAsUser(this, UserHandle.ALL, filter, PERMISSION_SELF, mHandler);
+        mHasRegisteredReceiver = true;
+    }
+
+    /**
+     * Returns whether this subscriber is currently registered.  If {@param removeFoundSubscriber}
+     * is true, then remove the subscriber before returning.
+     */
+    private boolean findRegisteredSubscriber(Object subscriber, boolean removeFoundSubscriber) {
+        for (int i = mSubscribers.size() - 1; i >= 0; i--) {
+            Subscriber sub = mSubscribers.get(i);
+            if (sub.getReference() == subscriber) {
+                if (removeFoundSubscriber) {
+                    mSubscribers.remove(i);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return whether {@param method} is a valid (normal or interprocess) event bus handler method
+     */
+    private boolean isValidEventBusHandlerMethod(Method method, Class<?>[] parameterTypes,
+            MutableBoolean isInterprocessEventOut) {
+        int modifiers = method.getModifiers();
+        if (Modifier.isPublic(modifiers) &&
+                Modifier.isFinal(modifiers) &&
+                method.getReturnType().equals(Void.TYPE) &&
+                parameterTypes.length == 1) {
+            if (EventBus.InterprocessEvent.class.isAssignableFrom(parameterTypes[0]) &&
+                    method.getName().startsWith(INTERPROCESS_METHOD_PREFIX)) {
+                isInterprocessEventOut.value = true;
+                return true;
+            } else if (EventBus.Event.class.isAssignableFrom(parameterTypes[0]) &&
+                            method.getName().startsWith(METHOD_PREFIX)) {
+                isInterprocessEventOut.value = false;
+                return true;
+            } else {
+                if (DEBUG_TRACE_ALL) {
+                    if (!EventBus.Event.class.isAssignableFrom(parameterTypes[0])) {
+                        logWithPid("  Expected method take an Event-based parameter: " + method.getName());
+                    } else if (!method.getName().startsWith(INTERPROCESS_METHOD_PREFIX) &&
+                            !method.getName().startsWith(METHOD_PREFIX)) {
+                        logWithPid("  Expected method start with method prefix: " + method.getName());
+                    }
+                }
+            }
+        } else {
+            if (DEBUG_TRACE_ALL) {
+                if (!Modifier.isPublic(modifiers)) {
+                    logWithPid("  Expected method to be public: " + method.getName());
+                } else if (!Modifier.isFinal(modifiers)) {
+                    logWithPid("  Expected method to be final: " + method.getName());
+                } else if (!method.getReturnType().equals(Void.TYPE)) {
+                    logWithPid("  Expected method to return null: " + method.getName());
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Sorts the event handlers by priority and registration time.
+     */
+    private void sortEventHandlersByPriority(List<EventHandler> eventHandlers) {
+        Collections.sort(eventHandlers, EVENT_HANDLER_COMPARATOR);
+    }
+
+    /**
+     * Helper method to log the given {@param text} with the current process and user id.
+     */
+    private static void logWithPid(String text) {
+        Log.d(TAG, "[" + android.os.Process.myPid() + ", u" + UserHandle.myUserId() + "] " + text);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppWidgetProviderChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppWidgetProviderChangedEvent.java
new file mode 100644
index 0000000..52cfe18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/AppWidgetProviderChangedEvent.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.RecentsAppWidgetHost;
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent by the {@link RecentsAppWidgetHost} whenever the search provider widget changes, and
+ * subscribers can update accordingly.
+ */
+public class AppWidgetProviderChangedEvent extends EventBus.Event {
+    // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
new file mode 100644
index 0000000..3b68574
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/PackagesChangedEvent.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.RecentsPackageMonitor;
+import com.android.systemui.recents.views.TaskStackView;
+
+/**
+ * This event is sent by {@link RecentsPackageMonitor} when a package on the the system changes.
+ * {@link TaskStackView}s listen for this event, and remove the tasks associated with the removed
+ * packages.
+ */
+public class PackagesChangedEvent extends EventBus.Event {
+
+    public final RecentsPackageMonitor monitor;
+    public final String packageName;
+    public final int userId;
+
+    public PackagesChangedEvent(RecentsPackageMonitor monitor, String packageName, int userId) {
+        this.monitor = monitor;
+        this.packageName = packageName;
+        this.userId = userId;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
index e48e5f0..8f9a293 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsPackageMonitor.java
@@ -21,6 +21,8 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import com.android.internal.content.PackageMonitor;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
 import java.util.HashSet;
@@ -31,18 +33,9 @@
  * Recents list.
  */
 public class RecentsPackageMonitor extends PackageMonitor {
-    public interface PackageCallbacks {
-        public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName,
-                                      int userId);
-    }
-
-    PackageCallbacks mCb;
-    SystemServicesProxy mSystemServicesProxy;
 
     /** Registers the broadcast receivers with the specified callbacks. */
-    public void register(Context context, PackageCallbacks cb) {
-        mSystemServicesProxy = new SystemServicesProxy(context);
-        mCb = cb;
+    public void register(Context context) {
         try {
             // We register for events from all users, but will cross-reference them with
             // packages for the current user and any profiles they have
@@ -60,17 +53,13 @@
         } catch (IllegalStateException e) {
             e.printStackTrace();
         }
-        mSystemServicesProxy = null;
-        mCb = null;
     }
 
     @Override
     public void onPackageRemoved(String packageName, int uid) {
-        if (mCb == null) return;
-
         // Notify callbacks that a package has changed
         final int eventUserId = getChangingUserId();
-        mCb.onPackagesChanged(this, packageName, eventUserId);
+        EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId));
     }
 
     @Override
@@ -81,11 +70,9 @@
 
     @Override
     public void onPackageModified(String packageName) {
-        if (mCb == null) return;
-
         // Notify callbacks that a package has changed
         final int eventUserId = getChangingUserId();
-        mCb.onPackagesChanged(this, packageName, eventUserId);
+        EventBus.getDefault().send(new PackagesChangedEvent(this, packageName, eventUserId));
     }
 
     /**
@@ -108,7 +95,8 @@
                     // If we know that the component still exists in the package, then skip
                     continue;
                 }
-                if (mSystemServicesProxy.getActivityInfo(cn, userId) != null) {
+                SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+                if (ssp.getActivityInfo(cn, userId) != null) {
                     existingComponents.add(cn);
                 } else {
                     removedComponents.add(cn);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 760382e..39bef81 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -262,8 +262,6 @@
     TaskResourceLoadQueue mLoadQueue;
     TaskResourceLoader mLoader;
 
-    RecentsPackageMonitor mPackageMonitor;
-
     int mMaxThumbnailCacheSize;
     int mMaxIconCacheSize;
     int mNumVisibleTasksLoaded;
@@ -293,7 +291,6 @@
 
         // Initialize the proxy, cache and loaders
         mSystemServicesProxy = new SystemServicesProxy(context);
-        mPackageMonitor = new RecentsPackageMonitor();
         mLoadQueue = new TaskResourceLoadQueue();
         mApplicationIconCache = new DrawableLruCache(iconCacheSize);
         mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
@@ -519,17 +516,6 @@
         mLoadQueue.clearTasks();
     }
 
-    /** Registers any broadcast receivers. */
-    public void registerReceivers(Context context, RecentsPackageMonitor.PackageCallbacks cb) {
-        // Register the broadcast receiver to handle messages related to packages being added/removed
-        mPackageMonitor.register(context, cb);
-    }
-
-    /** Unregisters any broadcast receivers. */
-    public void unregisterReceivers() {
-        mPackageMonitor.unregister();
-    }
-
     /**
      * Handles signals from the system, trimming memory when requested to prevent us from running
      * out of memory.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 73c9be9..68faccc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -59,8 +59,7 @@
  * This view is the the top level layout that contains TaskStacks (which are laid out according
  * to their SpaceNode bounds.
  */
-public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks,
-        RecentsPackageMonitor.PackageCallbacks {
+public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks {
 
     private static final String TAG = "RecentsView";
 
@@ -732,14 +731,4 @@
             mCb.onTaskResize(t);
         }
     }
-
-    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
-
-    @Override
-    public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
-        // Propagate this event down to each task stack view
-        if (mTaskStackView != null) {
-            mTaskStackView.onPackagesChanged(monitor, packageName, userId);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index b5f29a0..76b091c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -33,11 +33,13 @@
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsActivityLaunchState;
+import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.PackagesChangedEvent;
 import com.android.systemui.recents.misc.DozeTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsPackageMonitor;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
@@ -54,7 +56,7 @@
 /* The visual representation of a task stack view */
 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
         TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks,
-        ViewPool.ViewPoolConsumer<TaskView, Task>, RecentsPackageMonitor.PackageCallbacks {
+        ViewPool.ViewPoolConsumer<TaskView, Task> {
 
     /** The TaskView callbacks */
     interface TaskStackViewCallbacks {
@@ -77,7 +79,7 @@
     TaskStackViewTouchHandler mTouchHandler;
     TaskStackViewCallbacks mCb;
     ViewPool<TaskView, Task> mViewPool;
-    ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
+    ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
     DozeTrigger mUIDozeTrigger;
     DismissView mDismissAllButton;
     boolean mDismissAllButtonAnimating;
@@ -97,9 +99,9 @@
     Matrix mTmpMatrix = new Matrix();
     Rect mTmpRect = new Rect();
     TaskViewTransform mTmpTransform = new TaskViewTransform();
-    HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>();
-    ArrayList<TaskView> mTaskViews = new ArrayList<TaskView>();
-    List<TaskView> mImmutableTaskViews = new ArrayList<TaskView>();
+    HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
+    ArrayList<TaskView> mTaskViews = new ArrayList<>();
+    List<TaskView> mImmutableTaskViews = new ArrayList<>();
     LayoutInflater mInflater;
     boolean mLayersDisabled;
 
@@ -147,6 +149,18 @@
         mCb = cb;
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+        super.onAttachedToWindow();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        EventBus.getDefault().unregister(this);
+    }
+
     /** Sets the task stack */
     void setStack(TaskStack stack) {
         // Set the new stack
@@ -1430,13 +1444,12 @@
         postInvalidateOnAnimation();
     }
 
-    /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
+    /**** EventBus Events ****/
 
-    @Override
-    public void onPackagesChanged(RecentsPackageMonitor monitor, String packageName, int userId) {
+    public final void onBusEvent(PackagesChangedEvent event) {
         // Compute which components need to be removed
-        HashSet<ComponentName> removedComponents = monitor.computeComponentsRemoved(
-                mStack.getTaskKeys(), packageName, userId);
+        HashSet<ComponentName> removedComponents = event.monitor.computeComponentsRemoved(
+                mStack.getTaskKeys(), event.packageName, event.userId);
 
         // For other tasks, just remove them directly if they no longer exist
         ArrayList<Task> tasks = mStack.getTasks();
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 9dad7a1..ab1d775 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -37,6 +37,7 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManagerNative;
 import android.app.AlertDialog;
 import android.app.AppGlobals;
@@ -280,8 +281,19 @@
     boolean mSystemReady;
 
     /**
-     * Id of the currently selected input method.
+     * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
+     * method.  This is to be synchronized with the secure settings keyed with
+     * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
+     *
+     * <p>This can be transiently {@code null} when the system is re-initializing input method
+     * settings, e.g., the system locale is just changed.</p>
+     *
+     * <p>Note that {@link #mCurId} is used to track which IME is being connected to
+     * {@link InputMethodManagerService}.</p>
+     *
+     * @see #mCurId
      */
+    @Nullable
     String mCurMethodId;
 
     /**
@@ -311,9 +323,14 @@
     EditorInfo mCurAttribute;
 
     /**
-     * The input method ID of the input method service that we are currently
+     * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
      * connected to or in the process of connecting to.
+     *
+     * <p>This can be {@code null} when no input method is connected.</p>
+     *
+     * @see #mCurMethodId
      */
+    @Nullable
     String mCurId;
 
     /**
@@ -918,7 +935,6 @@
                 || (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
             if (!updateOnlyWhenLocaleChanged) {
                 hideCurrentInputLocked(0, null);
-                mCurMethodId = null;
                 unbindCurrentMethodLocked(true, false);
             }
             if (DEBUG) {
@@ -1474,7 +1490,11 @@
         channel.dispose();
     }
 
-    void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) {
+    void unbindCurrentMethodLocked(boolean resetCurrentMethodAndClient, boolean savePosition) {
+        if (resetCurrentMethodAndClient) {
+            mCurMethodId = null;
+        }
+
         if (mVisibleBound) {
             mContext.unbindService(mVisibleConnection);
             mVisibleBound = false;
@@ -1501,9 +1521,8 @@
         mCurId = null;
         clearCurMethodLocked();
 
-        if (reportToClient && mCurClient != null) {
-            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
-                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
+        if (resetCurrentMethodAndClient) {
+            unbindCurrentClientLocked();
         }
     }
 
@@ -1857,13 +1876,11 @@
                 setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
             } catch (IllegalArgumentException e) {
                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
-                mCurMethodId = null;
                 unbindCurrentMethodLocked(true, false);
             }
             mShortcutInputMethodsAndSubtypes.clear();
         } else {
             // There is no longer an input method set, so stop any current one.
-            mCurMethodId = null;
             unbindCurrentMethodLocked(true, false);
         }
         // Here is not the perfect place to reset the switching controller. Ideally
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bd10c63..617264c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -20,8 +20,11 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
 import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.HOME_STACK_ID;
 import static android.app.ActivityManager.INVALID_STACK_ID;
+import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static com.android.internal.util.XmlUtils.readBooleanAttribute;
 import static com.android.internal.util.XmlUtils.readIntAttribute;
@@ -8685,7 +8688,32 @@
                     Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found");
                     return;
                 }
-                mStackSupervisor.resizeTaskLocked(task, bounds, resizeMode);
+                // Place the task in the right stack if it isn't there already based on
+                // the requested bounds.
+                // The stack transition logic is:
+                // - a null bounds on a freeform task moves that task to fullscreen
+                // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
+                //   that task to freeform
+                // - otherwise the task is not moved
+                // Note it's not allowed to resize a home stack task, or a docked task.
+                int stackId = task.stack.mStackId;
+                if (stackId == HOME_STACK_ID || stackId == DOCKED_STACK_ID) {
+                    throw new IllegalArgumentException("trying to resizeTask on a "
+                            + "home or docked task");
+                }
+                if (bounds == null && stackId == FREEFORM_WORKSPACE_STACK_ID) {
+                    stackId = FULLSCREEN_WORKSPACE_STACK_ID;
+                } else if (bounds != null && stackId != FREEFORM_WORKSPACE_STACK_ID ) {
+                    stackId = FREEFORM_WORKSPACE_STACK_ID;
+                }
+                boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
+                if (stackId != task.stack.mStackId) {
+                    mStackSupervisor.moveTaskToStackUncheckedLocked(
+                            task, stackId, ON_TOP, !FORCE_FOCUS, "resizeTask");
+                    preserveWindow = false;
+                }
+
+                mStackSupervisor.resizeTaskLocked(task, bounds, resizeMode, preserveWindow);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -11893,7 +11921,8 @@
             updateCurrentProfileIdsLocked();
 
             mRecentTasks.clear();
-            mRecentTasks.addAll(mTaskPersister.restoreTasksLocked());
+            mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(
+                    getUserManagerLocked().getUserIds()));
             mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
             mTaskPersister.startPersisting();
 
@@ -20644,9 +20673,6 @@
                 // Kill all the processes for the user.
                 forceStopUserLocked(userId, "finish user");
             }
-
-            // Explicitly remove the old information in mRecentTasks.
-            mRecentTasks.removeTasksForUserLocked(userId);
         }
 
         for (int i=0; i<callbacks.size(); i++) {
@@ -20665,6 +20691,10 @@
         }
     }
 
+    void onUserRemovedLocked(int userId) {
+        mRecentTasks.removeTasksForUserLocked(userId);
+    }
+
     @Override
     public UserInfo getCurrentUser() {
         if ((checkCallingPermission(INTERACT_ACROSS_USERS)
@@ -20949,6 +20979,13 @@
                 return homeActivity == null ? null : homeActivity.realActivity;
             }
         }
+
+        @Override
+        public void onUserRemoved(int userId) {
+            synchronized (ActivityManagerService.this) {
+                ActivityManagerService.this.onUserRemovedLocked(userId);
+            }
+        }
     }
 
     private final class SleepTokenImpl extends SleepToken {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 6b5f205..98b6ee6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3058,7 +3058,7 @@
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
-    void resizeTaskLocked(TaskRecord task, Rect bounds, int resizeMode) {
+    void resizeTaskLocked(TaskRecord task, Rect bounds, int resizeMode, boolean preserveWindow) {
         if (!task.mResizeable) {
             Slog.w(TAG, "resizeTask: task " + task + " not resizeable.");
             return;
@@ -3066,7 +3066,7 @@
 
         // If this is a forced resize, let it go through even if the bounds is not changing,
         // as we might need a relayout due to surface size change (to/from fullscreen).
-        final boolean forced = (resizeMode == RESIZE_MODE_FORCED);
+        final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0;
         if (task.mBounds != null && task.mBounds.equals(bounds) && !forced) {
             // Nothing to do here...
             return;
@@ -3084,22 +3084,11 @@
             return;
         }
 
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + task.taskId);
+        // Do not move the task to another stack here.
+        // This method assumes that the task is already placed in the right stack.
+        // we do not mess with that decision and we only do the resize!
 
-        // The stack of a task is determined by its size (fullscreen vs non-fullscreen).
-        // Place the task in the right stack if it isn't there already based on the requested
-        // bounds.
-        int stackId = task.stack.mStackId;
-        if (bounds == null && stackId != FULLSCREEN_WORKSPACE_STACK_ID) {
-            stackId = FULLSCREEN_WORKSPACE_STACK_ID;
-        } else if (bounds != null
-                && stackId != FREEFORM_WORKSPACE_STACK_ID && stackId != DOCKED_STACK_ID) {
-            stackId = FREEFORM_WORKSPACE_STACK_ID;
-        }
-        final boolean changedStacks = stackId != task.stack.mStackId;
-        if (changedStacks) {
-            moveTaskToStackUncheckedLocked(task, stackId, ON_TOP, !FORCE_FOCUS, "resizeTask");
-        }
+        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + task.taskId);
 
         final Configuration overrideConfig =  task.updateOverrideConfiguration(bounds);
         // This variable holds information whether the configuration didn't change in a signficant
@@ -3110,9 +3099,6 @@
             ActivityRecord r = task.topRunningActivityLocked(null);
             if (r != null) {
                 final ActivityStack stack = task.stack;
-                final boolean preserveWindow = !changedStacks &&
-                        (resizeMode == RESIZE_MODE_USER
-                        || resizeMode == RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
                 kept = stack.ensureActivityConfigurationLocked(r, 0, preserveWindow);
                 // All other activities must be made visible with their correct configuration.
                 ensureActivitiesVisibleLocked(r, 0, !PRESERVE_WINDOWS);
@@ -3204,7 +3190,7 @@
      * @param reason Reason the task is been moved.
      * @return The stack the task was moved to.
      */
-    private ActivityStack moveTaskToStackUncheckedLocked(
+    ActivityStack moveTaskToStackUncheckedLocked(
             TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {
         final ActivityRecord r = task.getTopActivity();
         final boolean wasFocused = isFrontStack(task.stack) && (topRunningActivityLocked() == r);
@@ -3261,12 +3247,15 @@
 
         // Make sure the task has the appropriate bounds/size for the stack it is in.
         if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
-            resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM);
+            resizeTaskLocked(task, stack.mBounds,
+                    RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
         } else if (stackId == FREEFORM_WORKSPACE_STACK_ID
                 && task.mBounds == null && task.mLastNonFullscreenBounds != null) {
-            resizeTaskLocked(task, task.mLastNonFullscreenBounds, RESIZE_MODE_SYSTEM);
+            resizeTaskLocked(task, task.mLastNonFullscreenBounds,
+                    RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
         } else if (stackId == DOCKED_STACK_ID) {
-            resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM);
+            resizeTaskLocked(task, stack.mBounds,
+                    RESIZE_MODE_SYSTEM, !PRESERVE_WINDOWS);
         }
 
         // The task might have already been running and its visibility needs to be synchronized with
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index aa154a7..871331b 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -35,6 +35,7 @@
 import android.util.Xml;
 import android.os.Process;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
 
@@ -330,7 +331,7 @@
         return null;
     }
 
-    ArrayList<TaskRecord> restoreTasksLocked() {
+    ArrayList<TaskRecord> restoreTasksLocked(final int [] validUserIds) {
         final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
         ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
 
@@ -362,15 +363,18 @@
                             if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
                                     task);
                             if (task != null) {
-                                task.isPersistable = true;
                                 // XXX Don't add to write queue... there is no reason to write
                                 // out the stuff we just read, if we don't write it we will
                                 // read the same thing again.
                                 //mWriteQueue.add(new TaskWriteQueueItem(task));
-                                tasks.add(task);
                                 final int taskId = task.taskId;
-                                recoveredTaskIds.add(taskId);
                                 mStackSupervisor.setNextTaskId(taskId);
+                                // Check if it's a valid user id. Don't add tasks for removed users.
+                                if (ArrayUtils.contains(validUserIds, task.userId)) {
+                                    task.isPersistable = true;
+                                    tasks.add(task);
+                                    recoveredTaskIds.add(taskId);
+                                }
                             } else {
                                 Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
                                         fileToString(taskFile));
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index e49a7e4..c4b57f1 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -501,7 +501,6 @@
     private volatile IRingtonePlayer mRingtonePlayer;
 
     private int mDeviceOrientation = Configuration.ORIENTATION_UNDEFINED;
-    private int mDeviceRotation = Surface.ROTATION_0;
 
     // Request to override default use of A2DP for media.
     private boolean mBluetoothA2dpEnabled;
@@ -545,8 +544,6 @@
     // If absolute volume is supported in AVRCP device
     private boolean mAvrcpAbsVolSupported = false;
 
-    private AudioOrientationEventListener mOrientationListener;
-
     private static Long mLastDeviceConnectMsgTime = new Long(0);
 
     private AudioManagerInternal.RingerModeDelegate mRingerModeDelegate;
@@ -669,15 +666,7 @@
         }
         mMonitorRotation = SystemProperties.getBoolean("ro.audio.monitorRotation", false);
         if (mMonitorRotation) {
-            mDeviceRotation = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
-                    .getDefaultDisplay().getRotation();
-            Log.v(TAG, "monitoring device rotation, initial=" + mDeviceRotation);
-
-            mOrientationListener = new AudioOrientationEventListener(mContext);
-            mOrientationListener.enable();
-
-            // initialize rotation in AudioSystem
-            setRotationForAudioSystem();
+            RotationHelper.init(mContext, mAudioHandler);
         }
 
         context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
@@ -805,7 +794,7 @@
             setOrientationForAudioSystem();
         }
         if (mMonitorRotation) {
-            setRotationForAudioSystem();
+            RotationHelper.updateOrientation();
         }
 
         synchronized (mBluetoothA2dpEnabledLock) {
@@ -1058,25 +1047,6 @@
         return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex();
     }
 
-    private class AudioOrientationEventListener
-            extends OrientationEventListener {
-        public AudioOrientationEventListener(Context context) {
-            super(context);
-        }
-
-        @Override
-        public void onOrientationChanged(int orientation) {
-            //Even though we're responding to phone orientation events,
-            //use display rotation so audio stays in sync with video/dialogs
-            int newRotation = ((WindowManager) mContext.getSystemService(
-                    Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
-            if (newRotation != mDeviceRotation) {
-                mDeviceRotation = newRotation;
-                setRotationForAudioSystem();
-            }
-        }
-    }
-
     ///////////////////////////////////////////////////////////////////////////
     // IPC methods
     ///////////////////////////////////////////////////////////////////////////
@@ -5066,14 +5036,13 @@
                 }
             } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
                 if (mMonitorRotation) {
-                    mOrientationListener.onOrientationChanged(0); //argument is ignored anyway
-                    mOrientationListener.enable();
+                    RotationHelper.enable();
                 }
                 AudioSystem.setParameters("screen_state=on");
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 if (mMonitorRotation) {
                     //reduce wakeups (save current) by only listening when display is on
-                    mOrientationListener.disable();
+                    RotationHelper.disable();
                 }
                 AudioSystem.setParameters("screen_state=off");
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
@@ -5322,6 +5291,7 @@
         }
     }
 
+    //TODO move to an external "orientation helper" class
     private void setOrientationForAudioSystem() {
         switch (mDeviceOrientation) {
             case Configuration.ORIENTATION_LANDSCAPE:
@@ -5345,26 +5315,6 @@
         }
     }
 
-    private void setRotationForAudioSystem() {
-        switch (mDeviceRotation) {
-            case Surface.ROTATION_0:
-                AudioSystem.setParameters("rotation=0");
-                break;
-            case Surface.ROTATION_90:
-                AudioSystem.setParameters("rotation=90");
-                break;
-            case Surface.ROTATION_180:
-                AudioSystem.setParameters("rotation=180");
-                break;
-            case Surface.ROTATION_270:
-                AudioSystem.setParameters("rotation=270");
-                break;
-            default:
-                Log.e(TAG, "Unknown device rotation");
-        }
-    }
-
-
     // Handles request to override default use of A2DP for media.
     // Must be called synchronized on mConnectedDevices
     public void setBluetoothA2dpOnInt(boolean on) {
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
new file mode 100644
index 0000000..f03e6c7
--- /dev/null
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+package com.android.server.audio;
+
+import android.content.Context;
+import android.media.AudioSystem;
+import android.os.Handler;
+import android.util.Log;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import com.android.server.policy.WindowOrientationListener;
+
+/**
+ * Class to handle device rotation events for AudioService, and forward device rotation
+ * to the audio HALs through AudioSystem.
+ *
+ * The role of this class is to monitor device orientation changes, and upon rotation,
+ * verify the UI orientation. In case of a change, send the new orientation, in increments
+ * of 90deg, through AudioSystem.
+ *
+ * Note that even though we're responding to device orientation events, we always
+ * query the display rotation so audio stays in sync with video/dialogs. This is
+ * done with .getDefaultDisplay().getRotation() from WINDOW_SERVICE.
+ */
+class RotationHelper {
+
+    private static final String TAG = "AudioService.RotationHelper";
+
+    private static AudioOrientationListener sOrientationListener;
+    private static AudioWindowOrientationListener sWindowOrientationListener;
+
+    private static final Object sRotationLock = new Object();
+    private static int sDeviceRotation = Surface.ROTATION_0; // R/W synchronized on sRotationLock
+
+    private static Context sContext;
+
+    /**
+     * post conditions:
+     * - (sWindowOrientationListener != null) xor (sOrientationListener != null)
+     * - sWindowOrientationListener xor sOrientationListener is enabled
+     * - sContext != null
+     */
+    static void init(Context context, Handler handler) {
+        if (context == null) {
+            throw new IllegalArgumentException("Invalid null context");
+        }
+        sContext = context;
+        sWindowOrientationListener = new AudioWindowOrientationListener(context, handler);
+        sWindowOrientationListener.enable();
+        if (!sWindowOrientationListener.canDetectOrientation()) {
+            // cannot use com.android.server.policy.WindowOrientationListener, revert to public
+            // orientation API
+            Log.i(TAG, "Not using WindowOrientationListener, reverting to OrientationListener");
+            sWindowOrientationListener.disable();
+            sWindowOrientationListener = null;
+            sOrientationListener = new AudioOrientationListener(context);
+            sOrientationListener.enable();
+        }
+    }
+
+    static void enable() {
+        if (sWindowOrientationListener != null) {
+            sWindowOrientationListener.enable();
+        } else {
+            sOrientationListener.enable();
+        }
+        updateOrientation();
+    }
+
+    static void disable() {
+        if (sWindowOrientationListener != null) {
+            sWindowOrientationListener.disable();
+        } else {
+            sOrientationListener.disable();
+        }
+    }
+
+    /**
+     * Query current display rotation and publish the change if any.
+     */
+    static void updateOrientation() {
+        // Even though we're responding to device orientation events,
+        // use display rotation so audio stays in sync with video/dialogs
+        int newRotation = ((WindowManager) sContext.getSystemService(
+                Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
+        synchronized(sRotationLock) {
+            if (newRotation != sDeviceRotation) {
+                sDeviceRotation = newRotation;
+                publishRotation(sDeviceRotation);
+            }
+        }
+    }
+
+    private static void publishRotation(int rotation) {
+        Log.v(TAG, "publishing device rotation =" + rotation + " (x90deg)");
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                AudioSystem.setParameters("rotation=0");
+                break;
+            case Surface.ROTATION_90:
+                AudioSystem.setParameters("rotation=90");
+                break;
+            case Surface.ROTATION_180:
+                AudioSystem.setParameters("rotation=180");
+                break;
+            case Surface.ROTATION_270:
+                AudioSystem.setParameters("rotation=270");
+                break;
+            default:
+                Log.e(TAG, "Unknown device rotation");
+        }
+    }
+
+    /**
+     * Uses android.view.OrientationEventListener
+     */
+    final static class AudioOrientationListener extends OrientationEventListener {
+        AudioOrientationListener(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onOrientationChanged(int orientation) {
+            updateOrientation();
+        }
+    }
+
+    /**
+     * Uses com.android.server.policy.WindowOrientationListener
+     */
+    final static class AudioWindowOrientationListener extends WindowOrientationListener {
+        private static RotationCheckThread sRotationCheckThread;
+
+        AudioWindowOrientationListener(Context context, Handler handler) {
+            super(context, handler);
+        }
+
+        public void onProposedRotationChanged(int rotation) {
+            updateOrientation();
+            if (sRotationCheckThread != null) {
+                sRotationCheckThread.endCheck();
+            }
+            sRotationCheckThread = new RotationCheckThread();
+            sRotationCheckThread.beginCheck();
+        }
+    }
+
+    /**
+     * When com.android.server.policy.WindowOrientationListener report an orientation change,
+     * the UI may not have rotated yet. This thread polls with gradually increasing delays
+     * the new orientation.
+     */
+    final static class RotationCheckThread extends Thread {
+        // how long to wait between each rotation check
+        private final int[] WAIT_TIMES_MS = { 10, 20, 50, 100, 100, 200, 200, 500 };
+        private int mWaitCounter;
+        private final Object mCounterLock = new Object();
+
+        RotationCheckThread() {
+            super("RotationCheck");
+        }
+
+        void beginCheck() {
+            synchronized(mCounterLock) {
+                mWaitCounter = 0;
+            }
+            try {
+                start();
+            } catch (IllegalStateException e) { }
+        }
+
+        void endCheck() {
+            synchronized(mCounterLock) {
+                mWaitCounter = WAIT_TIMES_MS.length;
+            }
+        }
+
+        public void run() {
+            int newRotation;
+            while (mWaitCounter < WAIT_TIMES_MS.length) {
+                updateOrientation();
+                int waitTimeMs;
+                synchronized(mCounterLock) {
+                    waitTimeMs = WAIT_TIMES_MS[mWaitCounter];
+                    mWaitCounter++;
+                }
+                try {
+                    sleep(waitTimeMs);
+                } catch (InterruptedException e) { }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index de106a1..6386a91 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
 import android.app.IStopUserCallback;
 import android.app.admin.DevicePolicyManager;
@@ -64,7 +65,7 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
-import com.android.server.accounts.AccountManagerService;
+import com.android.server.LocalServices;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -242,13 +243,15 @@
             synchronized (mPackagesLock) {
                 // Prune out any partially created/partially removed users.
                 ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
-                for (int i = 0; i < mUsers.size(); i++) {
+                final int userSize = mUsers.size();
+                for (int i = 0; i < userSize; i++) {
                     UserInfo ui = mUsers.valueAt(i);
                     if ((ui.partial || ui.guestToRemove) && i != 0) {
                         partials.add(ui);
                     }
                 }
-                for (int i = 0; i < partials.size(); i++) {
+                final int partialsSize = partials.size();
+                for (int i = 0; i < partialsSize; i++) {
                     UserInfo ui = partials.get(i);
                     Slog.w(LOG_TAG, "Removing partially created user " + ui.id
                             + " (name=" + ui.name + ")");
@@ -272,7 +275,8 @@
     public UserInfo getPrimaryUser() {
         checkManageUsersPermission("query users");
         synchronized (mPackagesLock) {
-            for (int i = 0; i < mUsers.size(); i++) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
                 UserInfo ui = mUsers.valueAt(i);
                 if (ui.isPrimary()) {
                     return ui;
@@ -287,7 +291,8 @@
         checkManageUsersPermission("query users");
         synchronized (mPackagesLock) {
             ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
-            for (int i = 0; i < mUsers.size(); i++) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
                 UserInfo ui = mUsers.valueAt(i);
                 if (ui.partial) {
                     continue;
@@ -323,7 +328,8 @@
             // Probably a dying user
             return users;
         }
-        for (int i = 0; i < mUsers.size(); i++) {
+        final int userSize = mUsers.size();
+        for (int i = 0; i < userSize; i++) {
             UserInfo profile = mUsers.valueAt(i);
             if (!isProfileOf(user, profile)) {
                 continue;
@@ -1010,7 +1016,8 @@
             serializer.startTag(null, TAG_GUEST_RESTRICTIONS);
             writeRestrictionsLocked(serializer, mGuestRestrictions);
             serializer.endTag(null, TAG_GUEST_RESTRICTIONS);
-            for (int i = 0; i < mUsers.size(); i++) {
+            final int userSize = mUsers.size();
+            for (int i = 0; i < userSize; i++) {
                 UserInfo user = mUsers.valueAt(i);
                 serializer.startTag(null, TAG_USER);
                 serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
@@ -1587,6 +1594,9 @@
                             }
                             new Thread() {
                                 public void run() {
+                                    // Clean up any ActivityManager state
+                                    LocalServices.getService(ActivityManagerInternal.class)
+                                            .onUserRemoved(userHandle);
                                     synchronized (mInstallLock) {
                                         synchronized (mPackagesLock) {
                                             removeUserStateLocked(userHandle);
@@ -1951,14 +1961,15 @@
      */
     private void updateUserIdsLocked() {
         int num = 0;
-        for (int i = 0; i < mUsers.size(); i++) {
+        final int userSize = mUsers.size();
+        for (int i = 0; i < userSize; i++) {
             if (!mUsers.valueAt(i).partial) {
                 num++;
             }
         }
         final int[] newUsers = new int[num];
         int n = 0;
-        for (int i = 0; i < mUsers.size(); i++) {
+        for (int i = 0; i < userSize; i++) {
             if (!mUsers.valueAt(i).partial) {
                 newUsers[n++] = mUsers.keyAt(i);
             }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 745874c..6b5ecdc 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -584,12 +584,7 @@
     }
 
     TaskStack getDockedStackLocked() {
-        for (int i = mStacks.size() - 1; i >= 0; i--) {
-            TaskStack stack = mStacks.get(i);
-            if (stack.mStackId == DOCKED_STACK_ID && stack.isVisibleLocked()) {
-                return stack;
-            }
-        }
-        return null;
+        final TaskStack stack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        return (stack != null && stack.isVisibleLocked()) ? stack : null;
     }
 }
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index df2e5e8..9da7406 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -19,8 +19,8 @@
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.RESIZE_MODE_FORCED;
 import static android.app.ActivityManager.RESIZE_MODE_USER;
+import static android.app.ActivityManager.RESIZE_MODE_USER_FORCED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static com.android.server.wm.WindowManagerService.DEBUG_TASK_POSITIONING;
@@ -179,7 +179,7 @@
                             // We were using fullscreen surface during resizing. Request
                             // resizeTask() one last time to restore surface to window size.
                             mService.mActivityManager.resizeTask(
-                                    mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_FORCED);
+                                    mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
                         }
 
                         if (mCurrentDimSide != 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 10ea4e2..f030b9a 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -25,7 +25,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Debug;
-import android.os.RemoteException;
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -59,7 +58,7 @@
 
     /** For comparison with DisplayContent bounds. */
     private Rect mTmpRect = new Rect();
-    private Rect TmpRect2 = new Rect();
+    private Rect mTmpRect2 = new Rect();
 
     /** Content limits relative to the DisplayContent this sits in. */
     private Rect mBounds = new Rect();
@@ -226,10 +225,10 @@
             } else if (mFullscreen) {
                 setBounds(null);
             } else {
-                TmpRect2.set(mBounds);
+                mTmpRect2.set(mBounds);
                 mDisplayContent.rotateBounds(
-                        mRotation, mDisplayContent.getDisplayInfo().rotation, TmpRect2);
-                if (setBounds(TmpRect2)) {
+                        mRotation, mDisplayContent.getDisplayInfo().rotation, mTmpRect2);
+                if (setBounds(mTmpRect2)) {
                     // Post message to inform activity manager of the bounds change simulating
                     // a one-way call. We do this to prevent a deadlock between window manager
                     // lock and activity manager lock been held.
@@ -383,14 +382,18 @@
         mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId());
 
         Rect bounds = null;
-        final boolean dockedStackExists = mService.mStackIdToStack.get(DOCKED_STACK_ID) != null;
-        if (mStackId == DOCKED_STACK_ID || (dockedStackExists
+        final TaskStack dockedStack = mService.mStackIdToStack.get(DOCKED_STACK_ID);
+        if (mStackId == DOCKED_STACK_ID || (dockedStack != null
                 && mStackId >= FIRST_STATIC_STACK_ID && mStackId <= LAST_STATIC_STACK_ID)) {
             // The existence of a docked stack affects the size of any static stack created since
             // the docked stack occupies a dedicated region on screen.
             bounds = new Rect();
             displayContent.getLogicalDisplayRect(mTmpRect);
-            getInitialDockedStackBounds(mTmpRect, bounds, mStackId,
+            mTmpRect2.setEmpty();
+            if (dockedStack != null) {
+                dockedStack.getRawBounds(mTmpRect2);
+            }
+            getInitialDockedStackBounds(mTmpRect, bounds, mStackId, mTmpRect2,
                     mDisplayContent.mDividerControllerLocked.getWidthAdjustment());
         }
 
@@ -400,7 +403,7 @@
             // Attaching a docked stack to the display affects the size of all other static
             // stacks since the docked stack occupies a dedicated region on screen.
             // Resize existing static stacks so they are pushed to the side of the docked stack.
-            resizeNonDockedStacks(!FULLSCREEN);
+            resizeNonDockedStacks(!FULLSCREEN, mBounds);
         }
     }
 
@@ -410,29 +413,49 @@
      * @param displayRect The bounds of the display the docked stack is on.
      * @param outBounds Output bounds that should be used for the stack.
      * @param stackId Id of stack we are calculating the bounds for.
+     * @param dockedBounds Bounds of the docked stack.
      * @param adjustment
      */
-    private static void getInitialDockedStackBounds(Rect displayRect, Rect outBounds, int stackId,
-            int adjustment) {
-        // Docked stack start off occupying half the screen space.
+    private static void getInitialDockedStackBounds(
+            Rect displayRect, Rect outBounds, int stackId, Rect dockedBounds, int adjustment) {
         final boolean dockedStack = stackId == DOCKED_STACK_ID;
         final boolean splitHorizontally = displayRect.width() > displayRect.height();
         final boolean topOrLeftCreateMode =
                 WindowManagerService.sDockedStackCreateMode == DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-        final boolean placeTopOrLeft = (dockedStack && topOrLeftCreateMode)
-                || (!dockedStack && !topOrLeftCreateMode);
+
         outBounds.set(displayRect);
-        if (placeTopOrLeft) {
-            if (splitHorizontally) {
-                outBounds.right = displayRect.centerX() - adjustment;
+        if (dockedStack) {
+            // The initial bounds of the docked stack when it is created half the screen space and
+            // its bounds can be adjusted after that. The bounds of all other stacks are adjusted
+            // to occupy whatever screen space the docked stack isn't occupying.
+            if (topOrLeftCreateMode) {
+                if (splitHorizontally) {
+                    outBounds.right = displayRect.centerX() - adjustment;
+                } else {
+                    outBounds.bottom = displayRect.centerY() - adjustment;
+                }
             } else {
-                outBounds.bottom = displayRect.centerY() - adjustment;
+                if (splitHorizontally) {
+                    outBounds.left = displayRect.centerX() + adjustment;
+                } else {
+                    outBounds.top = displayRect.centerY() + adjustment;
+                }
+            }
+            return;
+        }
+
+        // Other stacks occupy whatever space is left by the docked stack.
+        if (!topOrLeftCreateMode) {
+            if (splitHorizontally) {
+                outBounds.right = dockedBounds.left - adjustment;
+            } else {
+                outBounds.bottom = dockedBounds.top - adjustment;
             }
         } else {
             if (splitHorizontally) {
-                outBounds.left = displayRect.centerX() + adjustment;
+                outBounds.left = dockedBounds.right + adjustment;
             } else {
-                outBounds.top = displayRect.centerY() + adjustment;
+                outBounds.top = dockedBounds.bottom + adjustment;
             }
         }
     }
@@ -441,11 +464,14 @@
      * based on the presence of a docked stack.
      * @param fullscreen If true the stacks will be resized to fullscreen, else they will be
      *                   resized to the appropriate size based on the presence of a docked stack.
+     * @param dockedBounds Bounds of the docked stack.
      */
-    private void resizeNonDockedStacks(boolean fullscreen) {
-        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+    private void resizeNonDockedStacks(boolean fullscreen, Rect dockedBounds) {
+        // Not using mTmpRect because we are posting the object in a message.
+        final Rect bounds = new Rect();
+        mDisplayContent.getLogicalDisplayRect(bounds);
         if (!fullscreen) {
-            getInitialDockedStackBounds(mTmpRect, mTmpRect, FULLSCREEN_WORKSPACE_STACK_ID,
+            getInitialDockedStackBounds(bounds, bounds, FULLSCREEN_WORKSPACE_STACK_ID, dockedBounds,
                     mDisplayContent.mDividerControllerLocked.getWidth());
         }
 
@@ -456,11 +482,8 @@
             if (otherStackId != DOCKED_STACK_ID
                     && otherStackId >= FIRST_STATIC_STACK_ID
                     && otherStackId <= LAST_STATIC_STACK_ID) {
-                try {
-                    mService.mActivityManager.resizeStack(otherStackId, mTmpRect);
-                } catch (RemoteException e) {
-                    // This will not happen since we are in the same process.
-                }
+                mService.mH.sendMessage(
+                        mService.mH.obtainMessage(RESIZE_STACK, otherStackId, -1, bounds));
             }
         }
     }
@@ -488,7 +511,7 @@
         if (mStackId == DOCKED_STACK_ID) {
             // Docked stack was detached from the display, so we no longer need to restrict the
             // region of the screen other static stacks occupy. Go ahead and make them fullscreen.
-            resizeNonDockedStacks(FULLSCREEN);
+            resizeNonDockedStacks(FULLSCREEN, null);
         }
 
         close();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index c6c7497..f4ffe2e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -50,11 +50,11 @@
         private final File mDeviceOwnerFile;
         private final File mProfileOwnerBase;
 
-        public OwnersTestable(Context context, File dataDir) {
+        public OwnersTestable(DpmMockContext context) {
             super(context);
-            mLegacyFile = new File(dataDir, LEGACY_FILE);
-            mDeviceOwnerFile = new File(dataDir, DEVICE_OWNER_FILE);
-            mProfileOwnerBase = new File(dataDir, PROFILE_OWNER_FILE_BASE);
+            mLegacyFile = new File(context.dataDir, LEGACY_FILE);
+            mDeviceOwnerFile = new File(context.dataDir, DEVICE_OWNER_FILE);
+            mProfileOwnerBase = new File(context.dataDir, PROFILE_OWNER_FILE_BASE);
         }
 
         @Override
@@ -90,27 +90,15 @@
 
         public final File dataDir;
 
-        public final File systemUserDataDir;
-        public final File secondUserDataDir;
-
         private MockInjector(DpmMockContext context, File dataDir) {
             super(context);
             this.context = context;
             this.dataDir = dataDir;
-
-            systemUserDataDir = new File(dataDir, "user0");
-            DpmTestUtils.clearDir(dataDir);
-
-            secondUserDataDir = new File(dataDir, "user" + DpmMockContext.CALLER_USER_HANDLE);
-            DpmTestUtils.clearDir(secondUserDataDir);
-
-            when(context.environment.getUserSystemDirectory(
-                    eq(DpmMockContext.CALLER_USER_HANDLE))).thenReturn(secondUserDataDir);
         }
 
         @Override
         Owners newOwners() {
-            return new OwnersTestable(context, dataDir);
+            return new OwnersTestable(context);
         }
 
         @Override
@@ -165,7 +153,7 @@
 
         @Override
         String getDevicePolicyFilePathForSystemUser() {
-            return systemUserDataDir.getAbsolutePath();
+            return context.systemUserDataDir.getAbsolutePath();
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 0072f52..5b23798 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -91,11 +91,13 @@
         admin2 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin2.class);
         admin3 = new ComponentName(mRealTestContext, DummyDeviceAdmins.Admin3.class);
 
-        setUpPackageManagerForAdmin(admin1);
-        setUpPackageManagerForAdmin(admin2);
-        setUpPackageManagerForAdmin(admin3);
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
+        setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
+        setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
 
-        setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
+        setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+                DpmMockContext.CALLER_UID);
+
         setUpPackageInfo();
         setUpUserManager();
     }
@@ -105,7 +107,7 @@
      * the actual ResolveInfo for the admin component, but we need to mock PM so it'll return
      * it for user {@link DpmMockContext#CALLER_USER_HANDLE}.
      */
-    private void setUpPackageManagerForAdmin(ComponentName admin) {
+    private void setUpPackageManagerForAdmin(ComponentName admin, int packageUid) {
         final Intent resolveIntent = new Intent();
         resolveIntent.setComponent(admin);
         final List<ResolveInfo> realResolveInfo =
@@ -115,32 +117,36 @@
         assertNotNull(realResolveInfo);
         assertEquals(1, realResolveInfo.size());
 
+        // We need to change AI, so set a clone.
+        realResolveInfo.set(0, DpmTestUtils.cloneParcelable(realResolveInfo.get(0)));
+
         // We need to rewrite the UID in the activity info.
-        realResolveInfo.get(0).activityInfo.applicationInfo.uid = DpmMockContext.CALLER_UID;
+        realResolveInfo.get(0).activityInfo.applicationInfo.uid = packageUid;
 
         doReturn(realResolveInfo).when(mContext.packageManager).queryBroadcastReceivers(
                 MockUtils.checkIntentComponent(admin),
                 eq(PackageManager.GET_META_DATA
                         | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
-                eq(DpmMockContext.CALLER_USER_HANDLE)
-        );
+                eq(UserHandle.getUserId(packageUid)));
     }
 
     /**
      * Set up a mock result for {@link IPackageManager#getApplicationInfo} for user
      * {@link DpmMockContext#CALLER_USER_HANDLE}.
      */
-    private void setUpApplicationInfo(int enabledSetting) throws Exception {
-        final ApplicationInfo ai = mRealTestContext.getPackageManager().getApplicationInfo(
-                admin1.getPackageName(),
-                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
+    private void setUpApplicationInfo(int enabledSetting, int packageUid) throws Exception {
+        final ApplicationInfo ai = DpmTestUtils.cloneParcelable(
+                mRealTestContext.getPackageManager().getApplicationInfo(
+                        admin1.getPackageName(),
+                        PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS));
 
         ai.enabledSetting = enabledSetting;
+        ai.uid = packageUid;
 
         doReturn(ai).when(mContext.ipackageManager).getApplicationInfo(
                 eq(admin1.getPackageName()),
                 eq(PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS),
-                eq(DpmMockContext.CALLER_USER_HANDLE));
+                eq(UserHandle.getUserId(packageUid)));
     }
 
     /**
@@ -193,16 +199,8 @@
         }).when(mContext.userManager).getApplicationRestrictions(
                 anyString(), any(UserHandle.class));
 
-        // System user is always running.
-        when(mContext.userManager.isUserRunning(MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)))
-                .thenReturn(true);
-
-        // Set up (default) UserInfo for CALLER_USER_HANDLE.
-        final UserInfo uh = new UserInfo(DpmMockContext.CALLER_USER_HANDLE,
-                "user" + DpmMockContext.CALLER_USER_HANDLE, 0);
-
-        when(mContext.userManager.getUserInfo(eq(DpmMockContext.CALLER_USER_HANDLE)))
-                .thenReturn(uh);
+        // Add the first secondary user.
+        mContext.addUser(DpmMockContext.CALLER_USER_HANDLE, 0);
     }
 
     private void setAsProfileOwner(ComponentName admin) {
@@ -309,7 +307,8 @@
 
         // Next, add one more admin.
         // Before doing so, update the application info, now it's enabled.
-        setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                DpmMockContext.CALLER_UID);
 
         dpm.setActiveAdmin(admin2, /* replace =*/ false);
 
@@ -354,6 +353,35 @@
         mContext.callerPermissions.remove("android.permission.INTERACT_ACROSS_USERS_FULL");
     }
 
+    public void testSetActiveAdmin_multiUsers() throws Exception {
+
+        final int ANOTHER_USER_ID = 100;
+        final int ANOTHER_ADMIN_UID = UserHandle.getUid(ANOTHER_USER_ID, 20456);
+
+        mMockContext.addUser(ANOTHER_USER_ID, 0); // Add one more user.
+
+        // Set up pacakge manager for the other user.
+        setUpPackageManagerForAdmin(admin2, ANOTHER_ADMIN_UID);
+        setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+                ANOTHER_ADMIN_UID);
+
+        mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+
+        dpm.setActiveAdmin(admin1, /* replace =*/ false);
+
+        mMockContext.binder.callingUid = ANOTHER_ADMIN_UID;
+        dpm.setActiveAdmin(admin2, /* replace =*/ false);
+
+
+        mMockContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        assertTrue(dpm.isAdminActive(admin1));
+        assertFalse(dpm.isAdminActive(admin2));
+
+        mMockContext.binder.callingUid = ANOTHER_ADMIN_UID;
+        assertFalse(dpm.isAdminActive(admin1));
+        assertTrue(dpm.isAdminActive(admin2));
+    }
+
     /**
      * Test for:
      * {@link DevicePolicyManager#setActiveAdmin}
@@ -400,9 +428,11 @@
         // having MANAGE_DEVICE_ADMINS.
         mContext.callerPermissions.clear();
 
+        // Change the caller, and call into DPMS directly with a different user-id.
+
         mContext.binder.callingUid = 1234567;
         try {
-            dpm.removeActiveAdmin(admin1);
+            dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE);
             fail("Didn't throw SecurityException");
         } catch (SecurityException expected) {
         }
@@ -412,7 +442,7 @@
      * Test for:
      * {@link DevicePolicyManager#removeActiveAdmin}
      */
-    public void testRemoveActiveAdmin_fromDifferentUserWithMINTERACT_ACROSS_USERS_FULL() {
+    public void testRemoveActiveAdmin_fromDifferentUserWithINTERACT_ACROSS_USERS_FULL() {
         mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
 
         // Add admin1.
@@ -424,8 +454,11 @@
 
         // Different user, but should work, because caller has proper permissions.
         mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
+
+        // Change the caller, and call into DPMS directly with a different user-id.
         mContext.binder.callingUid = 1234567;
-        dpm.removeActiveAdmin(admin1);
+
+        dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE);
 
         assertTrue(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
 
@@ -498,9 +531,14 @@
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
         mContext.callerPermissions.add(permission.INTERACT_ACROSS_USERS_FULL);
 
-        // Call from a process on the system user.
+        // In this test, change the caller user to "system".
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
 
+        // Make sure admin1 is installed on system user.
+        setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+        setUpApplicationInfo(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+                DpmMockContext.CALLER_SYSTEM_USER_UID);
+
         // DO needs to be an DA.
         dpm.setActiveAdmin(admin1, /* replace =*/ false);
 
@@ -536,8 +574,6 @@
         // Call from a process on the system user.
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
 
-        // DO needs to be an DA.
-        dpm.setActiveAdmin(admin1, /* replace =*/ false);
         try {
             dpm.setDeviceOwner("a.b.c");
             fail("Didn't throw IllegalArgumentException");
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
index 325bf9f..b80f3bf 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
@@ -16,6 +16,7 @@
 package com.android.server.devicepolicy;
 
 import android.app.admin.DevicePolicyManager;
+import android.os.UserHandle;
 
 /**
  * Overrides {@link #DevicePolicyManager} for dependency injection.
@@ -31,6 +32,6 @@
 
     @Override
     public int myUserId() {
-        return DpmMockContext.CALLER_USER_HANDLE;
+        return UserHandle.getUserId(dpms.context.binder.callingUid);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 3b30a37..7b36e88 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -27,6 +27,7 @@
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.media.IAudioService;
 import android.os.Bundle;
 import android.os.Handler;
@@ -43,8 +44,10 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 /**
  * Context used throughout DPMS tests.
@@ -58,12 +61,12 @@
     /**
      * UID corresponding to {@link #CALLER_USER_HANDLE}.
      */
-    public static final int CALLER_UID = UserHandle.PER_USER_RANGE * CALLER_USER_HANDLE + 123;
+    public static final int CALLER_UID = UserHandle.getUid(CALLER_USER_HANDLE, 20123);
 
     /**
      * UID used when a caller is on the system user.
      */
-    public static final int CALLER_SYSTEM_USER_UID = 123;
+    public static final int CALLER_SYSTEM_USER_UID = 20321;
 
     /**
      * PID of the caller.
@@ -164,6 +167,9 @@
      */
     public final Context spiedContext;
 
+    public final File dataDir;
+    public final File systemUserDataDir;
+
     public final MockBinder binder;
     public final EnvironmentForMock environment;
     public final SystemPropertiesForMock systemProperties;
@@ -184,8 +190,14 @@
 
     public final List<String> callerPermissions = new ArrayList<>();
 
-    public DpmMockContext(Context context) {
+    private final ArrayList<UserInfo> mUserInfos = new ArrayList<>();
+
+    public DpmMockContext(Context context, File dataDir) {
         realTestContext = context;
+
+        this.dataDir = dataDir;
+        DpmTestUtils.clearDir(dataDir);
+
         binder = new MockBinder();
         environment = mock(EnvironmentForMock.class);
         systemProperties= mock(SystemPropertiesForMock.class);
@@ -205,6 +217,39 @@
         packageManager = spy(context.getPackageManager());
 
         spiedContext = mock(Context.class);
+
+        // Add the system user
+        systemUserDataDir = addUser(UserHandle.USER_SYSTEM, UserInfo.FLAG_PRIMARY);
+
+        // System user is always running.
+        when(userManager.isUserRunning(MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)))
+                .thenReturn(true);
+    }
+
+    public File addUser(int userId, int flags) {
+
+        // Set up (default) UserInfo for CALLER_USER_HANDLE.
+        final UserInfo uh = new UserInfo(userId, "user" + userId, flags);
+        when(userManager.getUserInfo(eq(userId))).thenReturn(uh);
+
+        mUserInfos.add(uh);
+        when(userManager.getUsers()).thenReturn(mUserInfos);
+
+        // Create a data directory.
+        final File dir = new File(dataDir, "user" + userId);
+        DpmTestUtils.clearDir(dir);
+
+        when(environment.getUserSystemDirectory(eq(userId))).thenReturn(dir);
+        return dir;
+    }
+
+    /**
+     * Add multiple users at once.  They'll all have flag 0.
+     */
+    public void addUsers(int... userIds) {
+        for (int userId : userIds) {
+            addUser(userId, 0);
+        }
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index 6f9f6ab..63bf125 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -34,10 +34,9 @@
         super.setUp();
 
         mRealTestContext = super.getContext();
-        mMockContext = new DpmMockContext(super.getContext());
 
-        dataDir = new File(mRealTestContext.getCacheDir(), "test-data");
-        DpmTestUtils.clearDir(dataDir);
+        mMockContext = new DpmMockContext(
+                mRealTestContext, new File(mRealTestContext.getCacheDir(), "test-data"));
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
index 44a851a..7506273 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestUtils.java
@@ -17,6 +17,8 @@
 package com.android.server.devicepolicy;
 
 import android.os.FileUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.Log;
 import android.util.Printer;
 
@@ -41,6 +43,15 @@
         return list == null ? 0 : list.size();
     }
 
+    public static <T extends Parcelable> T cloneParcelable(T source) {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(source, 0);
+        p.setDataPosition(0);
+        final T clone = p.readParcelable(DpmTestUtils.class.getClassLoader());
+        p.recycle();
+        return clone;
+    }
+
     public static Printer LOG_PRINTER = new Printer() {
         @Override
         public void println(String x) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index a07d615..4a39614 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -69,22 +69,12 @@
         }
     }
 
-    private void addUsersToUserManager(int... userIds) {
-        final ArrayList<UserInfo> userInfos = new ArrayList<>();
-        for (int userId : userIds) {
-            final UserInfo ui = new UserInfo();
-            ui.id = userId;
-            userInfos.add(ui);
-        }
-        when(getContext().userManager.getUsers()).thenReturn(userInfos);
-    }
-
     public void testUpgrade01() throws Exception {
-        addUsersToUserManager(10, 11, 20, 21);
+        getContext().addUsers(10, 11, 20, 21);
 
         // First, migrate.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
 
             createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
                     readAsset("OwnersTest/test01/input.xml"));
@@ -111,7 +101,7 @@
 
         // Then re-read and check.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
             owners.load();
 
             assertFalse(owners.hasDeviceOwner());
@@ -123,11 +113,11 @@
     }
 
     public void testUpgrade02() throws Exception {
-        addUsersToUserManager(10, 11, 20, 21);
+        getContext().addUsers(10, 11, 20, 21);
 
         // First, migrate.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
 
             createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
                     readAsset("OwnersTest/test02/input.xml"));
@@ -156,7 +146,7 @@
 
         // Then re-read and check.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
             owners.load();
 
             assertTrue(owners.hasDeviceOwner());
@@ -171,11 +161,11 @@
     }
 
     public void testUpgrade03() throws Exception {
-        addUsersToUserManager(10, 11, 20, 21);
+        getContext().addUsers(10, 11, 20, 21);
 
         // First, migrate.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
 
             createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
                     readAsset("OwnersTest/test03/input.xml"));
@@ -212,7 +202,7 @@
 
         // Then re-read and check.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
             owners.load();
 
             assertFalse(owners.hasDeviceOwner());
@@ -235,11 +225,11 @@
     }
 
     public void testUpgrade04() throws Exception {
-        addUsersToUserManager(10, 11, 20, 21);
+        getContext().addUsers(10, 11, 20, 21);
 
         // First, migrate.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
 
             createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
                     readAsset("OwnersTest/test04/input.xml"));
@@ -281,7 +271,7 @@
 
         // Then re-read and check.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
             owners.load();
 
             assertTrue(owners.hasDeviceOwner());
@@ -309,11 +299,11 @@
     }
 
     public void testUpgrade05() throws Exception {
-        addUsersToUserManager(10, 11, 20, 21);
+        getContext().addUsers(10, 11, 20, 21);
 
         // First, migrate.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
 
             createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
                     readAsset("OwnersTest/test05/input.xml"));
@@ -341,7 +331,7 @@
 
         // Then re-read and check.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
             owners.load();
 
             assertFalse(owners.hasDeviceOwner());
@@ -356,11 +346,11 @@
     }
 
     public void testUpgrade06() throws Exception {
-        addUsersToUserManager(10, 11, 20, 21);
+        getContext().addUsers(10, 11, 20, 21);
 
         // First, migrate.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
 
             createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),
                     readAsset("OwnersTest/test06/input.xml"));
@@ -387,7 +377,7 @@
 
         // Then re-read and check.
         {
-            final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+            final OwnersTestable owners = new OwnersTestable(getContext());
             owners.load();
 
             assertFalse(owners.hasDeviceOwner());
@@ -401,9 +391,9 @@
     }
 
     public void testRemoveExistingFiles() throws Exception {
-        addUsersToUserManager(10, 11, 20, 21);
+        getContext().addUsers(10, 11, 20, 21);
 
-        final OwnersTestable owners = new OwnersTestable(getContext(), dataDir);
+        final OwnersTestable owners = new OwnersTestable(getContext());
 
         // First, migrate to create new-style config files.
         createLegacyFile(owners.getLegacyConfigFileWithTestOverride(),