Refactoring secondary user recents logic.
- Removing old broadcasts in favor of direct aidl interface between
system and secondary users. Also moving user specific implementation
into RecentsImpl, allowing Recents to handle proxying between users.
Change-Id: I4bd5ef1d1ee47309b7c754f50a5e8b2e2aab988f
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 314b3c4..0cc2a67 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -3,7 +3,7 @@
LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under,src) \
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-proto-files-under,src) $(call all-Iaidl-files-under, src) \
src/com/android/systemui/EventLogTags.logtags
LOCAL_STATIC_JAVA_LIBRARIES := Keyguard
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index bbb099e..5d622a0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -199,6 +199,11 @@
android:value="com.android.settings.category.system" />
</activity>
+ <!-- Service used by secondary users to register themselves with the system user. -->
+ <service android:name=".recents.RecentsSystemUserService"
+ android:exported="false"
+ android:permission="com.android.systemui.permission.SELF" />
+
<!-- Alternate Recents -->
<activity android:name=".recents.RecentsActivity"
android:label="@string/accessibility_desc_recent_apps"
@@ -215,17 +220,6 @@
</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>
-
<!-- Callback for dismissing screenshot notification after a share target is picked -->
<receiver android:name=".screenshot.GlobalScreenshot$TargetChosenReceiver"
android:process=":screenshot"
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
new file mode 100644
index 0000000..79eca30d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * Due to the fact that RecentsActivity is per-user, we need to establish an
+ * interface (this) for the system user to callback to the secondary users in
+ * response to UI events coming in from the system user's SystemUI.
+ */
+oneway interface IRecentsNonSystemUserCallbacks {
+ void preloadRecents();
+ void cancelPreloadingRecents();
+ void showRecents(boolean triggeredFromAltTab);
+ void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey);
+ void toggleRecents();
+ void onConfigurationChanged();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
new file mode 100644
index 0000000..6b49195
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Due to the fact that RecentsActivity is per-user, we need to establish an
+ * interface (this) for the non-system user to register itself for callbacks and to
+ * callback to the system user to update internal state.
+ */
+oneway interface IRecentsSystemUserCallbacks {
+ void registerNonSystemUserCallbacks(IBinder nonSystemUserCallbacks, int userId);
+
+ void updateRecentsVisibility(boolean visible);
+ void startScreenPinning();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index aee3623..ae79fe2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -16,908 +16,385 @@
package com.android.systemui.recents;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ITaskStackListener;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.ServiceConnection;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.os.AsyncTask;
import android.os.Handler;
-import android.os.SystemClock;
+import android.os.IBinder;
+import android.os.RemoteException;
import android.os.UserHandle;
-import android.util.MutableBoolean;
+import android.util.Log;
import android.view.Display;
-import android.view.LayoutInflater;
import android.view.View;
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.Prefs;
-import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIApplication;
-import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskGrouping;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
-import com.android.systemui.recents.views.TaskViewHeader;
-import com.android.systemui.recents.views.TaskViewTransform;
-import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.ArrayList;
-/**
- * Annotation for a method that is only called from the system user's SystemUI process and will be
- * proxied to the current user.
- */
-@interface ProxyFromSystemToCurrentUser {}
-/**
- * Annotation for a method that may be called from any user's SystemUI process and will be proxied
- * to the system user.
- */
-@interface ProxyFromAnyToSystemUser {}
-/** A proxy implementation for the recents component */
+/**
+ * An implementation of the SystemUI recents component, which supports both system and secondary
+ * users.
+ */
public class Recents extends SystemUI
- implements ActivityOptions.OnAnimationStartedListener, RecentsComponent {
+ implements RecentsComponent {
+
+ private final static String TAG = "Recents";
+ private final static boolean DEBUG = false;
public final static int EVENT_BUS_PRIORITY = 1;
+ public final static int BIND_TO_SYSTEM_USER_RETRY_DELAY = 5000;
- 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";
+ private SystemServicesProxy mSystemServicesProxy;
+ private Handler mHandler;
+ private RecentsImpl mImpl;
- // Owner proxy events
- final public static String ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER =
- "action_notify_recents_visibility_change";
- final public static String ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER =
- "action_screen_pinning_request";
+ // Only For system user, this is the callbacks instance we return to each secondary user
+ private RecentsSystemUser mSystemUserCallbacks;
- final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
- final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
- final public static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
+ // Only for secondary users, this is the callbacks instance provided by the system user to make
+ // calls back
+ private IRecentsSystemUserCallbacks mCallbacksToSystemUser;
- final static int sMinToggleDelay = 350;
+ // The set of runnables to run after binding to the system user's service.
+ private final ArrayList<Runnable> mOnConnectRunnables = new ArrayList<>();
- public final static String sToggleRecentsAction = "com.android.systemui.recents.SHOW_RECENTS";
- public final static String sRecentsPackage = "com.android.systemui";
- public final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
-
- /**
- * 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 implements Runnable {
- Handler mHandler;
-
- public TaskStackListenerImpl(Handler handler) {
- mHandler = handler;
- }
-
+ // Only for secondary users, this is the death handler for the binder from the system user
+ private final IBinder.DeathRecipient mCallbacksToSystemUserDeathRcpt = new IBinder.DeathRecipient() {
@Override
- public void onTaskStackChanged() {
- // Debounce any task stack changes
- mHandler.removeCallbacks(this);
- mHandler.post(this);
- }
+ public void binderDied() {
+ mCallbacksToSystemUser = null;
- /** Preloads the next task */
- public void run() {
- // TODO: Temporarily skip this if multi stack is enabled
- /*
- RecentsConfiguration config = RecentsConfiguration.getInstance();
- if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- SystemServicesProxy ssp = loader.getSystemServicesProxy();
- ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
-
- // Load the next task only if we aren't svelte
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, true);
- RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- // This callback is made when a new activity is launched and the old one is paused
- // so ignore the current activity and try and preload the thumbnail for the
- // previous one.
- if (runningTaskInfo != null) {
- launchOpts.runningTaskId = runningTaskInfo.id;
+ // Retry after a fixed duration
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ registerWithSystemUser();
}
- launchOpts.numVisibleTasks = 2;
- launchOpts.numVisibleTaskThumbnails = 2;
- launchOpts.onlyLoadForCache = true;
- launchOpts.onlyLoadPausedActivities = true;
- loader.loadTasks(mContext, plan, launchOpts);
- }
- */
+ }, BIND_TO_SYSTEM_USER_RETRY_DELAY);
}
- }
+ };
- /**
- * A proxy for Recents events which happens strictly for the owner.
- */
- class RecentsOwnerEventProxyReceiver extends BroadcastReceiver {
+ // Only for secondary users, this is the service connection we use to connect to the system user
+ private final ServiceConnection mServiceConnectionToSystemUser = new ServiceConnection() {
@Override
- public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()) {
- case ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER:
- visibilityChanged(context,
- intent.getBooleanExtra(EXTRA_RECENTS_VISIBILITY, false));
- break;
- case ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER:
- onStartScreenPinning(context);
- break;
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (service != null) {
+ mCallbacksToSystemUser = IRecentsSystemUserCallbacks.Stub.asInterface(
+ service);
+
+ // Listen for system user's death, so that we can reconnect later
+ try {
+ service.linkToDeath(mCallbacksToSystemUserDeathRcpt, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Lost connection to (System) SystemUI", e);
+ }
+
+ // Run each of the queued runnables
+ runAndFlushOnConnectRunnables();
}
+
+ // Unbind ourselves now that we've registered our callbacks. The
+ // binder to the system user are still valid at this point.
+ mContext.unbindService(this);
}
- }
- static RecentsTaskLoadPlan sInstanceLoadPlan;
- static Recents sInstance;
-
- SystemServicesProxy mSystemServicesProxy;
- Handler mHandler;
- TaskStackListenerImpl mTaskStackListener;
- RecentsOwnerEventProxyReceiver mProxyBroadcastReceiver;
- RecentsAppWidgetHost mAppWidgetHost;
- boolean mBootCompleted;
- boolean mStartAnimationTriggered;
- boolean mCanReuseTaskStackViews = true;
-
- // Task launching
- RecentsConfiguration mConfig;
- Rect mSearchBarBounds = new Rect();
- Rect mTaskStackBounds = new Rect();
- Rect mLastTaskViewBounds = new Rect();
- TaskViewTransform mTmpTransform = new TaskViewTransform();
- int mStatusBarHeight;
- int mNavBarHeight;
- int mNavBarWidth;
- int mTaskBarHeight;
-
- // Header (for transition)
- TaskViewHeader mHeaderBar;
- final Object mHeaderBarLock = new Object();
- TaskStackView mDummyStackView;
-
- // Variables to keep track of if we need to start recents after binding
- boolean mTriggeredFromAltTab;
- long mLastToggleTime;
-
- Bitmap mThumbnailTransitionBitmapCache;
- Task mThumbnailTransitionBitmapCacheKey;
-
- public Recents() {
- }
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ // Do nothing
+ }
+ };
/**
- * Gets the singleton instance and starts it if needed. On the primary user on the device, this
- * component gets started as a normal {@link SystemUI} component. On a secondary user, this
- * lifecycle doesn't exist, so we need to start it manually here if needed.
+ * Returns the callbacks interface that non-system users can call.
*/
- public static Recents getInstanceAndStartIfNeeded(Context ctx) {
- if (sInstance == null) {
- sInstance = new Recents();
- sInstance.mContext = ctx;
- sInstance.start();
- sInstance.onBootCompleted();
- }
- return sInstance;
+ public IBinder getSystemUserCallbacks() {
+ return mSystemUserCallbacks;
}
- /** 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. */
- @ProxyFromSystemToCurrentUser
@Override
public void start() {
- if (sInstance == null) {
- sInstance = this;
- }
- Resources res = mContext.getResources();
- RecentsTaskLoader.initialize(mContext);
- LayoutInflater inflater = LayoutInflater.from(mContext);
mSystemServicesProxy = new SystemServicesProxy(mContext);
mHandler = new Handler();
- mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
+ mImpl = new RecentsImpl(mContext);
- // Register the task stack listener
- mTaskStackListener = new TaskStackListenerImpl(mHandler);
- mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+ // Register with the event bus
+ EventBus.getDefault().register(this, EVENT_BUS_PRIORITY);
- // Only the owner has the callback to update the SysUI visibility flags, so all non-owner
- // instances of RecentsComponent needs to notify the owner when the visibility
- // changes.
- if (mSystemServicesProxy.isForegroundUserSystem()) {
- mProxyBroadcastReceiver = new RecentsOwnerEventProxyReceiver();
- IntentFilter filter = new IntentFilter();
- filter.addAction(Recents.ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
- filter.addAction(Recents.ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
- mContext.registerReceiverAsUser(mProxyBroadcastReceiver, UserHandle.CURRENT, filter,
- null, mHandler);
+ // Due to the fact that RecentsActivity is per-user, we need to establish and interface for
+ // the system user's Recents component to pass events (like show/hide/toggleRecents) to the
+ // secondary user, and vice versa (like visibility change, screen pinning).
+ final int processUser = mSystemServicesProxy.getProcessUser();
+ if (mSystemServicesProxy.isSystemUser(processUser)) {
+ // For the system user, initialize an instance of the interface that we can pass to the
+ // secondary user
+ mSystemUserCallbacks = new RecentsSystemUser(mContext, mImpl);
+ } else {
+ // For the secondary user, bind to the primary user's service to get a persistent
+ // interface to register its implementation and to later update its state
+ registerWithSystemUser();
}
-
- // Initialize the static configuration resources
- mConfig = RecentsConfiguration.initialize(mContext, mSystemServicesProxy);
- mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
- mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
- mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
- mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
- mDummyStackView = new TaskStackView(mContext, new TaskStack());
- mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
- null, false);
- reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
-
- // 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.
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, true /* isTopTaskHome */);
- RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
- launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
- launchOpts.onlyLoadForCache = true;
- loader.loadTasks(mContext, plan, launchOpts);
putComponent(Recents.class, this);
}
@Override
public void onBootCompleted() {
- mBootCompleted = true;
- reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
+ mImpl.onBootCompleted();
}
- /** Shows the Recents. */
- @ProxyFromSystemToCurrentUser
+ /**
+ * Shows the Recents.
+ */
@Override
public void showRecents(boolean triggeredFromAltTab, View statusBarView) {
- if (mSystemServicesProxy.isForegroundUserSystem()) {
- showRecentsInternal(triggeredFromAltTab);
+ int currentUser = mSystemServicesProxy.getCurrentUser();
+ if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ mImpl.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);
+ if (mSystemUserCallbacks != null) {
+ IRecentsNonSystemUserCallbacks callbacks =
+ mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ if (callbacks != null) {
+ try {
+ callbacks.showRecents(triggeredFromAltTab);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ } else {
+ Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+ }
+ }
}
}
- void showRecentsInternal(boolean triggeredFromAltTab) {
- mTriggeredFromAltTab = triggeredFromAltTab;
-
- try {
- showRecentsActivity();
- } catch (ActivityNotFoundException e) {
- Console.logRawError("Failed to launch RecentAppsIntent", e);
- }
- }
-
- /** Hides the Recents. */
- @ProxyFromSystemToCurrentUser
+ /**
+ * Hides the Recents.
+ */
@Override
public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- if (mSystemServicesProxy.isForegroundUserSystem()) {
- hideRecentsInternal(triggeredFromAltTab, triggeredFromHomeKey);
+ int currentUser = mSystemServicesProxy.getCurrentUser();
+ if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ mImpl.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);
+ if (mSystemUserCallbacks != null) {
+ IRecentsNonSystemUserCallbacks callbacks =
+ mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ if (callbacks != null) {
+ try {
+ callbacks.hideRecents(triggeredFromAltTab, triggeredFromHomeKey);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ } else {
+ Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+ }
+ }
}
}
- void hideRecentsInternal(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
- if (mBootCompleted) {
- // Defer to the activity to handle hiding recents, if it handles it, then it must still
- // be visible
- 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);
- }
- }
-
- /** Toggles the Recents activity. */
- @ProxyFromSystemToCurrentUser
+ /**
+ * Toggles the Recents activity.
+ */
@Override
public void toggleRecents(Display display, int layoutDirection, View statusBarView) {
- if (mSystemServicesProxy.isForegroundUserSystem()) {
- toggleRecentsInternal();
+ int currentUser = mSystemServicesProxy.getCurrentUser();
+ if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ mImpl.toggleRecents();
} else {
- Intent intent = createLocalBroadcastIntent(mContext,
- RecentsUserEventProxyReceiver.ACTION_PROXY_TOGGLE_RECENTS_TO_USER);
- mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ if (mSystemUserCallbacks != null) {
+ IRecentsNonSystemUserCallbacks callbacks =
+ mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ if (callbacks != null) {
+ try {
+ callbacks.toggleRecents();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ } else {
+ Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+ }
+ }
}
}
- void toggleRecentsInternal() {
- mTriggeredFromAltTab = false;
-
- try {
- toggleRecentsActivity();
- } catch (ActivityNotFoundException e) {
- Console.logRawError("Failed to launch RecentAppsIntent", e);
- }
- }
-
- /** Preloads info for the Recents activity. */
- @ProxyFromSystemToCurrentUser
+ /**
+ * Preloads info for the Recents activity.
+ */
@Override
public void preloadRecents() {
- if (mSystemServicesProxy.isForegroundUserSystem()) {
- preloadRecentsInternal();
+ int currentUser = mSystemServicesProxy.getCurrentUser();
+ if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ mImpl.preloadRecents();
} else {
- Intent intent = createLocalBroadcastIntent(mContext,
- RecentsUserEventProxyReceiver.ACTION_PROXY_PRELOAD_RECENTS_TO_USER);
- mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
- }
- }
-
- void preloadRecentsInternal() {
- // Preload only the raw task list into a new load plan (which will be consumed by the
- // RecentsActivity) only if there is a task to animate to.
- ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
- MutableBoolean topTaskHome = new MutableBoolean(true);
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- sInstanceLoadPlan = loader.createLoadPlan(mContext);
- if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
- sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
- loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
- TaskStack stack = sInstanceLoadPlan.getTaskStack();
- if (stack.getTaskCount() > 0) {
- preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView,
- topTaskHome.value);
+ if (mSystemUserCallbacks != null) {
+ IRecentsNonSystemUserCallbacks callbacks =
+ mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ if (callbacks != null) {
+ try {
+ callbacks.preloadRecents();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ } else {
+ Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+ }
}
}
}
@Override
public void cancelPreloadingRecents() {
- // Do nothing
- }
-
- void showRelativeAffiliatedTask(boolean showNextTask) {
- // Return early if there is no focused stack
- int focusedStackId = mSystemServicesProxy.getFocusedStack();
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
- loader.preloadTasks(plan, true /* isTopTaskHome */);
- TaskStack focusedStack = plan.getTaskStack();
-
- // Return early if there are no tasks in the focused stack
- if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
-
- ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask();
- // Return early if there is no running task (can't determine affiliated tasks in this case)
- if (runningTask == null) return;
- // Return early if the running task is in the home stack (optimization)
- if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
-
- // Find the task in the recents list
- ArrayList<Task> tasks = focusedStack.getTasks();
- Task toTask = null;
- ActivityOptions launchOpts = null;
- int taskCount = tasks.size();
- int numAffiliatedTasks = 0;
- for (int i = 0; i < taskCount; i++) {
- Task task = tasks.get(i);
- if (task.key.id == runningTask.id) {
- TaskGrouping group = task.group;
- Task.TaskKey toTaskKey;
- if (showNextTask) {
- toTaskKey = group.getNextTaskInGroup(task);
- launchOpts = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_launch_next_affiliated_task_target,
- R.anim.recents_launch_next_affiliated_task_source);
- } else {
- toTaskKey = group.getPrevTaskInGroup(task);
- launchOpts = ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_launch_prev_affiliated_task_target,
- R.anim.recents_launch_prev_affiliated_task_source);
- }
- if (toTaskKey != null) {
- toTask = focusedStack.findTaskWithId(toTaskKey.id);
- }
- numAffiliatedTasks = group.getTaskCount();
- break;
- }
- }
-
- // Return early if there is no next task
- if (toTask == null) {
- if (numAffiliatedTasks > 1) {
- if (showNextTask) {
- mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
- ActivityOptions.makeCustomInPlaceAnimation(mContext,
- R.anim.recents_launch_next_affiliated_task_bounce));
- } else {
- mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
- ActivityOptions.makeCustomInPlaceAnimation(mContext,
- R.anim.recents_launch_prev_affiliated_task_bounce));
- }
- }
- return;
- }
-
- // Keep track of actually launched affiliated tasks
- MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
-
- // Launch the task
- if (toTask.isActive) {
- // Bring an active task to the foreground
- mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts);
+ int currentUser = mSystemServicesProxy.getCurrentUser();
+ if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ mImpl.cancelPreloadingRecents();
} else {
- mSystemServicesProxy.startActivityFromRecents(mContext, toTask.key.id,
- toTask.activityLabel, launchOpts);
+ if (mSystemUserCallbacks != null) {
+ IRecentsNonSystemUserCallbacks callbacks =
+ mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ if (callbacks != null) {
+ try {
+ callbacks.cancelPreloadingRecents();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ } else {
+ Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
+ }
+ }
}
}
@Override
public void showNextAffiliatedTask() {
- // Keep track of when the affiliated task is triggered
- MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
- showRelativeAffiliatedTask(true);
+ mImpl.showNextAffiliatedTask();
}
@Override
public void showPrevAffiliatedTask() {
- // Keep track of when the affiliated task is triggered
- MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
- showRelativeAffiliatedTask(false);
+ mImpl.showPrevAffiliatedTask();
}
- /** Updates on configuration change. */
- @ProxyFromSystemToCurrentUser
+ /**
+ * Updates on configuration change.
+ */
public void onConfigurationChanged(Configuration newConfig) {
- if (mSystemServicesProxy.isForegroundUserSystem()) {
- configurationChanged();
+ int currentUser = mSystemServicesProxy.getCurrentUser();
+ if (mSystemServicesProxy.isSystemUser(currentUser)) {
+ mImpl.onConfigurationChanged();
} 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;
- mConfig.updateOnConfigurationChange();
- }
-
- /**
- * Prepares the header bar layout for the next transition, if the task view bounds has changed
- * since the last call, it will attempt to re-measure and layout the header bar to the new size.
- *
- * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
- * is not already bound (can be expensive)
- */
- void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
- Rect windowRect = mSystemServicesProxy.getWindowRect();
-
- // Update the configuration for the current state
- mConfig.update(mContext, mSystemServicesProxy, mSystemServicesProxy.getWindowRect());
-
- if (tryAndBindSearchWidget) {
- // Try and pre-emptively bind the search widget on startup to ensure that we
- // have the right thumbnail bounds to animate to.
- // Note: We have to reload the widget id before we get the task stack bounds below
- if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
- mConfig.getSearchBarBounds(windowRect,
- mStatusBarHeight, mSearchBarBounds);
- }
- }
- Rect systemInsets = new Rect(0, mStatusBarHeight,
- (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
- (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
- mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
- mSearchBarBounds, mTaskStackBounds);
-
- // Rebind the header bar and draw it for the transition
- TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
- Rect taskStackBounds = new Rect(mTaskStackBounds);
- algo.setSystemInsets(systemInsets);
- algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
- Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
- if (!taskViewBounds.equals(mLastTaskViewBounds)) {
- mLastTaskViewBounds.set(taskViewBounds);
-
- int taskViewWidth = taskViewBounds.width();
- synchronized (mHeaderBarLock) {
- mHeaderBar.measure(
- View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
- View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
- mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
- }
- }
- }
-
- /** Toggles the recents activity */
- void toggleRecentsActivity() {
- // If the user has toggled it too quickly, then just eat up the event here (it's better than
- // showing a janky screenshot).
- // NOTE: Ideally, the screenshot mechanism would take the window transform into account
- if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
- return;
- }
-
- // If Recents is the front most activity, then we should just communicate with it directly
- // to launch the first task or dismiss itself
- ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
- MutableBoolean isTopTaskHome = new MutableBoolean(true);
- if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
- // Notify recents to toggle itself
- Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
- mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
- mLastToggleTime = SystemClock.elapsedRealtime();
- return;
- } else {
- // Otherwise, start the recents activity
- showRecentsActivity(topTask, isTopTaskHome.value);
- }
- }
-
- /** Shows the recents activity if it is not already running */
- void showRecentsActivity() {
- // Check if the top task is in the home stack, and start the recents activity
- ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
- MutableBoolean isTopTaskHome = new MutableBoolean(true);
- if (topTask == null || !mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
- showRecentsActivity(topTask, isTopTaskHome.value);
- }
- }
-
- /**
- * Creates the activity options for a unknown state->recents transition.
- */
- ActivityOptions getUnknownTransitionActivityOptions() {
- mStartAnimationTriggered = false;
- return ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_from_unknown_enter,
- R.anim.recents_from_unknown_exit,
- mHandler, this);
- }
-
- /**
- * Creates the activity options for a home->recents transition.
- */
- ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
- mStartAnimationTriggered = false;
- if (fromSearchHome) {
- return ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_from_search_launcher_enter,
- R.anim.recents_from_search_launcher_exit,
- mHandler, this);
- }
- return ActivityOptions.makeCustomAnimation(mContext,
- R.anim.recents_from_launcher_enter,
- R.anim.recents_from_launcher_exit,
- mHandler, this);
- }
-
- /**
- * Creates the activity options for an app->recents transition.
- */
- ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
- TaskStack stack, TaskStackView stackView) {
-
- // Update the destination rect
- Task toTask = new Task();
- TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
- topTask.id, toTask);
- Rect toTaskRect = toTransform.rect;
- Bitmap thumbnail;
- if (mThumbnailTransitionBitmapCacheKey != null
- && mThumbnailTransitionBitmapCacheKey.key != null
- && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
- thumbnail = mThumbnailTransitionBitmapCache;
- mThumbnailTransitionBitmapCacheKey = null;
- mThumbnailTransitionBitmapCache = null;
- } else {
- preloadIcon(topTask);
- thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
- }
- if (thumbnail != null) {
- mStartAnimationTriggered = false;
- return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
- thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
- toTaskRect.height(), mHandler, this);
- }
-
- // If both the screenshot and thumbnail fails, then just fall back to the default transition
- return getUnknownTransitionActivityOptions();
- }
-
- /**
- * Preloads the icon of a task.
- */
- void preloadIcon(ActivityManager.RunningTaskInfo task) {
-
- // Ensure that we load the running task's icon
- RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
- launchOpts.runningTaskId = task.id;
- launchOpts.loadThumbnails = false;
- launchOpts.onlyLoadForCache = true;
- RecentsTaskLoader.getInstance().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
- }
-
- /**
- * Caches the header thumbnail used for a window animation asynchronously into
- * {@link #mThumbnailTransitionBitmapCache}.
- */
- void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
- TaskStack stack, TaskStackView stackView, boolean isTopTaskHome) {
- preloadIcon(topTask);
-
- // Update the header bar if necessary
- reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
-
- // Update the destination rect
- mDummyStackView.updateMinMaxScrollForStack(stack);
- final Task toTask = new Task();
- final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
- topTask.id, toTask);
- new AsyncTask<Void, Void, Bitmap>() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- return drawThumbnailTransitionBitmap(toTask, toTransform);
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- mThumbnailTransitionBitmapCache = bitmap;
- mThumbnailTransitionBitmapCacheKey = toTask;
- }
- }.execute();
- }
-
- /**
- * Draws the header of a task used for the window animation into a bitmap.
- */
- Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
- if (toTransform != null && toTask.key != null) {
- Bitmap thumbnail;
- synchronized (mHeaderBarLock) {
- int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
- int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
- thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
- Bitmap.Config.ARGB_8888);
- if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
- thumbnail.eraseColor(0xFFff0000);
- } else {
- Canvas c = new Canvas(thumbnail);
- c.scale(toTransform.scale, toTransform.scale);
- mHeaderBar.rebindToTask(toTask);
- mHeaderBar.draw(c);
- c.setBitmap(null);
- }
- }
- return thumbnail.createAshmemBitmap();
- }
- return null;
- }
-
- /** Returns the transition rect for the given task id. */
- TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView,
- int runningTaskId, Task runningTaskOut) {
- // Find the running task in the TaskStack
- Task task = null;
- ArrayList<Task> tasks = stack.getTasks();
- if (runningTaskId != -1) {
- // Otherwise, try and find the task with the
- int taskCount = tasks.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task t = tasks.get(i);
- if (t.key.id == runningTaskId) {
- task = t;
- runningTaskOut.copyFrom(t);
- break;
- }
- }
- }
- if (task == null) {
- // If no task is specified or we can not find the task just use the front most one
- task = tasks.get(tasks.size() - 1);
- runningTaskOut.copyFrom(task);
- }
-
- // Get the transform for the running task
- stackView.getScroller().setStackScrollToInitialState();
- mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
- stackView.getScroller().getStackScroll(), mTmpTransform, null);
- return mTmpTransform;
- }
-
- /** Shows the recents activity */
- void showRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
- RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
-
- // Update the header bar if necessary
- reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
-
- if (sInstanceLoadPlan == null) {
- // Create a new load plan if onPreloadRecents() was never triggered
- sInstanceLoadPlan = loader.createLoadPlan(mContext);
- }
-
- if (!sInstanceLoadPlan.hasTasks()) {
- loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
- }
- TaskStack stack = sInstanceLoadPlan.getTaskStack();
-
- // Prepare the dummy stack for the transition
- mDummyStackView.updateMinMaxScrollForStack(stack);
- TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
- mDummyStackView.computeStackVisibilityReport();
- boolean hasRecentTasks = stack.getTaskCount() > 0;
- boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
-
- if (useThumbnailTransition) {
- // Try starting with a thumbnail transition
- ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
- mDummyStackView);
- if (opts != null) {
- startRecentsActivity(topTask, opts, false /* fromHome */,
- false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
- } else {
- // Fall through below to the non-thumbnail transition
- useThumbnailTransition = false;
- }
- }
-
- if (!useThumbnailTransition) {
- // If there is no thumbnail transition, but is launching from home into recents, then
- // use a quick home transition and do the animation from home
- if (hasRecentTasks) {
- String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName();
- String searchWidgetPackage =
- Prefs.getString(mContext, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
-
- // Determine whether we are coming from a search owned home activity
- boolean fromSearchHome = (homeActivityPackage != null) &&
- homeActivityPackage.equals(searchWidgetPackage);
- ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
- startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
- false /* fromThumbnail */, stackVr);
- } else {
- // Otherwise we do the normal fade from an unknown source
- ActivityOptions opts = getUnknownTransitionActivityOptions();
- startRecentsActivity(topTask, opts, true /* fromHome */,
- false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
- }
- }
- mLastToggleTime = SystemClock.elapsedRealtime();
- }
-
- /** Starts the recents activity */
- void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
- ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
- TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
- // Update the configuration based on the launch options
- RecentsActivityLaunchState launchState = mConfig.getLaunchState();
- launchState.launchedFromHome = fromSearchHome || fromHome;
- launchState.launchedFromSearchHome = fromSearchHome;
- launchState.launchedFromAppWithThumbnail = fromThumbnail;
- launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
- launchState.launchedWithAltTab = mTriggeredFromAltTab;
- launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
- launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
- launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
- launchState.launchedHasConfigurationChanged = false;
-
- Intent intent = new Intent(sToggleRecentsAction);
- intent.setClassName(sRecentsPackage, sRecentsActivity);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
- | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
- if (opts != null) {
- mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
- } else {
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- }
- mCanReuseTaskStackViews = true;
- }
-
- /** Notifies the callbacks that the visibility of Recents has changed. */
- @ProxyFromAnyToSystemUser
- public static void notifyVisibilityChanged(Context context, SystemServicesProxy ssp,
- boolean visible) {
- if (ssp.isForegroundUserSystem()) {
- visibilityChanged(context, visible);
- } else {
- Intent intent = createLocalBroadcastIntent(context,
- ACTION_PROXY_NOTIFY_RECENTS_VISIBLITY_TO_OWNER);
- intent.putExtra(EXTRA_RECENTS_VISIBILITY, visible);
- context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
- }
- }
- static void visibilityChanged(Context context, boolean visible) {
- // For the primary user, the context for the SystemUI component is the SystemUIApplication
- SystemUIApplication app = (SystemUIApplication)
- getInstanceAndStartIfNeeded(context.getApplicationContext()).mContext;
- PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
- if (statusBar != null) {
- statusBar.updateRecentsVisibility(visible);
- }
- }
-
- /** Notifies the status bar to trigger screen pinning. */
- @ProxyFromAnyToSystemUser
- public static void startScreenPinning(Context context, SystemServicesProxy ssp) {
- if (ssp.isForegroundUserSystem()) {
- onStartScreenPinning(context);
- } else {
- Intent intent = createLocalBroadcastIntent(context,
- ACTION_PROXY_SCREEN_PINNING_REQUEST_TO_OWNER);
- context.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
- }
- }
- static void onStartScreenPinning(Context context) {
- // For the primary user, the context for the SystemUI component is the SystemUIApplication
- SystemUIApplication app = (SystemUIApplication)
- getInstanceAndStartIfNeeded(context.getApplicationContext()).mContext;
- PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
- if (statusBar != null) {
- statusBar.showScreenPinningRequest(false);
- }
- }
-
- /**
- * Returns the preloaded load plan and invalidates it.
- */
- public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
- RecentsTaskLoadPlan plan = sInstanceLoadPlan;
- sInstanceLoadPlan = null;
- return plan;
- }
-
- /**** OnAnimationStartedListener Implementation ****/
-
- @Override
- public void onAnimationStarted() {
- // Notify recents to start the enter animation
- if (!mStartAnimationTriggered) {
- // There can be a race condition between the start animation callback and
- // the start of the new activity (where we register the receiver that listens
- // to this broadcast, so we add our own receiver and if that gets called, then
- // we know the activity has not yet started and we can retry sending the broadcast.
- BroadcastReceiver fallbackReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (getResultCode() == Activity.RESULT_OK) {
- mStartAnimationTriggered = true;
- return;
+ if (mSystemUserCallbacks != null) {
+ IRecentsNonSystemUserCallbacks callbacks =
+ mSystemUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
+ if (callbacks != null) {
+ try {
+ callbacks.onConfigurationChanged();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
}
-
- // Schedule for the broadcast to be sent again after some time
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- onAnimationStarted();
- }
- }, 25);
+ } else {
+ Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
}
- };
-
- // Send the broadcast to notify Recents that the animation has started
- Intent intent = createLocalBroadcastIntent(mContext, ACTION_START_ENTER_ANIMATION);
- mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT, null,
- fallbackReceiver, null, Activity.RESULT_CANCELED, null, null);
+ }
}
}
+
+ /**
+ * Handle Recents activity visibility changed.
+ */
+ public final void onBusEvent(final RecentsVisibilityChangedEvent event) {
+ int processUser = event.systemServicesProxy.getProcessUser();
+ if (event.systemServicesProxy.isSystemUser(processUser)) {
+ mImpl.onVisibilityChanged(event.applicationContext, event.visible);
+ } else {
+ postToSystemUser(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mCallbacksToSystemUser.updateRecentsVisibility(event.visible);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Handle screen pinning request.
+ */
+ public final void onBusEvent(final ScreenPinningRequestEvent event) {
+ int processUser = event.systemServicesProxy.getProcessUser();
+ if (event.systemServicesProxy.isSystemUser(processUser)) {
+ mImpl.onStartScreenPinning(event.applicationContext);
+ } else {
+ postToSystemUser(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mCallbacksToSystemUser.startScreenPinning();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback failed", e);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Attempts to register with the system user.
+ */
+ private void registerWithSystemUser() {
+ final int processUser = mSystemServicesProxy.getProcessUser();
+ postToSystemUser(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mCallbacksToSystemUser.registerNonSystemUserCallbacks(mImpl, processUser);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Runs the runnable in the system user's Recents context, connecting to the service if
+ * necessary.
+ */
+ private void postToSystemUser(final Runnable onConnectRunnable) {
+ mOnConnectRunnables.add(onConnectRunnable);
+ if (mCallbacksToSystemUser == null) {
+ Intent systemUserServiceIntent = new Intent();
+ systemUserServiceIntent.setClass(mContext, RecentsSystemUserService.class);
+ boolean bound = mContext.bindServiceAsUser(systemUserServiceIntent,
+ mServiceConnectionToSystemUser, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+ if (!bound) {
+ // Retry after a fixed duration
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ registerWithSystemUser();
+ }
+ }, BIND_TO_SYSTEM_USER_RETRY_DELAY);
+ }
+ } else {
+ runAndFlushOnConnectRunnables();
+ }
+ }
+
+ /**
+ * Runs all the queued runnables after a service connection is made.
+ */
+ private void runAndFlushOnConnectRunnables() {
+ for (Runnable r : mOnConnectRunnables) {
+ r.run();
+ }
+ mOnConnectRunnables.clear();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index e647c1f..fa1c887 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -39,6 +39,8 @@
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.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.ui.DismissTaskEvent;
import com.android.systemui.recents.events.ui.ResizeTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
@@ -127,20 +129,20 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (action.equals(Recents.ACTION_HIDE_RECENTS_ACTIVITY)) {
- if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
+ if (action.equals(RecentsImpl.ACTION_HIDE_RECENTS_ACTIVITY)) {
+ if (intent.getBooleanExtra(RecentsImpl.EXTRA_TRIGGERED_FROM_ALT_TAB, false)) {
// If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
dismissRecentsToFocusedTaskOrHome(false);
- } else if (intent.getBooleanExtra(Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) {
+ } else if (intent.getBooleanExtra(RecentsImpl.EXTRA_TRIGGERED_FROM_HOME_KEY, false)) {
// Otherwise, dismiss Recents to Home
dismissRecentsToHome(true);
} else {
// Do nothing
}
- } else if (action.equals(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
+ } else if (action.equals(RecentsImpl.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
// If we are toggling Recents, then first unfilter any filtered stacks first
dismissRecentsToFocusedTaskOrHome(true);
- } else if (action.equals(Recents.ACTION_START_ENTER_ANIMATION)) {
+ } else if (action.equals(RecentsImpl.ACTION_START_ENTER_ANIMATION)) {
// Trigger the enter animation
onEnterAnimationTriggered();
// Notify the fallback receiver that we have successfully got the broadcast
@@ -174,7 +176,7 @@
// If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
// reconstructing the task stack
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
- RecentsTaskLoadPlan plan = Recents.consumeInstanceLoadPlan();
+ RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan();
if (plan == null) {
plan = loader.createLoadPlan(this);
}
@@ -371,13 +373,15 @@
MetricsLogger.visible(this, MetricsLogger.OVERVIEW_ACTIVITY);
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
- Recents.notifyVisibilityChanged(this, ssp, true);
+
+ // Notify that recents is now visible
+ EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
// Register the broadcast receiver to handle messages from our service
IntentFilter filter = new IntentFilter();
- filter.addAction(Recents.ACTION_HIDE_RECENTS_ACTIVITY);
- filter.addAction(Recents.ACTION_TOGGLE_RECENTS_ACTIVITY);
- filter.addAction(Recents.ACTION_START_ENTER_ANIMATION);
+ filter.addAction(RecentsImpl.ACTION_HIDE_RECENTS_ACTIVITY);
+ filter.addAction(RecentsImpl.ACTION_TOGGLE_RECENTS_ACTIVITY);
+ filter.addAction(RecentsImpl.ACTION_START_ENTER_ANIMATION);
registerReceiver(mServiceBroadcastReceiver, filter);
// Register any broadcast receivers for the task loader
@@ -415,7 +419,9 @@
MetricsLogger.hidden(this, MetricsLogger.OVERVIEW_ACTIVITY);
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
- Recents.notifyVisibilityChanged(this, ssp, false);
+
+ // Notify that recents is now hidden
+ EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, false));
// Notify the views that we are no longer visible
mRecentsView.onRecentsHidden();
@@ -564,7 +570,7 @@
public void onScreenPinningRequest() {
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
- Recents.startScreenPinning(this, ssp);
+ EventBus.getDefault().send(new ScreenPinningRequestEvent(this, ssp));
MetricsLogger.count(this, "overview_screen_pinned", 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
new file mode 100644
index 0000000..2c76087
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -0,0 +1,769 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ITaskStackListener;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.MutableBoolean;
+import android.view.LayoutInflater;
+import android.view.View;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.SystemUIApplication;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.recents.model.RecentsTaskLoader;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskGrouping;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.TaskStackView;
+import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskViewHeader;
+import com.android.systemui.recents.views.TaskViewTransform;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+import java.util.ArrayList;
+
+/**
+ * An implementation of the Recents component for the current user. For secondary users, this can
+ * be called remotely from the system user.
+ */
+public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
+ implements ActivityOptions.OnAnimationStartedListener {
+
+ private final static String TAG = "RecentsImpl";
+
+ final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "triggeredFromAltTab";
+ final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "triggeredFromHomeKey";
+
+ final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
+ final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
+ final public static String ACTION_HIDE_RECENTS_ACTIVITY = "action_hide_recents_activity";
+
+ final static int sMinToggleDelay = 350;
+
+ public final static String RECENTS_PACKAGE = "com.android.systemui";
+ public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
+
+
+ /**
+ * 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 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() {
+ // TODO: Temporarily skip this if multi stack is enabled
+ /*
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ SystemServicesProxy ssp = loader.getSystemServicesProxy();
+ ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
+
+ // Load the next task only if we aren't svelte
+ RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+ loader.preloadTasks(plan, true);
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+ // This callback is made when a new activity is launched and the old one is paused
+ // so ignore the current activity and try and preload the thumbnail for the
+ // previous one.
+ if (runningTaskInfo != null) {
+ launchOpts.runningTaskId = runningTaskInfo.id;
+ }
+ launchOpts.numVisibleTasks = 2;
+ launchOpts.numVisibleTaskThumbnails = 2;
+ launchOpts.onlyLoadForCache = true;
+ launchOpts.onlyLoadPausedActivities = true;
+ loader.loadTasks(mContext, plan, launchOpts);
+ }
+ */
+ }
+ }
+
+ static RecentsTaskLoadPlan sInstanceLoadPlan;
+
+ Context mContext;
+ SystemServicesProxy mSystemServicesProxy;
+ Handler mHandler;
+ TaskStackListenerImpl mTaskStackListener;
+ RecentsAppWidgetHost mAppWidgetHost;
+ boolean mBootCompleted;
+ boolean mStartAnimationTriggered;
+ boolean mCanReuseTaskStackViews = true;
+
+ // Task launching
+ RecentsConfiguration mConfig;
+ Rect mSearchBarBounds = new Rect();
+ Rect mTaskStackBounds = new Rect();
+ Rect mLastTaskViewBounds = new Rect();
+ TaskViewTransform mTmpTransform = new TaskViewTransform();
+ int mStatusBarHeight;
+ int mNavBarHeight;
+ int mNavBarWidth;
+ int mTaskBarHeight;
+
+ // Header (for transition)
+ TaskViewHeader mHeaderBar;
+ final Object mHeaderBarLock = new Object();
+ TaskStackView mDummyStackView;
+
+ // Variables to keep track of if we need to start recents after binding
+ boolean mTriggeredFromAltTab;
+ long mLastToggleTime;
+
+ Bitmap mThumbnailTransitionBitmapCache;
+ Task mThumbnailTransitionBitmapCacheKey;
+
+
+ public RecentsImpl(Context context) {
+ mContext = context;
+ mSystemServicesProxy = new SystemServicesProxy(mContext);
+ mHandler = new Handler();
+ mAppWidgetHost = new RecentsAppWidgetHost(mContext, Constants.Values.App.AppWidgetHostId);
+ Resources res = mContext.getResources();
+ RecentsTaskLoader.initialize(mContext);
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+
+ // Register the task stack listener
+ mTaskStackListener = new TaskStackListenerImpl(mHandler);
+ mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+
+ // Initialize the static configuration resources
+ mConfig = RecentsConfiguration.initialize(mContext, mSystemServicesProxy);
+ mStatusBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+ mNavBarHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_height);
+ mNavBarWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.navigation_bar_width);
+ mTaskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
+ mDummyStackView = new TaskStackView(mContext, new TaskStack());
+ mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
+ null, false);
+ reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
+
+ // 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.
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+ loader.preloadTasks(plan, true /* isTopTaskHome */);
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+ launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
+ launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
+ launchOpts.onlyLoadForCache = true;
+ loader.loadTasks(mContext, plan, launchOpts);
+ }
+
+ public void onBootCompleted() {
+ mBootCompleted = true;
+ reloadHeaderBarLayout(true /* tryAndBindSearchWidget */);
+ }
+
+ @Override
+ public void onConfigurationChanged() {
+ // Don't reuse task stack views if the configuration changes
+ mCanReuseTaskStackViews = false;
+ mConfig.updateOnConfigurationChange();
+ }
+
+ /**
+ * This is only called from the system user's Recents. Secondary users will instead proxy their
+ * visibility change events through to the system user via
+ * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
+ */
+ public void onVisibilityChanged(Context context, boolean visible) {
+ SystemUIApplication app = (SystemUIApplication) context;
+ PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
+ if (statusBar != null) {
+ statusBar.updateRecentsVisibility(visible);
+ }
+ }
+
+ /**
+ * This is only called from the system user's Recents. Secondary users will instead proxy their
+ * visibility change events through to the system user via
+ * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
+ */
+ public void onStartScreenPinning(Context context) {
+ SystemUIApplication app = (SystemUIApplication) context;
+ PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
+ if (statusBar != null) {
+ statusBar.showScreenPinningRequest(false);
+ }
+ }
+
+ @Override
+ public void showRecents(boolean triggeredFromAltTab) {
+ mTriggeredFromAltTab = triggeredFromAltTab;
+
+ try {
+ // Check if the top task is in the home stack, and start the recents activity
+ ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+ MutableBoolean isTopTaskHome = new MutableBoolean(true);
+ if (topTask == null || !mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
+ startRecentsActivity(topTask, isTopTaskHome.value);
+ }
+ } catch (ActivityNotFoundException e) {
+ Console.logRawError("Failed to launch RecentAppsIntent", e);
+ }
+ }
+
+ @Override
+ public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
+ if (mBootCompleted) {
+ // Defer to the activity to handle hiding recents, if it handles it, then it must still
+ // be visible
+ 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);
+ }
+ }
+
+ @Override
+ public void toggleRecents() {
+ mTriggeredFromAltTab = false;
+
+ try {
+ // If the user has toggled it too quickly, then just eat up the event here (it's better
+ // than showing a janky screenshot).
+ // NOTE: Ideally, the screenshot mechanism would take the window transform into account
+ if ((SystemClock.elapsedRealtime() - mLastToggleTime) < sMinToggleDelay) {
+ return;
+ }
+
+ // If Recents is the front most activity, then we should just communicate with it
+ // directly to launch the first task or dismiss itself
+ ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+ MutableBoolean isTopTaskHome = new MutableBoolean(true);
+ if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
+ // Notify recents to toggle itself
+ Intent intent = createLocalBroadcastIntent(mContext, ACTION_TOGGLE_RECENTS_ACTIVITY);
+ mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+ mLastToggleTime = SystemClock.elapsedRealtime();
+ return;
+ } else {
+ // Otherwise, start the recents activity
+ startRecentsActivity(topTask, isTopTaskHome.value);
+ }
+ } catch (ActivityNotFoundException e) {
+ Console.logRawError("Failed to launch RecentAppsIntent", e);
+ }
+ }
+
+ @Override
+ public void preloadRecents() {
+ // Preload only the raw task list into a new load plan (which will be consumed by the
+ // RecentsActivity) only if there is a task to animate to.
+ ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+ MutableBoolean topTaskHome = new MutableBoolean(true);
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ sInstanceLoadPlan = loader.createLoadPlan(mContext);
+ if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
+ sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
+ loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
+ TaskStack stack = sInstanceLoadPlan.getTaskStack();
+ if (stack.getTaskCount() > 0) {
+ preCacheThumbnailTransitionBitmapAsync(topTask, stack, mDummyStackView);
+ }
+ }
+ }
+
+ @Override
+ public void cancelPreloadingRecents() {
+ // Do nothing
+ }
+
+ public void showRelativeAffiliatedTask(boolean showNextTask) {
+ // Return early if there is no focused stack
+ int focusedStackId = mSystemServicesProxy.getFocusedStack();
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+ RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
+ loader.preloadTasks(plan, true /* isTopTaskHome */);
+ TaskStack focusedStack = plan.getTaskStack();
+
+ // Return early if there are no tasks in the focused stack
+ if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
+
+ ActivityManager.RunningTaskInfo runningTask = mSystemServicesProxy.getTopMostTask();
+ // Return early if there is no running task (can't determine affiliated tasks in this case)
+ if (runningTask == null) return;
+ // Return early if the running task is in the home stack (optimization)
+ if (mSystemServicesProxy.isInHomeStack(runningTask.id)) return;
+
+ // Find the task in the recents list
+ ArrayList<Task> tasks = focusedStack.getTasks();
+ Task toTask = null;
+ ActivityOptions launchOpts = null;
+ int taskCount = tasks.size();
+ int numAffiliatedTasks = 0;
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ if (task.key.id == runningTask.id) {
+ TaskGrouping group = task.group;
+ Task.TaskKey toTaskKey;
+ if (showNextTask) {
+ toTaskKey = group.getNextTaskInGroup(task);
+ launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_launch_next_affiliated_task_target,
+ R.anim.recents_launch_next_affiliated_task_source);
+ } else {
+ toTaskKey = group.getPrevTaskInGroup(task);
+ launchOpts = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_launch_prev_affiliated_task_target,
+ R.anim.recents_launch_prev_affiliated_task_source);
+ }
+ if (toTaskKey != null) {
+ toTask = focusedStack.findTaskWithId(toTaskKey.id);
+ }
+ numAffiliatedTasks = group.getTaskCount();
+ break;
+ }
+ }
+
+ // Return early if there is no next task
+ if (toTask == null) {
+ if (numAffiliatedTasks > 1) {
+ if (showNextTask) {
+ mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
+ ActivityOptions.makeCustomInPlaceAnimation(mContext,
+ R.anim.recents_launch_next_affiliated_task_bounce));
+ } else {
+ mSystemServicesProxy.startInPlaceAnimationOnFrontMostApplication(
+ ActivityOptions.makeCustomInPlaceAnimation(mContext,
+ R.anim.recents_launch_prev_affiliated_task_bounce));
+ }
+ }
+ return;
+ }
+
+ // Keep track of actually launched affiliated tasks
+ MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
+
+ // Launch the task
+ if (toTask.isActive) {
+ // Bring an active task to the foreground
+ mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts);
+ } else {
+ mSystemServicesProxy.startActivityFromRecents(mContext, toTask.key.id,
+ toTask.activityLabel, launchOpts);
+ }
+ }
+
+ public void showNextAffiliatedTask() {
+ // Keep track of when the affiliated task is triggered
+ MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
+ showRelativeAffiliatedTask(true);
+ }
+
+ public void showPrevAffiliatedTask() {
+ // Keep track of when the affiliated task is triggered
+ MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
+ showRelativeAffiliatedTask(false);
+ }
+
+ /**
+ * Returns the preloaded load plan and invalidates it.
+ */
+ public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
+ RecentsTaskLoadPlan plan = sInstanceLoadPlan;
+ sInstanceLoadPlan = null;
+ return plan;
+ }
+
+ /**
+ * Prepares the header bar layout for the next transition, if the task view bounds has changed
+ * since the last call, it will attempt to re-measure and layout the header bar to the new size.
+ *
+ * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
+ * is not already bound (can be expensive)
+ */
+ private void reloadHeaderBarLayout(boolean tryAndBindSearchWidget) {
+ Rect windowRect = mSystemServicesProxy.getWindowRect();
+
+ // Update the configuration for the current state
+ mConfig.update(mContext, mSystemServicesProxy, mSystemServicesProxy.getWindowRect());
+
+ if (tryAndBindSearchWidget) {
+ // Try and pre-emptively bind the search widget on startup to ensure that we
+ // have the right thumbnail bounds to animate to.
+ // Note: We have to reload the widget id before we get the task stack bounds below
+ if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
+ mConfig.getSearchBarBounds(windowRect,
+ mStatusBarHeight, mSearchBarBounds);
+ }
+ }
+ Rect systemInsets = new Rect(0, mStatusBarHeight,
+ (mConfig.hasTransposedNavBar ? mNavBarWidth : 0),
+ (mConfig.hasTransposedNavBar ? 0 : mNavBarHeight));
+ mConfig.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
+ mSearchBarBounds, mTaskStackBounds);
+
+ // Rebind the header bar and draw it for the transition
+ TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
+ Rect taskStackBounds = new Rect(mTaskStackBounds);
+ algo.setSystemInsets(systemInsets);
+ algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds);
+ Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
+ if (!taskViewBounds.equals(mLastTaskViewBounds)) {
+ mLastTaskViewBounds.set(taskViewBounds);
+
+ int taskViewWidth = taskViewBounds.width();
+ synchronized (mHeaderBarLock) {
+ mHeaderBar.measure(
+ View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
+ mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
+ }
+ }
+ }
+
+ /**
+ * Preloads the icon of a task.
+ */
+ private void preloadIcon(ActivityManager.RunningTaskInfo task) {
+
+ // Ensure that we load the running task's icon
+ RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+ launchOpts.runningTaskId = task.id;
+ launchOpts.loadThumbnails = false;
+ launchOpts.onlyLoadForCache = true;
+ RecentsTaskLoader.getInstance().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
+ }
+
+ /**
+ * Caches the header thumbnail used for a window animation asynchronously into
+ * {@link #mThumbnailTransitionBitmapCache}.
+ */
+ private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
+ TaskStack stack, TaskStackView stackView) {
+ preloadIcon(topTask);
+
+ // Update the header bar if necessary
+ reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
+
+ // Update the destination rect
+ mDummyStackView.updateMinMaxScrollForStack(stack);
+ final Task toTask = new Task();
+ final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
+ topTask.id, toTask);
+ new AsyncTask<Void, Void, Bitmap>() {
+ @Override
+ protected Bitmap doInBackground(Void... params) {
+ return drawThumbnailTransitionBitmap(toTask, toTransform);
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ mThumbnailTransitionBitmapCache = bitmap;
+ mThumbnailTransitionBitmapCacheKey = toTask;
+ }
+ }.execute();
+ }
+
+ /**
+ * Creates the activity options for a unknown state->recents transition.
+ */
+ private ActivityOptions getUnknownTransitionActivityOptions() {
+ mStartAnimationTriggered = false;
+ return ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_from_unknown_enter,
+ R.anim.recents_from_unknown_exit,
+ mHandler, this);
+ }
+
+ /**
+ * Creates the activity options for a home->recents transition.
+ */
+ private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
+ mStartAnimationTriggered = false;
+ if (fromSearchHome) {
+ return ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_from_search_launcher_enter,
+ R.anim.recents_from_search_launcher_exit,
+ mHandler, this);
+ }
+ return ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.recents_from_launcher_enter,
+ R.anim.recents_from_launcher_exit,
+ mHandler, this);
+ }
+
+ /**
+ * Creates the activity options for an app->recents transition.
+ */
+ private ActivityOptions getThumbnailTransitionActivityOptions(
+ ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
+
+ // Update the destination rect
+ Task toTask = new Task();
+ TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
+ topTask.id, toTask);
+ Rect toTaskRect = toTransform.rect;
+ Bitmap thumbnail;
+ if (mThumbnailTransitionBitmapCacheKey != null
+ && mThumbnailTransitionBitmapCacheKey.key != null
+ && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
+ thumbnail = mThumbnailTransitionBitmapCache;
+ mThumbnailTransitionBitmapCacheKey = null;
+ mThumbnailTransitionBitmapCache = null;
+ } else {
+ preloadIcon(topTask);
+ thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
+ }
+ if (thumbnail != null) {
+ mStartAnimationTriggered = false;
+ return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
+ thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
+ toTaskRect.height(), mHandler, this);
+ }
+
+ // If both the screenshot and thumbnail fails, then just fall back to the default transition
+ return getUnknownTransitionActivityOptions();
+ }
+
+ /**
+ * Returns the transition rect for the given task id.
+ */
+ private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
+ TaskStackView stackView, int runningTaskId, Task runningTaskOut) {
+ // Find the running task in the TaskStack
+ Task task = null;
+ ArrayList<Task> tasks = stack.getTasks();
+ if (runningTaskId != -1) {
+ // Otherwise, try and find the task with the
+ int taskCount = tasks.size();
+ for (int i = taskCount - 1; i >= 0; i--) {
+ Task t = tasks.get(i);
+ if (t.key.id == runningTaskId) {
+ task = t;
+ runningTaskOut.copyFrom(t);
+ break;
+ }
+ }
+ }
+ if (task == null) {
+ // If no task is specified or we can not find the task just use the front most one
+ task = tasks.get(tasks.size() - 1);
+ runningTaskOut.copyFrom(task);
+ }
+
+ // Get the transform for the running task
+ stackView.getScroller().setStackScrollToInitialState();
+ mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
+ stackView.getScroller().getStackScroll(), mTmpTransform, null);
+ return mTmpTransform;
+ }
+
+ /**
+ * Draws the header of a task used for the window animation into a bitmap.
+ */
+ private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
+ if (toTransform != null && toTask.key != null) {
+ Bitmap thumbnail;
+ synchronized (mHeaderBarLock) {
+ int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
+ int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
+ thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
+ Bitmap.Config.ARGB_8888);
+ if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+ thumbnail.eraseColor(0xFFff0000);
+ } else {
+ Canvas c = new Canvas(thumbnail);
+ c.scale(toTransform.scale, toTransform.scale);
+ mHeaderBar.rebindToTask(toTask);
+ mHeaderBar.draw(c);
+ c.setBitmap(null);
+ }
+ }
+ return thumbnail.createAshmemBitmap();
+ }
+ return null;
+ }
+
+ /**
+ * Shows the recents activity
+ */
+ private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+ boolean isTopTaskHome) {
+ RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
+
+ // Update the header bar if necessary
+ reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
+
+ if (sInstanceLoadPlan == null) {
+ // Create a new load plan if onPreloadRecents() was never triggered
+ sInstanceLoadPlan = loader.createLoadPlan(mContext);
+ }
+
+ if (!sInstanceLoadPlan.hasTasks()) {
+ loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
+ }
+ TaskStack stack = sInstanceLoadPlan.getTaskStack();
+
+ // Prepare the dummy stack for the transition
+ mDummyStackView.updateMinMaxScrollForStack(stack);
+ TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
+ mDummyStackView.computeStackVisibilityReport();
+ boolean hasRecentTasks = stack.getTaskCount() > 0;
+ boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
+
+ if (useThumbnailTransition) {
+ // Try starting with a thumbnail transition
+ ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
+ mDummyStackView);
+ if (opts != null) {
+ startRecentsActivity(topTask, opts, false /* fromHome */,
+ false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
+ } else {
+ // Fall through below to the non-thumbnail transition
+ useThumbnailTransition = false;
+ }
+ }
+
+ if (!useThumbnailTransition) {
+ // If there is no thumbnail transition, but is launching from home into recents, then
+ // use a quick home transition and do the animation from home
+ if (hasRecentTasks) {
+ String homeActivityPackage = mSystemServicesProxy.getHomeActivityPackageName();
+ String searchWidgetPackage =
+ Prefs.getString(mContext, Prefs.Key.SEARCH_APP_WIDGET_PACKAGE, null);
+
+ // Determine whether we are coming from a search owned home activity
+ boolean fromSearchHome = (homeActivityPackage != null) &&
+ homeActivityPackage.equals(searchWidgetPackage);
+ ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
+ startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
+ false /* fromThumbnail */, stackVr);
+ } else {
+ // Otherwise we do the normal fade from an unknown source
+ ActivityOptions opts = getUnknownTransitionActivityOptions();
+ startRecentsActivity(topTask, opts, true /* fromHome */,
+ false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
+ }
+ }
+ mLastToggleTime = SystemClock.elapsedRealtime();
+ }
+
+ /**
+ * Starts the recents activity.
+ */
+ private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
+ ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
+ TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
+ // Update the configuration based on the launch options
+ RecentsActivityLaunchState launchState = mConfig.getLaunchState();
+ launchState.launchedFromHome = fromSearchHome || fromHome;
+ launchState.launchedFromSearchHome = fromSearchHome;
+ launchState.launchedFromAppWithThumbnail = fromThumbnail;
+ launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
+ launchState.launchedWithAltTab = mTriggeredFromAltTab;
+ launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
+ launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
+ launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
+ launchState.launchedHasConfigurationChanged = false;
+
+ Intent intent = new Intent();
+ intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ if (opts != null) {
+ mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
+ } else {
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+ mCanReuseTaskStackViews = true;
+ }
+
+ /**
+ * Creates a new broadcast intent to send to the Recents activity.
+ * TODO: Use EventBus
+ */
+ private 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;
+ }
+
+ /**** OnAnimationStartedListener Implementation ****/
+
+ @Override
+ public void onAnimationStarted() {
+ // Notify recents to start the enter animation
+ // TODO: Use EventBus
+ if (!mStartAnimationTriggered) {
+ // There can be a race condition between the start animation callback and
+ // the start of the new activity (where we register the receiver that listens
+ // to this broadcast, so we add our own receiver and if that gets called, then
+ // we know the activity has not yet started and we can retry sending the broadcast.
+ BroadcastReceiver fallbackReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (getResultCode() == Activity.RESULT_OK) {
+ mStartAnimationTriggered = true;
+ return;
+ }
+
+ // Schedule for the broadcast to be sent again after some time
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ onAnimationStarted();
+ }
+ }, 25);
+ }
+ };
+
+ // Send the broadcast to notify Recents that the animation has started
+ 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/RecentsSystemUser.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
new file mode 100644
index 0000000..fb21500
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+
+/**
+ * An implementation of the system user's Recents interface to be called remotely by secondary
+ * users.
+ */
+public class RecentsSystemUser extends IRecentsSystemUserCallbacks.Stub {
+
+ private static final String TAG = "RecentsSystemUser";
+
+ private Context mContext;
+ private RecentsImpl mImpl;
+ private final SparseArray<IRecentsNonSystemUserCallbacks> mNonSystemUserRecents =
+ new SparseArray<>();
+
+ public RecentsSystemUser(Context context, RecentsImpl impl) {
+ mContext = context;
+ mImpl = impl;
+ }
+
+ @Override
+ public void registerNonSystemUserCallbacks(final IBinder nonSystemUserCallbacks, int userId) {
+ try {
+ final IRecentsNonSystemUserCallbacks callback =
+ IRecentsNonSystemUserCallbacks.Stub.asInterface(nonSystemUserCallbacks);
+ nonSystemUserCallbacks.linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ mNonSystemUserRecents.removeAt(mNonSystemUserRecents.indexOfValue(callback));
+ }
+ }, 0);
+ mNonSystemUserRecents.put(userId, callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register NonSystemUserCallbacks", e);
+ }
+ }
+
+ public IRecentsNonSystemUserCallbacks getNonSystemUserRecentsForUser(int userId) {
+ return mNonSystemUserRecents.get(userId);
+ }
+
+ @Override
+ public void updateRecentsVisibility(boolean visible) {
+ mImpl.onVisibilityChanged(mContext, visible);
+ }
+
+ @Override
+ public void startScreenPinning() {
+ mImpl.onStartScreenPinning(mContext);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java
new file mode 100644
index 0000000..39d0d59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUserService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 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.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+import com.android.systemui.SystemUIApplication;
+
+/**
+ * A strictly system-user service that is started by the secondary user's Recents (with a limited
+ * lifespan), to get the interface that the secondary user's Recents can call through to the system
+ * user's Recents.
+ */
+public class RecentsSystemUserService extends Service {
+
+ private static final String TAG = "RecentsSystemUserService";
+ private static final boolean DEBUG = false;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ SystemUIApplication app = (SystemUIApplication) getApplication();
+ Recents recents = app.getComponent(Recents.class);
+ if (DEBUG) {
+ Log.d(TAG, "onBind: " + recents);
+ }
+ if (recents != null) {
+ return recents.getSystemUserCallbacks();
+ }
+ return null;
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
deleted file mode 100644
index 5eefbc7..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsUserEventProxyReceiver.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-
-/**
- * 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) {
- Recents recents = Recents.getInstanceAndStartIfNeeded(context);
- switch (intent.getAction()) {
- case ACTION_PROXY_SHOW_RECENTS_TO_USER: {
- boolean triggeredFromAltTab = intent.getBooleanExtra(
- Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
- recents.showRecentsInternal(triggeredFromAltTab);
- break;
- }
- case ACTION_PROXY_HIDE_RECENTS_TO_USER: {
- boolean triggeredFromAltTab = intent.getBooleanExtra(
- Recents.EXTRA_TRIGGERED_FROM_ALT_TAB, false);
- boolean triggeredFromHome = intent.getBooleanExtra(
- Recents.EXTRA_TRIGGERED_FROM_HOME_KEY, false);
- recents.hideRecentsInternal(triggeredFromAltTab, triggeredFromHome);
- break;
- }
- case ACTION_PROXY_TOGGLE_RECENTS_TO_USER:
- recents.toggleRecentsInternal();
- break;
- case ACTION_PROXY_PRELOAD_RECENTS_TO_USER:
- recents.preloadRecentsInternal();
- break;
- case ACTION_PROXY_CONFIG_CHANGE_TO_USER:
- recents.configurationChanged();
- break;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
new file mode 100644
index 0000000..898d1fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/RecentsVisibilityChangedEvent.java
@@ -0,0 +1,38 @@
+/*
+ * 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.component;
+
+import android.content.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+
+/**
+ * This is sent when the visibility of the RecentsActivity for the current user changes.
+ */
+public class RecentsVisibilityChangedEvent extends EventBus.Event {
+
+ public final Context applicationContext;
+ public final SystemServicesProxy systemServicesProxy;
+ public final boolean visible;
+
+ public RecentsVisibilityChangedEvent(Context context, SystemServicesProxy systemServicesProxy,
+ boolean visible) {
+ this.applicationContext = context.getApplicationContext();
+ this.systemServicesProxy = systemServicesProxy;
+ this.visible = visible;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
new file mode 100644
index 0000000..5cb4ccf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
@@ -0,0 +1,35 @@
+/*
+ * 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.component;
+
+import android.content.Context;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+
+/**
+ * This is sent when we want to start screen pinning.
+ */
+public class ScreenPinningRequestEvent extends EventBus.Event {
+
+ public final Context applicationContext;
+ public final SystemServicesProxy systemServicesProxy;
+
+ public ScreenPinningRequestEvent(Context context, SystemServicesProxy systemServicesProxy) {
+ this.applicationContext = context.getApplicationContext();
+ this.systemServicesProxy = systemServicesProxy;
+ }
+}
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 568d2b1..dab2c65 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -16,9 +16,6 @@
package com.android.systemui.recents.misc;
-import static android.app.ActivityManager.DOCKED_STACK_ID;
-import static android.app.ActivityManager.INVALID_STACK_ID;
-
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
@@ -55,6 +52,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
import android.util.MutableBoolean;
@@ -66,7 +64,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
-import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsImpl;
import java.io.IOException;
import java.util.ArrayList;
@@ -74,6 +72,9 @@
import java.util.List;
import java.util.Random;
+import static android.app.ActivityManager.DOCKED_STACK_ID;
+import static android.app.ActivityManager.INVALID_STACK_ID;
+
/**
* Acts as a shim around the real system services that we need to access data from, and provides
* a point of injection when testing UI.
@@ -100,6 +101,7 @@
IPackageManager mIpm;
AssistUtils mAssistUtils;
WindowManager mWm;
+ UserManager mUm;
Display mDisplay;
String mRecentsPackage;
ComponentName mAssistComponent;
@@ -122,6 +124,7 @@
mIpm = AppGlobals.getPackageManager();
mAssistUtils = new AssistUtils(context);
mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mUm = UserManager.get(context);
mDisplay = mWm.getDefaultDisplay();
mRecentsPackage = context.getPackageName();
mBgThreadHandler = new Handler(sBgThread.getLooper());
@@ -244,8 +247,8 @@
ComponentName topActivity = topTask.topActivity;
// Check if the front most activity is recents
- if (topActivity.getPackageName().equals(Recents.sRecentsPackage) &&
- topActivity.getClassName().equals(Recents.sRecentsActivity)) {
+ if (topActivity.getPackageName().equals(RecentsImpl.RECENTS_PACKAGE) &&
+ topActivity.getClassName().equals(RecentsImpl.RECENTS_ACTIVITY)) {
if (isHomeTopMost != null) {
isHomeTopMost.value = false;
}
@@ -552,12 +555,27 @@
}
/**
- * Returns whether the foreground user is the owner.
+ * Returns whether the provided {@param userId} represents the system user.
*/
- public boolean isForegroundUserSystem() {
- if (mAm == null) return false;
+ public boolean isSystemUser(int userId) {
+ return userId == UserHandle.USER_SYSTEM;
+ }
- return mAm.getCurrentUser() == UserHandle.USER_SYSTEM;
+ /**
+ * Returns the current user id.
+ */
+ public int getCurrentUser() {
+ if (mAm == null) return 0;
+
+ return mAm.getCurrentUser();
+ }
+
+ /**
+ * Returns the processes user id.
+ */
+ public int getProcessUser() {
+ if (mUm == null) return 0;
+ return mUm.getUserHandle();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 4322f1a..3673131 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -395,8 +395,10 @@
// The user intentionally tapped on the background, which is like a tap on the "desktop".
// Hide recents and transition to the launcher.
+ /* TODO: Use EventBus for this later
Recents recents = Recents.getInstanceAndStartIfNeeded(mSv.getContext());
- recents.hideRecents(false /* altTab */, true /* homeKey */);
+ recents.hideRecents(false, true);
+ */
}
/** Handles generic motion events */
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index d6d17cb..fdc2543 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -22,7 +22,9 @@
LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors
LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages com.android.systemui:com.android.keyguard
+
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, src) \
$(call all-java-files-under, ../src) \
$(call all-proto-files-under, ../src) \
src/com/android/systemui/EventLogTags.logtags
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
new file mode 120000
index 0000000..0ea3e91
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
@@ -0,0 +1 @@
+../../../../../../src/com/android/systemui/recents/IRecentsNonSystemUserCallbacks.aidl
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
new file mode 120000
index 0000000..b1a0963
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
@@ -0,0 +1 @@
+../../../../../../src/com/android/systemui/recents/IRecentsSystemUserCallbacks.aidl
\ No newline at end of file