Updating Overview to work with PiP
- Ensure that an activity that is auto-entering PiP when hitting Overview
does not show up in Overview. This is done by listening for the
onActivityPinned() callback from the system, and remove the pinned task.
- Ensure that we show the PiP task in Overview after it is dismissed, while
Overview is open. This is done by listening for the onActivityUnpinned()
callback from the system and refreshing the task list similar to when
the multi-window mode changes.
- When launching from a PiP activity, or launching back into Overview where
the next task should be PiP, then ensure that we scroll the stack to the
front so that the first task is fully visible.
- Fix two lingering Overview issues, when there are no handlers (ie. with
dynamically registered handlers), ensure that we call pre/post dispatch
callbacks (otherwise it could cause animated events not to work correctly).
Also, ensure that we don't update the dummy stack view TaskStack without
clearing the stack first, since we may be modifying the same stack
that the activity consumed when starting.
Bug: 34185886
Bug: 38207296
Test: Launch PIP activity from hitting Overview in the various ways
described above
Change-Id: I699e655106e6ed7206e163f9d3c15477bbfd52ef
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index da2d38f..0da4681 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -16,9 +16,11 @@
package com.android.systemui.pip.phone;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
+import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -33,6 +35,8 @@
import android.view.WindowManagerGlobal;
import com.android.systemui.pip.BasePipManager;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.component.ExpandPipEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
import com.android.systemui.statusbar.CommandQueue;
@@ -65,7 +69,7 @@
*/
TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
- public void onActivityPinned(String packageName) {
+ public void onActivityPinned(String packageName, int taskId) {
if (!checkCurrentUserId(false /* debug */)) {
return;
}
@@ -186,6 +190,7 @@
mInputConsumerController);
mNotificationController = new PipNotificationController(context, mActivityManager,
mTouchHandler.getMotionHelper());
+ EventBus.getDefault().register(this);
}
/**
@@ -196,6 +201,26 @@
}
/**
+ * Expands the PIP.
+ */
+ public final void onBusEvent(ExpandPipEvent event) {
+ if (event.clearThumbnailWindows) {
+ try {
+ StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ if (stackInfo != null && stackInfo.taskIds != null) {
+ SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
+ for (int taskId : stackInfo.taskIds) {
+ ssp.cancelThumbnailTransition(taskId);
+ }
+ }
+ } catch (RemoteException e) {
+ // Do nothing
+ }
+ }
+ mTouchHandler.getMotionHelper().expandPip(false /* skipAnimation */);
+ }
+
+ /**
* Sent from KEYCODE_WINDOW handler in PhoneWindowManager, to request the menu to be shown.
*/
public void showPictureInPictureMenu() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 65f24cf..5f4e547 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -63,6 +63,8 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.component.HidePipMenuEvent;
import java.util.ArrayList;
import java.util.Collections;
@@ -231,6 +233,7 @@
super.onStop();
cancelDelayedFinish();
+ EventBus.getDefault().unregister(this);
}
@Override
@@ -290,6 +293,19 @@
// Do nothing
}
+ public final void onBusEvent(HidePipMenuEvent event) {
+ if (mMenuState != MENU_STATE_NONE) {
+ // If the menu is visible in either the closed or full state, then hide the menu and
+ // trigger the animation trigger afterwards
+ event.getAnimationTrigger().increment();
+ hideMenu(() -> {
+ mHandler.post(() -> {
+ event.getAnimationTrigger().decrement();
+ });
+ }, true /* notifyMenuVisibility */);
+ }
+ }
+
private void showMenu(int menuState, Rect stackBounds, Rect movementBounds,
boolean allowMenuTimeout) {
mAllowMenuTimeout = allowMenuTimeout;
@@ -374,11 +390,16 @@
private void updateFromIntent(Intent intent) {
mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER);
notifyActivityCallback(mMessenger);
+
+ // Register for HidePipMenuEvents once we notify the controller of this activity
+ EventBus.getDefault().register(this);
+
ParceledListSlice actions = intent.getParcelableExtra(EXTRA_ACTIONS);
if (actions != null) {
mActions.clear();
mActions.addAll(actions.getList());
}
+
final int menuState = intent.getIntExtra(EXTRA_MENU_STATE, MENU_STATE_NONE);
if (menuState != MENU_STATE_NONE) {
Rect stackBounds = intent.getParcelableExtra(EXTRA_STACK_BOUNDS);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 5afa53f..1ccea6f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -36,6 +36,9 @@
import android.view.IWindowManager;
import com.android.systemui.pip.phone.PipMediaController.ActionListener;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.component.HidePipMenuEvent;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -119,6 +122,7 @@
// The dismiss fraction update is sent frequently, so use a temporary bundle for the message
private Bundle mTmpDismissFractionData = new Bundle();
+ private ReferenceCountedTrigger mOnAttachDecrementTrigger;
private boolean mStartActivityRequested;
private Messenger mToActivityMessenger;
private Messenger mMessenger = new Messenger(new Handler() {
@@ -157,6 +161,10 @@
case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
mToActivityMessenger = msg.replyTo;
mStartActivityRequested = false;
+ if (mOnAttachDecrementTrigger != null) {
+ mOnAttachDecrementTrigger.decrement();
+ mOnAttachDecrementTrigger = null;
+ }
// Mark the menu as invisible once the activity finishes as well
if (mToActivityMessenger == null) {
onMenuStateChanged(MENU_STATE_NONE, true /* resize */);
@@ -181,6 +189,8 @@
mActivityManager = activityManager;
mMediaController = mediaController;
mInputConsumerController = inputConsumerController;
+
+ EventBus.getDefault().register(this);
}
public void onActivityPinned() {
@@ -435,6 +445,15 @@
mMenuState = menuState;
}
+ public final void onBusEvent(HidePipMenuEvent event) {
+ if (mStartActivityRequested) {
+ // If the menu has been start-requested, but not actually started, then we defer the
+ // trigger callback until the menu has started and called back to the controller
+ mOnAttachDecrementTrigger = event.getAnimationTrigger();
+ mOnAttachDecrementTrigger.increment();
+ }
+ }
+
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 6667b71..92496aa 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -603,7 +603,7 @@
}
@Override
- public void onActivityPinned(String packageName) {
+ public void onActivityPinned(String packageName, int taskId) {
if (DEBUG) Log.d(TAG, "onActivityPinned()");
if (!checkCurrentUserId(DEBUG)) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 7bc591f..edbf01d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -62,6 +62,7 @@
import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
+import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
@@ -205,6 +206,10 @@
}
Settings.Secure.putLongForUser(RecentsActivity.this.getContentResolver(),
Secure.OVERVIEW_LAST_STACK_ACTIVE_TIME, currentTime, currentUser);
+
+ // Clear the last PiP task time, it's an edge case and we'd rather it
+ // not relaunch the PiP task if the user double taps
+ RecentsImpl.clearLastPipTime();
}
}
}
@@ -393,6 +398,7 @@
* Reloads the stack views upon launching Recents.
*/
private void reloadStackView() {
+
// If the Recents component has preloaded a load plan, then use that to prevent
// reconstructing the task stack
RecentsTaskLoader loader = Recents.getTaskLoader();
@@ -499,28 +505,7 @@
public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
super.onMultiWindowModeChanged(isInMultiWindowMode);
- // Reload the task stack completely
- RecentsConfiguration config = Recents.getConfiguration();
- RecentsActivityLaunchState launchState = config.getLaunchState();
- RecentsTaskLoader loader = Recents.getTaskLoader();
- RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
- loader.preloadTasks(loadPlan, -1 /* runningTaskId */,
- false /* includeFrontMostExcludedTask */);
-
- RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
- loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
- loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
- loader.loadTasks(this, loadPlan, loadOpts);
-
- TaskStack stack = loadPlan.getTaskStack();
- int numStackTasks = stack.getStackTaskCount();
- boolean showDeferredAnimation = numStackTasks > 0;
-
- EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */,
- false /* fromDeviceOrientationChange */, false /* fromDisplayDensityChange */,
- numStackTasks > 0));
- EventBus.getDefault().send(new MultiWindowStateChangedEvent(isInMultiWindowMode,
- showDeferredAnimation, stack));
+ reloadTaskStack(isInMultiWindowMode, true /* sendConfigChangedEvent */);
}
@Override
@@ -819,6 +804,41 @@
mRecentsView.invalidate();
}
+ public final void onBusEvent(final ActivityUnpinnedEvent event) {
+ if (mIsVisible) {
+ // Skip the configuration change event as the PiP activity does not actually affect the
+ // config of recents
+ reloadTaskStack(isInMultiWindowMode(), false /* sendConfigChangedEvent */);
+ }
+ }
+
+ private void reloadTaskStack(boolean isInMultiWindowMode, boolean sendConfigChangedEvent) {
+ // Reload the task stack completely
+ RecentsConfiguration config = Recents.getConfiguration();
+ RecentsActivityLaunchState launchState = config.getLaunchState();
+ RecentsTaskLoader loader = Recents.getTaskLoader();
+ RecentsTaskLoadPlan loadPlan = loader.createLoadPlan(this);
+ loader.preloadTasks(loadPlan, -1 /* runningTaskId */,
+ false /* includeFrontMostExcludedTask */);
+
+ RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
+ loadOpts.numVisibleTasks = launchState.launchedNumVisibleTasks;
+ loadOpts.numVisibleTaskThumbnails = launchState.launchedNumVisibleThumbnails;
+ loader.loadTasks(this, loadPlan, loadOpts);
+
+ TaskStack stack = loadPlan.getTaskStack();
+ int numStackTasks = stack.getStackTaskCount();
+ boolean showDeferredAnimation = numStackTasks > 0;
+
+ if (sendConfigChangedEvent) {
+ EventBus.getDefault().send(new ConfigurationChangedEvent(true /* fromMultiWindow */,
+ false /* fromDeviceOrientationChange */, false /* fromDisplayDensityChange */,
+ numStackTasks > 0));
+ }
+ EventBus.getDefault().send(new MultiWindowStateChangedEvent(isInMultiWindowMode,
+ showDeferredAnimation, stack));
+ }
+
@Override
public boolean onPreDraw() {
mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index a7f6b70..5b8ed94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -29,6 +29,10 @@
public boolean launchedWithAltTab;
public boolean launchedFromApp;
+ // Set if the activity that we launched from entered PiP during the transition into Recents
+ public boolean launchedFromPipApp;
+ // Set if the next activity that quick-switch will launch is the PiP activity
+ public boolean launchedWithNextPipApp;
public boolean launchedFromBlacklistedApp;
public boolean launchedFromHome;
public boolean launchedViaDragGesture;
@@ -41,6 +45,8 @@
launchedFromHome = false;
launchedFromApp = false;
launchedFromBlacklistedApp = false;
+ launchedFromPipApp = false;
+ launchedWithNextPipApp = false;
launchedToTaskId = -1;
launchedWithAltTab = false;
launchedViaDragGesture = false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 2b812a5..b65cac8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -57,6 +57,9 @@
import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
+import com.android.systemui.recents.events.component.ActivityPinnedEvent;
+import com.android.systemui.recents.events.component.ActivityUnpinnedEvent;
+import com.android.systemui.recents.events.component.HidePipMenuEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
@@ -127,6 +130,7 @@
// previous one.
VisibilityReport visibilityReport;
synchronized (mDummyStackView) {
+ mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */);
mDummyStackView.setTasks(plan.getTaskStack(), false /* allowNotify */);
updateDummyStackViewLayout(plan.getTaskStack(),
getWindowRect(null /* windowRectOverride */));
@@ -151,12 +155,31 @@
}
@Override
+ public void onActivityPinned(String packageName, int taskId) {
+ // This time needs to be fetched the same way the last active time is fetched in
+ // {@link TaskRecord#touchActiveTime}
+ Recents.getConfiguration().getLaunchState().launchedFromPipApp = true;
+ Recents.getConfiguration().getLaunchState().launchedWithNextPipApp = false;
+ EventBus.getDefault().send(new ActivityPinnedEvent(taskId));
+ consumeInstanceLoadPlan();
+ sLastPipTime = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onActivityUnpinned() {
+ EventBus.getDefault().send(new ActivityUnpinnedEvent());
+ sLastPipTime = -1;
+ }
+
+ @Override
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) {
EventBus.getDefault().send(new TaskSnapshotChangedEvent(taskId, snapshot));
}
}
protected static RecentsTaskLoadPlan sInstanceLoadPlan;
+ // Stores the last pinned task time
+ protected static long sLastPipTime = -1;
protected Context mContext;
protected Handler mHandler;
@@ -593,6 +616,20 @@
}
/**
+ * @return the time at which a task last entered picture-in-picture.
+ */
+ public static long getLastPipTime() {
+ return sLastPipTime;
+ }
+
+ /**
+ * Clears the time at which a task last entered picture-in-picture.
+ */
+ public static void clearLastPipTime() {
+ sLastPipTime = -1;
+ }
+
+ /**
* Reloads all the resources for the current configuration.
*/
private void reloadResources() {
@@ -674,6 +711,7 @@
updateDummyStackViewLayout(stack, windowRect);
if (stack != null) {
TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
+ mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */);
mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
// Get the width of a task view so that we know how wide to draw the header bar.
if (useGridLayout) {
@@ -919,6 +957,9 @@
launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
launchState.launchedFromBlacklistedApp = launchState.launchedFromApp && isBlacklisted;
+ launchState.launchedFromPipApp = false;
+ launchState.launchedWithNextPipApp =
+ stack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime());
launchState.launchedViaDockGesture = mLaunchedWhileDocking;
launchState.launchedViaDragGesture = mDraggingInRecents;
launchState.launchedToTaskId = runningTaskId;
@@ -986,12 +1027,16 @@
| 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);
- }
- EventBus.getDefault().send(new RecentsActivityStartingEvent());
+ HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent();
+ hideMenuEvent.addPostAnimationCallback(() -> {
+ if (opts != null) {
+ mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
+ } else {
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+ EventBus.getDefault().send(new RecentsActivityStartingEvent());
+ });
+ EventBus.getDefault().send(hideMenuEvent);
}
/**** OnAnimationFinishedListener Implementation ****/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
index a737505..d7abb38 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/EventBus.java
@@ -810,6 +810,11 @@
private void queueEvent(final Event event) {
ArrayList<EventHandler> eventHandlers = mEventTypeMap.get(event.getClass());
if (eventHandlers == null) {
+ // This is just an optimization to return early if there are no handlers. However, we
+ // should still ensure that we call pre/post dispatch callbacks so that AnimatedEvents
+ // are still cleaned up correctly if a listener has not been registered to handle them
+ event.onPreDispatch();
+ event.onPostDispatch();
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowEmptyViewEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowEmptyViewEvent.java
new file mode 100644
index 0000000..75bfd7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/ShowEmptyViewEvent.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * Sent when the stack should be hidden and the empty view shown.
+ */
+public class ShowEmptyViewEvent extends EventBus.Event {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ActivityPinnedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ActivityPinnedEvent.java
new file mode 100644
index 0000000..f4d2fcf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ActivityPinnedEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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 com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when an activity is pinned.
+ */
+public class ActivityPinnedEvent extends EventBus.Event {
+
+ public final int taskId;
+
+ public ActivityPinnedEvent(int taskId) {
+ this.taskId = taskId;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ActivityUnpinnedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ActivityUnpinnedEvent.java
new file mode 100644
index 0000000..48c5f0b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ActivityUnpinnedEvent.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 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 com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when an activity is unpinned.
+ */
+public class ActivityUnpinnedEvent extends EventBus.Event {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
new file mode 100644
index 0000000..8fe4975
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ExpandPipEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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 com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the PiP should be expanded due to being relaunched.
+ */
+public class ExpandPipEvent extends EventBus.Event {
+ public final boolean clearThumbnailWindows = true;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/HidePipMenuEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/HidePipMenuEvent.java
new file mode 100644
index 0000000..ce4f207
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/HidePipMenuEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 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 com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when the PiP menu should be hidden.
+ */
+public class HidePipMenuEvent extends EventBus.AnimatedEvent {
+ // Simple event
+}
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 f431517..2452242 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -156,7 +156,7 @@
public void onTaskStackChangedBackground() { }
public void onTaskStackChanged() { }
public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
- public void onActivityPinned(String packageName) { }
+ public void onActivityPinned(String packageName, int taskId) { }
public void onActivityUnpinned() { }
public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
public void onPinnedStackAnimationStarted() { }
@@ -211,9 +211,9 @@
}
@Override
- public void onActivityPinned(String packageName) throws RemoteException {
+ public void onActivityPinned(String packageName, int taskId) throws RemoteException {
mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
- mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, packageName).sendToTarget();
+ mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, taskId, 0, packageName).sendToTarget();
}
@Override
@@ -442,9 +442,18 @@
* Returns the top running task.
*/
public ActivityManager.RunningTaskInfo getRunningTask() {
- List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(1);
+ // Note: The set of running tasks from the system is ordered by recency
+ List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10);
if (tasks != null && !tasks.isEmpty()) {
- return tasks.get(0);
+ // Find the first task in a valid stack, we ignore everything from the Recents and PiP
+ // stacks
+ for (int i = 0; i < tasks.size(); i++) {
+ ActivityManager.RunningTaskInfo task = tasks.get(i);
+ int stackId = task.stackId;
+ if (stackId != RECENTS_STACK_ID && stackId != PINNED_STACK_ID) {
+ return task;
+ }
+ }
}
return null;
}
@@ -1283,7 +1292,7 @@
}
case ON_ACTIVITY_PINNED: {
for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
- mTaskStackListeners.get(i).onActivityPinned((String) msg.obj);
+ mTaskStackListeners.get(i).onActivityPinned((String) msg.obj, msg.arg1);
}
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 9b25ef8..6e3be09 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -229,7 +229,8 @@
* Notifies when a task has been removed from the stack.
*/
void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
- AnimationProps animation, boolean fromDockGesture);
+ AnimationProps animation, boolean fromDockGesture,
+ boolean dismissRecentsIfAllRemoved);
/**
* Notifies when all tasks have been removed from the stack.
@@ -631,13 +632,22 @@
* how they should update themselves.
*/
public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture) {
+ removeTask(t, animation, fromDockGesture, true /* dismissRecentsIfAllRemoved */);
+ }
+
+ /**
+ * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
+ * how they should update themselves.
+ */
+ public void removeTask(Task t, AnimationProps animation, boolean fromDockGesture,
+ boolean dismissRecentsIfAllRemoved) {
if (mStackTaskList.contains(t)) {
removeTaskImpl(mStackTaskList, t);
Task newFrontMostTask = getStackFrontMostTask(false /* includeFreeform */);
if (mCb != null) {
// Notify that a task has been removed
mCb.onStackTaskRemoved(this, t, newFrontMostTask, animation,
- fromDockGesture);
+ fromDockGesture, dismissRecentsIfAllRemoved);
}
}
mRawTaskList.remove(t);
@@ -646,19 +656,27 @@
/**
* Removes all tasks from the stack.
*/
- public void removeAllTasks() {
+ public void removeAllTasks(boolean notifyStackChanges) {
ArrayList<Task> tasks = mStackTaskList.getTasks();
for (int i = tasks.size() - 1; i >= 0; i--) {
Task t = tasks.get(i);
removeTaskImpl(mStackTaskList, t);
mRawTaskList.remove(t);
}
- if (mCb != null) {
+ if (mCb != null && notifyStackChanges) {
// Notify that all tasks have been removed
mCb.onStackTasksRemoved(this);
}
}
+
+ /**
+ * @see #setTasks(Context, List, boolean, boolean)
+ */
+ public void setTasks(Context context, TaskStack stack, boolean notifyStackChanges) {
+ setTasks(context, stack.mRawTaskList, notifyStackChanges);
+ }
+
/**
* Sets a few tasks in one go, without calling any callbacks.
*
@@ -723,7 +741,8 @@
Task newFrontMostTask = getStackFrontMostTask(false);
for (int i = 0; i < removedTaskCount; i++) {
mCb.onStackTaskRemoved(this, removedTasks.get(i), newFrontMostTask,
- AnimationProps.IMMEDIATE, false /* fromDockGesture */);
+ AnimationProps.IMMEDIATE, false /* fromDockGesture */,
+ true /* dismissRecentsIfAllRemoved */);
}
// Only callback for the newly added tasks after this stack has been updated
@@ -854,21 +873,46 @@
}
/**
+ * Returns whether the next launch target should actually be the PiP task.
+ */
+ public boolean isNextLaunchTargetPip(long lastPipTime) {
+ Task launchTarget = getLaunchTarget();
+ Task nextLaunchTarget = getNextLaunchTargetRaw();
+ if (nextLaunchTarget != null && lastPipTime > 0) {
+ // If the PiP time is more recent than the next launch target, then launch the PiP task
+ return lastPipTime > nextLaunchTarget.key.lastActiveTime;
+ } else if (launchTarget != null && lastPipTime > 0 && getTaskCount() == 1) {
+ // Otherwise, if there is no next launch target, but there is a PiP, then launch
+ // the PiP task
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Returns the task in stack tasks which should be launched next if Recents are toggled
- * again, or null if there is no task to be launched.
+ * again, or null if there is no task to be launched. Callers should check
+ * {@link #isNextLaunchTargetPip(long)} before fetching the next raw launch target from the
+ * stack.
*/
public Task getNextLaunchTarget() {
+ Task nextLaunchTarget = getNextLaunchTargetRaw();
+ if (nextLaunchTarget != null) {
+ return nextLaunchTarget;
+ }
+ return getStackTasks().get(getTaskCount() - 1);
+ }
+
+ private Task getNextLaunchTargetRaw() {
int taskCount = getTaskCount();
if (taskCount == 0) {
return null;
}
int launchTaskIndex = indexOfStackTask(getLaunchTarget());
- if (launchTaskIndex != -1) {
- launchTaskIndex = Math.max(0, launchTaskIndex - 1);
- } else {
- launchTaskIndex = getTaskCount() - 1;
+ if (launchTaskIndex != -1 && launchTaskIndex > 0) {
+ return getStackTasks().get(launchTaskIndex - 1);
}
- return getStackTasks().get(launchTaskIndex);
+ return null;
}
/** Returns the index of this task in this current task stack */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 8f9c457..fcca622 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -58,7 +58,9 @@
import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
+import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
+import com.android.systemui.recents.events.component.ExpandPipEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
@@ -253,6 +255,12 @@
/** Launches the task that recents was launched from if possible */
public boolean launchPreviousTask() {
+ if (Recents.getConfiguration().getLaunchState().launchedFromPipApp) {
+ // If the app auto-entered PiP on the way to Recents, then just re-expand it
+ EventBus.getDefault().send(new ExpandPipEvent());
+ return true;
+ }
+
if (mTaskStackView != null) {
Task task = getStack().getLaunchTarget();
if (task != null) {
@@ -639,6 +647,10 @@
updateStack(event.stack, false /* setStackViewTasks */);
}
+ public final void onBusEvent(ShowEmptyViewEvent event) {
+ showEmptyView(R.string.recents_empty_message);
+ }
+
/**
* Shows the stack action button.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 7ba705e..8135034 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -562,7 +562,8 @@
mMinScrollP = 0;
mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
Math.max(0, mFocusedRange.getAbsoluteX(maxBottomNormX)));
- if (launchState.launchedFromHome) {
+ if (launchState.launchedFromHome || launchState.launchedFromPipApp
+ || launchState.launchedWithNextPipApp) {
mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
} else {
mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
@@ -581,8 +582,8 @@
mMinScrollP = 0;
mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
Math.max(0, mUnfocusedRange.getAbsoluteX(maxBottomNormX)));
- boolean scrollToFront = launchState.launchedFromHome ||
- launchState.launchedViaDockGesture;
+ boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp
+ || launchState.launchedWithNextPipApp || launchState.launchedViaDockGesture;
if (launchState.launchedFromBlacklistedApp) {
mInitialScrollP = mMaxScrollP;
} else if (launchState.launchedWithAltTab) {
@@ -608,6 +609,8 @@
mTaskIndexOverrideMap.clear();
boolean scrollToFront = launchState.launchedFromHome ||
+ launchState.launchedFromPipApp ||
+ launchState.launchedWithNextPipApp ||
launchState.launchedFromBlacklistedApp ||
launchState.launchedViaDockGesture;
if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 18a9bab..5f9a8f5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -57,6 +57,7 @@
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.RecentsDebugFlags;
+import com.android.systemui.recents.RecentsImpl;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
@@ -72,7 +73,11 @@
import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
+import com.android.systemui.recents.events.activity.ShowEmptyViewEvent;
import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent;
+import com.android.systemui.recents.events.component.ActivityPinnedEvent;
+import com.android.systemui.recents.events.component.ExpandPipEvent;
+import com.android.systemui.recents.events.component.HidePipMenuEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
@@ -379,8 +384,7 @@
// Only notify if we are already initialized, otherwise, everything will pick up all the
// new and old tasks when we next layout
- mStack.setTasks(getContext(), stack.computeAllTasksList(),
- allowNotifyStackChanges && isInitialized);
+ mStack.setTasks(getContext(), stack, allowNotifyStackChanges && isInitialized);
}
/** Returns the task stack. */
@@ -1496,7 +1500,7 @@
*/
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
- AnimationProps animation, boolean fromDockGesture) {
+ AnimationProps animation, boolean fromDockGesture, boolean dismissRecentsIfAllRemoved) {
if (mFocusedTask == removedTask) {
resetFocusedTask(removedTask);
}
@@ -1527,9 +1531,13 @@
// If there are no remaining tasks, then just close recents
if (mStack.getTaskCount() == 0) {
- EventBus.getDefault().send(new AllTaskViewsDismissedEvent(fromDockGesture
- ? R.string.recents_empty_message
- : R.string.recents_empty_message_dismissed_all));
+ if (dismissRecentsIfAllRemoved) {
+ EventBus.getDefault().send(new AllTaskViewsDismissedEvent(fromDockGesture
+ ? R.string.recents_empty_message
+ : R.string.recents_empty_message_dismissed_all));
+ } else {
+ EventBus.getDefault().send(new ShowEmptyViewEvent());
+ }
}
}
@@ -1802,14 +1810,36 @@
return;
}
- final Task launchTask = mStack.getNextLaunchTarget();
- if (launchTask != null) {
- launchTask(launchTask);
- MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK,
- launchTask.key.getComponent().toString());
- } else if (mStack.getTaskCount() == 0) {
- // If there are no tasks, then just hide recents back to home.
- EventBus.getDefault().send(new HideRecentsEvent(false, true));
+ if (mStack.getTaskCount() == 0) {
+ if (RecentsImpl.getLastPipTime() != -1) {
+ EventBus.getDefault().send(new ExpandPipEvent());
+ MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK,
+ "pip");
+ } else {
+ // If there are no tasks, then just hide recents back to home.
+ EventBus.getDefault().send(new HideRecentsEvent(false, true));
+ }
+ return;
+ }
+
+ if (!Recents.getConfiguration().getLaunchState().launchedFromPipApp
+ && mStack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime())) {
+ // If the launch task is in the pinned stack, then expand the PiP now
+ EventBus.getDefault().send(new ExpandPipEvent());
+ MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, "pip");
+ } else {
+ final Task launchTask = mStack.getNextLaunchTarget();
+ if (launchTask != null) {
+ // Defer launching the task until the PiP menu has been dismissed (if it is
+ // showing at all)
+ HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent();
+ hideMenuEvent.addPostAnimationCallback(() -> {
+ launchTask(launchTask);
+ });
+ EventBus.getDefault().send(hideMenuEvent);
+ MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK,
+ launchTask.key.getComponent().toString());
+ }
}
}
@@ -1871,7 +1901,7 @@
R.string.accessibility_recents_all_items_dismissed));
// Remove all tasks and delete the task data for all tasks
- mStack.removeAllTasks();
+ mStack.removeAllTasks(true /* notifyStackChanges */);
for (int i = tasks.size() - 1; i >= 0; i--) {
EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i)));
}
@@ -2217,11 +2247,23 @@
}
}
+ public final void onBusEvent(ActivityPinnedEvent event) {
+ // If an activity enters PiP while Recents is open, remove the stack task associated with
+ // the new PiP task
+ Task removeTask = mStack.findTaskWithId(event.taskId);
+ if (removeTask != null) {
+ // In this case, we remove the task, but if the last task is removed, don't dismiss
+ // Recents to home
+ mStack.removeTask(removeTask, AnimationProps.IMMEDIATE, false /* fromDockGesture */,
+ false /* dismissRecentsIfAllRemoved */);
+ }
+ updateLayoutAlgorithm(false /* boundScroll */);
+ updateToInitialState();
+ }
+
public void reloadOnConfigurationChange() {
mStableLayoutAlgorithm.reloadOnConfigurationChange(getContext());
mLayoutAlgorithm.reloadOnConfigurationChange(getContext());
-
- boolean hasDockedTask = Recents.getSystemServices().hasDockedTask();
}
/**