Fixing major regressions in Recents with multi-user. (Bug 18574950)

In the process of front-loading a lot of the loading work, we moved logic
into AlternateRecentsComponent, which is not multi-user friendly.  This CL 
proxies all the necessary events for non-primary users to their own SystemUI 
process to ensure that they handle them correctly, and proxies visibility
changes back to the primary user (since that is the only process with the
right callbacks).

Change-Id: I9fed92214ac4d4e2309ad6ffc35d09b5726c2880
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0e69f74..b606a6f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -197,6 +197,7 @@
         <!-- Alternate Recents -->
         <activity android:name=".recents.RecentsActivity"
                   android:label="@string/accessibility_desc_recent_apps"
+                  android:exported="false"
                   android:launchMode="singleInstance"
                   android:excludeFromRecents="true"
                   android:stateNotNeeded="true"
@@ -207,6 +208,17 @@
             </intent-filter>
         </activity>
 
+        <receiver android:name=".recents.RecentsUserEventProxyReceiver"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="com.android.systemui.recents.action.SHOW_RECENTS_FOR_USER" />
+                <action android:name="com.android.systemui.recents.action.HIDE_RECENTS_FOR_USER" />
+                <action android:name="com.android.systemui.recents.action.TOGGLE_RECENTS_FOR_USER" />
+                <action android:name="com.android.systemui.recents.action.PRELOAD_RECENTS_FOR_USER" />
+                <action android:name="com.android.systemui.recents.action.CONFIG_CHANGED_FOR_USER" />
+            </intent-filter>
+        </receiver>
+
         <!-- started from UsbDeviceSettingsManager -->
         <activity android:name=".usb.UsbConfirmActivity"
             android:exported="true"
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Recents.java b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
index 9a55590..e9f3cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Recents.java
@@ -18,6 +18,7 @@
 
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
+import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -44,16 +45,29 @@
 
     // Which recents to use
     boolean mUseAlternateRecents = true;
-    AlternateRecentsComponent mAlternateRecents;
     boolean mBootCompleted = false;
+    static AlternateRecentsComponent sAlternateRecents;
+
+    /** Returns the Recents component, creating a new one in-process if necessary. */
+    public static AlternateRecentsComponent getRecentsComponent(Context context,
+            boolean forceInitialize) {
+        if (sAlternateRecents == null) {
+            sAlternateRecents = new AlternateRecentsComponent(context);
+            if (forceInitialize) {
+                sAlternateRecents.onStart();
+                sAlternateRecents.onBootCompleted();
+            }
+        }
+        return sAlternateRecents;
+    }
 
     @Override
     public void start() {
         if (mUseAlternateRecents) {
-            if (mAlternateRecents == null) {
-                mAlternateRecents = new AlternateRecentsComponent(mContext);
+            if (sAlternateRecents == null) {
+                sAlternateRecents = getRecentsComponent(mContext, false);
             }
-            mAlternateRecents.onStart();
+            sAlternateRecents.onStart();
         }
 
         putComponent(RecentsComponent.class, this);
@@ -62,8 +76,8 @@
     @Override
     protected void onBootCompleted() {
         if (mUseAlternateRecents) {
-            if (mAlternateRecents != null) {
-                mAlternateRecents.onBootCompleted();
+            if (sAlternateRecents != null) {
+                sAlternateRecents.onBootCompleted();
             }
         }
         mBootCompleted = true;
@@ -72,14 +86,14 @@
     @Override
     public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
         if (mUseAlternateRecents) {
-            mAlternateRecents.onShowRecents(triggeredFromAltTab, statusBarView);
+            sAlternateRecents.onShowRecents(triggeredFromAltTab);
         }
     }
 
     @Override
     public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
         if (mUseAlternateRecents) {
-            mAlternateRecents.onHideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+            sAlternateRecents.onHideRecents(triggeredFromAltTab, triggeredFromHomeKey);
         } else {
             Intent intent = new Intent(RecentsActivity.CLOSE_RECENTS_INTENT);
             intent.setPackage("com.android.systemui");
@@ -93,7 +107,7 @@
     public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
         if (mUseAlternateRecents) {
             // Launch the alternate recents if required
-            mAlternateRecents.onToggleRecents(statusBarView);
+            sAlternateRecents.onToggleRecents();
             return;
         }
 
@@ -241,14 +255,14 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         if (mUseAlternateRecents) {
-            mAlternateRecents.onConfigurationChanged(newConfig);
+            sAlternateRecents.onConfigurationChanged(newConfig);
         }
     }
 
     @Override
     public void preloadRecents() {
         if (mUseAlternateRecents) {
-            mAlternateRecents.onPreloadRecents();
+            sAlternateRecents.onPreloadRecents();
         } else {
             Intent intent = new Intent(RecentsActivity.PRELOAD_INTENT);
             intent.setClassName("com.android.systemui",
@@ -262,7 +276,7 @@
     @Override
     public void cancelPreloadingRecents() {
         if (mUseAlternateRecents) {
-            mAlternateRecents.onCancelPreloadingRecents();
+            sAlternateRecents.onCancelPreloadingRecents();
         } else {
             Intent intent = new Intent(RecentsActivity.CANCEL_PRELOAD_INTENT);
             intent.setClassName("com.android.systemui",
@@ -276,21 +290,21 @@
     @Override
     public void showNextAffiliatedTask() {
         if (mUseAlternateRecents) {
-            mAlternateRecents.onShowNextAffiliatedTask();
+            sAlternateRecents.onShowNextAffiliatedTask();
         }
     }
 
     @Override
     public void showPrevAffiliatedTask() {
         if (mUseAlternateRecents) {
-            mAlternateRecents.onShowPrevAffiliatedTask();
+            sAlternateRecents.onShowPrevAffiliatedTask();
         }
     }
 
     @Override
     public void setCallback(Callbacks cb) {
         if (mUseAlternateRecents) {
-            mAlternateRecents.setRecentsComponentCallback(cb);
+            sAlternateRecents.setRecentsComponentCallback(cb);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index f1bf66d..09a6ccc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -27,6 +27,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -38,7 +39,6 @@
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
-
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.recents.misc.Console;
@@ -57,12 +57,27 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+/**
+ * Annotation for a method that is only called from the primary user's SystemUI process and will be
+ * proxied to the current user.
+ */
+@interface ProxyFromPrimaryToCurrentUser {}
+/**
+ * Annotation for a method that may be called from any user's SystemUI process and will be proxied
+ * to the primary user.
+ */
+@interface ProxyFromAnyToPrimaryUser {}
 
 /** A proxy implementation for the recents component */
 public class AlternateRecentsComponent implements ActivityOptions.OnAnimationStartedListener {
 
-    final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab";
-    final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "recents.triggeredFromHomeKey";
+    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";
+
+    // Owner proxy events
+    final public static String ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER =
+            "action_notify_recents_visibility_change";
 
     final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
     final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
@@ -78,9 +93,22 @@
      * An implementation of ITaskStackListener, that allows us to listen for changes to the system
      * task stacks and update recents accordingly.
      */
-    class TaskStackListenerImpl extends ITaskStackListener.Stub {
+    class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
+        Handler mHandler;
+
+        public TaskStackListenerImpl(Handler handler) {
+            mHandler = handler;
+        }
+
         @Override
         public void onTaskStackChanged() {
+            // Debounce any task stack changes
+            mHandler.removeCallbacks(this);
+            mHandler.post(this);
+        }
+
+        /** Preloads the next task */
+        public void run() {
             RecentsConfiguration config = RecentsConfiguration.getInstance();
             if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
                 // Load the next task only if we aren't svelte
@@ -96,6 +124,20 @@
         }
     }
 
+    /**
+     * A proxy for Recents events which happens strictly for the owner.
+     */
+    class RecentsOwnerEventProxyReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            switch (intent.getAction()) {
+                case ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER:
+                    visibilityChanged(intent.getBooleanExtra(EXTRA_RECENTS_VISIBILITY, false));
+                    break;
+            }
+        }
+    }
+
     static RecentsComponent.Callbacks sRecentsComponentCallbacks;
     static RecentsTaskLoadPlan sInstanceLoadPlan;
 
@@ -104,6 +146,7 @@
     SystemServicesProxy mSystemServicesProxy;
     Handler mHandler;
     TaskStackListenerImpl mTaskStackListener;
+    RecentsOwnerEventProxyReceiver mProxyBroadcastReceiver;
     boolean mBootCompleted;
     boolean mStartAnimationTriggered;
     boolean mCanReuseTaskStackViews = true;
@@ -123,7 +166,6 @@
     TaskStackView mDummyStackView;
 
     // Variables to keep track of if we need to start recents after binding
-    View mStatusBarView;
     boolean mTriggeredFromAltTab;
     long mLastToggleTime;
 
@@ -136,30 +178,37 @@
         mTaskStackBounds = new Rect();
 
         // Register the task stack listener
-        mTaskStackListener = new TaskStackListenerImpl();
+        mTaskStackListener = new TaskStackListenerImpl(mHandler);
         mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+
+        // Only the owner has the callback to update the SysUI visibility flags, so all non-owner
+        // instances of AlternateRecentsComponent needs to notify the owner when the visibility
+        // changes.
+        if (mSystemServicesProxy.isForegroundUserOwner()) {
+            mProxyBroadcastReceiver = new RecentsOwnerEventProxyReceiver();
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(AlternateRecentsComponent.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
+            mContext.registerReceiverAsUser(mProxyBroadcastReceiver, UserHandle.CURRENT, filter,
+                    null, mHandler);
+        }
     }
 
+    /** Creates a new broadcast intent */
+    static Intent createLocalBroadcastIntent(Context context, String action) {
+        Intent intent = new Intent(action);
+        intent.setPackage(context.getPackageName());
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
+                Intent.FLAG_RECEIVER_FOREGROUND);
+        return intent;
+    }
+
+    /** Initializes the Recents. */
+    @ProxyFromPrimaryToCurrentUser
     public void onStart() {
         // Initialize some static datastructures
         TaskStackViewLayoutAlgorithm.initializeCurve();
         // Load the header bar layout
-        reloadHeaderBarLayout();
-        // Try and pre-emptively bind the search widget on startup to ensure that we
-        // have the right thumbnail bounds to animate to.
-        if (Constants.DebugFlags.App.EnableSearchLayout) {
-            // If there is no id, then bind a new search app widget
-            if (mConfig.searchBarAppWidgetId < 0) {
-                AppWidgetHost host = new RecentsAppWidgetHost(mContext,
-                        Constants.Values.App.AppWidgetHostId);
-                Pair<Integer, AppWidgetProviderInfo> widgetInfo =
-                        mSystemServicesProxy.bindSearchAppWidget(host);
-                if (widgetInfo != null) {
-                    // Save the app widget id into the settings
-                    mConfig.updateSearchBarAppWidgetId(mContext, widgetInfo.first);
-                }
-            }
-        }
+        reloadHeaderBarLayout(true);
 
         // When we start, preload the data associated with the previous recent tasks.
         // We can use a new plan since the caches will be the same.
@@ -177,9 +226,19 @@
         mBootCompleted = true;
     }
 
-    /** Shows the recents */
-    public void onShowRecents(boolean triggeredFromAltTab, View statusBarView) {
-        mStatusBarView = statusBarView;
+    /** Shows the Recents. */
+    @ProxyFromPrimaryToCurrentUser
+    public void onShowRecents(boolean triggeredFromAltTab) {
+        if (mSystemServicesProxy.isForegroundUserOwner()) {
+            showRecents(triggeredFromAltTab);
+        } else {
+            Intent intent = createLocalBroadcastIntent(mContext,
+                    RecentsUserEventProxyReceiver.ACTION_PROXY_SHOW_RECENTS_TO_USER);
+            intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
+            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+        }
+    }
+    void showRecents(boolean triggeredFromAltTab) {
         mTriggeredFromAltTab = triggeredFromAltTab;
 
         try {
@@ -189,16 +248,25 @@
         }
     }
 
-    /** Hides the recents */
+    /** Hides the Recents. */
+    @ProxyFromPrimaryToCurrentUser
     public void onHideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+        if (mSystemServicesProxy.isForegroundUserOwner()) {
+            hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+        } else {
+            Intent intent = createLocalBroadcastIntent(mContext,
+                    RecentsUserEventProxyReceiver.ACTION_PROXY_HIDE_RECENTS_TO_USER);
+            intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
+            intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
+            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+        }
+    }
+    void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
         if (mBootCompleted) {
             ActivityManager.RunningTaskInfo topTask = getTopMostTask();
             if (topTask != null && isRecentsTopMost(topTask, null)) {
                 // Notify recents to hide itself
-                Intent intent = new Intent(ACTION_HIDE_RECENTS_ACTIVITY);
-                intent.setPackage(mContext.getPackageName());
-                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
-                        Intent.FLAG_RECEIVER_FOREGROUND);
+                Intent intent = createLocalBroadcastIntent(mContext, ACTION_HIDE_RECENTS_ACTIVITY);
                 intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, triggeredFromAltTab);
                 intent.putExtra(EXTRA_TRIGGERED_FROM_HOME_KEY, triggeredFromHomeKey);
                 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
@@ -206,9 +274,18 @@
         }
     }
 
-    /** Toggles the alternate recents activity */
-    public void onToggleRecents(View statusBarView) {
-        mStatusBarView = statusBarView;
+    /** Toggles the Recents activity. */
+    @ProxyFromPrimaryToCurrentUser
+    public void onToggleRecents() {
+        if (mSystemServicesProxy.isForegroundUserOwner()) {
+            toggleRecents();
+        } else {
+            Intent intent = createLocalBroadcastIntent(mContext,
+                    RecentsUserEventProxyReceiver.ACTION_PROXY_TOGGLE_RECENTS_TO_USER);
+            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+        }
+    }
+    void toggleRecents() {
         mTriggeredFromAltTab = false;
 
         try {
@@ -218,7 +295,18 @@
         }
     }
 
+    /** Preloads info for the Recents activity. */
+    @ProxyFromPrimaryToCurrentUser
     public void onPreloadRecents() {
+        if (mSystemServicesProxy.isForegroundUserOwner()) {
+            preloadRecents();
+        } else {
+            Intent intent = createLocalBroadcastIntent(mContext,
+                    RecentsUserEventProxyReceiver.ACTION_PROXY_PRELOAD_RECENTS_TO_USER);
+            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+        }
+    }
+    void preloadRecents() {
         // Preload only the raw task list into a new load plan (which will be consumed by the
         // RecentsActivity)
         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
@@ -309,15 +397,26 @@
         showRelativeAffiliatedTask(false);
     }
 
+    /** Updates on configuration change. */
+    @ProxyFromPrimaryToCurrentUser
     public void onConfigurationChanged(Configuration newConfig) {
+        if (mSystemServicesProxy.isForegroundUserOwner()) {
+            configurationChanged();
+        } else {
+            Intent intent = createLocalBroadcastIntent(mContext,
+                    RecentsUserEventProxyReceiver.ACTION_PROXY_CONFIG_CHANGE_TO_USER);
+            mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+        }
+    }
+    void configurationChanged() {
         // Don't reuse task stack views if the configuration changes
         mCanReuseTaskStackViews = false;
         // Reload the header bar layout
-        reloadHeaderBarLayout();
+        reloadHeaderBarLayout(false);
     }
 
     /** Prepares the header bar layout. */
-    void reloadHeaderBarLayout() {
+    void reloadHeaderBarLayout(boolean reloadWidget) {
         Resources res = mContext.getResources();
         mWindowRect = mSystemServicesProxy.getWindowRect();
         mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
@@ -325,6 +424,10 @@
         mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
         mConfig = RecentsConfiguration.reinitialize(mContext, mSystemServicesProxy);
         mConfig.updateOnConfigurationChange();
+        if (reloadWidget) {
+            // Reload the widget id before we get the task stack bounds
+            reloadSearchBarAppWidget(mContext, mSystemServicesProxy);
+        }
         mConfig.getTaskStackBounds(mWindowRect.width(), mWindowRect.height(), mStatusBarHeight,
                 (mConfig.hasTransposedNavBar ? mNavBarWidth : 0), mTaskStackBounds);
         if (mConfig.isLandscape && mConfig.hasTransposedNavBar) {
@@ -350,6 +453,24 @@
         mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
     }
 
+    /** Prepares the search bar app widget */
+    void reloadSearchBarAppWidget(Context context, SystemServicesProxy ssp) {
+        // Try and pre-emptively bind the search widget on startup to ensure that we
+        // have the right thumbnail bounds to animate to.
+        if (Constants.DebugFlags.App.EnableSearchLayout) {
+            // If there is no id, then bind a new search app widget
+            if (mConfig.searchBarAppWidgetId < 0) {
+                AppWidgetHost host = new RecentsAppWidgetHost(context,
+                        Constants.Values.App.AppWidgetHostId);
+                Pair<Integer, AppWidgetProviderInfo> widgetInfo = ssp.bindSearchAppWidget(host);
+                if (widgetInfo != null) {
+                    // Save the app widget id into the settings
+                    mConfig.updateSearchBarAppWidgetId(context, widgetInfo.first);
+                }
+            }
+        }
+    }
+
     /** Gets the top task. */
     ActivityManager.RunningTaskInfo getTopMostTask() {
         SystemServicesProxy ssp = mSystemServicesProxy;
@@ -397,10 +518,7 @@
         AtomicBoolean isTopTaskHome = new AtomicBoolean(true);
         if (topTask != null && isRecentsTopMost(topTask, isTopTaskHome)) {
             // Notify recents to toggle itself
-            Intent intent = new Intent(ACTION_TOGGLE_RECENTS_ACTIVITY);
-            intent.setPackage(mContext.getPackageName());
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
-                    Intent.FLAG_RECEIVER_FOREGROUND);
+            Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
             mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
             mLastToggleTime = SystemClock.elapsedRealtime();
             return;
@@ -474,7 +592,7 @@
             }
 
             mStartAnimationTriggered = false;
-            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mStatusBarView,
+            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
                     thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
                     toTaskRect.height(), this);
         }
@@ -623,7 +741,19 @@
     }
 
     /** Notifies the callbacks that the visibility of Recents has changed. */
-    public static void notifyVisibilityChanged(boolean visible) {
+    @ProxyFromAnyToPrimaryUser
+    public static void notifyVisibilityChanged(Context context, SystemServicesProxy ssp,
+            boolean visible) {
+        if (ssp.isForegroundUserOwner()) {
+            visibilityChanged(visible);
+        } else {
+            Intent intent = createLocalBroadcastIntent(context,
+                    ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
+            intent.putExtra(EXTRA_RECENTS_VISIBILITY, visible);
+            context.sendBroadcastAsUser(intent, UserHandle.OWNER);
+        }
+    }
+    static void visibilityChanged(boolean visible) {
         if (sRecentsComponentCallbacks != null) {
             sRecentsComponentCallbacks.onVisibilityChanged(visible);
         }
@@ -667,10 +797,7 @@
             };
 
             // Send the broadcast to notify Recents that the animation has started
-            Intent intent = new Intent(ACTION_START_ENTER_ANIMATION);
-            intent.setPackage(mContext.getPackageName());
-            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT |
-                    Intent.FLAG_RECEIVER_FOREGROUND);
+            Intent intent = createLocalBroadcastIntent(mContext, ACTION_START_ENTER_ANIMATION);
             mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
                     fallbackReceiver, null, Activity.RESULT_CANCELED, null, null);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 6baff96..ee631f5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -434,7 +434,9 @@
     protected void onStart() {
         super.onStart();
         mVisible = true;
-        AlternateRecentsComponent.notifyVisibilityChanged(true);
+        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+        SystemServicesProxy ssp = loader.getSystemServicesProxy();
+        AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, true);
 
         // Register the broadcast receiver to handle messages from our service
         IntentFilter filter = new IntentFilter();
@@ -444,7 +446,7 @@
         registerReceiver(mServiceBroadcastReceiver, filter);
 
         // Register any broadcast receivers for the task loader
-        RecentsTaskLoader.getInstance().registerReceivers(this, mRecentsView);
+        loader.registerReceivers(this, mRecentsView);
 
         // Update the recent tasks
         updateRecentsTasks(getIntent());
@@ -454,7 +456,9 @@
     protected void onStop() {
         super.onStop();
         mVisible = false;
-        AlternateRecentsComponent.notifyVisibilityChanged(false);
+        RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+        SystemServicesProxy ssp = loader.getSystemServicesProxy();
+        AlternateRecentsComponent.notifyVisibilityChanged(this, ssp, false);
 
         // Notify the views that we are no longer visible
         mRecentsView.onRecentsHidden();
@@ -463,7 +467,7 @@
         unregisterReceiver(mServiceBroadcastReceiver);
 
         // Unregister any broadcast receivers for the task loader
-        RecentsTaskLoader.getInstance().unregisterReceivers();
+        loader.unregisterReceivers();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
new file mode 100644
index 0000000..236da5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import com.android.systemui.recent.Recents;
+
+
+/**
+ * A proxy for Recents events which happens strictly for non-owner users.
+ */
+public class RecentsUserEventProxyReceiver extends BroadcastReceiver {
+    final public static String ACTION_PROXY_SHOW_RECENTS_TO_USER =
+            "com.android.systemui.recents.action.SHOW_RECENTS_FOR_USER";
+    final public static String ACTION_PROXY_HIDE_RECENTS_TO_USER =
+            "com.android.systemui.recents.action.HIDE_RECENTS_FOR_USER";
+    final public static String ACTION_PROXY_TOGGLE_RECENTS_TO_USER =
+            "com.android.systemui.recents.action.TOGGLE_RECENTS_FOR_USER";
+    final public static String ACTION_PROXY_PRELOAD_RECENTS_TO_USER =
+            "com.android.systemui.recents.action.PRELOAD_RECENTS_FOR_USER";
+    final public static String ACTION_PROXY_CONFIG_CHANGE_TO_USER =
+            "com.android.systemui.recents.action.CONFIG_CHANGED_FOR_USER";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        AlternateRecentsComponent recents = Recents.getRecentsComponent(
+                context.getApplicationContext(), true);
+        switch (intent.getAction()) {
+            case ACTION_PROXY_SHOW_RECENTS_TO_USER: {
+                boolean triggeredFromAltTab = intent.getBooleanExtra(
+                        AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
+                recents.showRecents(triggeredFromAltTab);
+                break;
+            }
+            case ACTION_PROXY_HIDE_RECENTS_TO_USER: {
+                boolean triggeredFromAltTab = intent.getBooleanExtra(
+                        AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
+                boolean triggeredFromHome = intent.getBooleanExtra(
+                        AlternateRecentsComponent.EXTRA_TRIGGERED_FROM_HOME_KEY, false);
+                recents.hideRecents(triggeredFromAltTab, triggeredFromHome);
+                break;
+            }
+            case ACTION_PROXY_TOGGLE_RECENTS_TO_USER:
+                recents.toggleRecents();
+                break;
+            case ACTION_PROXY_PRELOAD_RECENTS_TO_USER:
+                recents.preloadRecents();
+                break;
+            case ACTION_PROXY_CONFIG_CHANGE_TO_USER:
+                recents.configurationChanged();
+                break;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 3fbd5a6..542f21c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -49,14 +49,12 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.IWindowManager;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
@@ -396,6 +394,15 @@
     }
 
     /**
+     * Returns whether the foreground user is the owner.
+     */
+    public boolean isForegroundUserOwner() {
+        if (mAm == null) return false;
+
+        return mAm.getCurrentUser() == UserHandle.USER_OWNER;
+    }
+
+    /**
      * Resolves and returns the first Recents widget from the same package as the global
      * assist activity.
      */