diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index 4994fbb0da3a0..b5b101773f153 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -30,7 +30,7 @@ oneway interface ITaskStackListener { void onTaskStackChanged(); /** Called whenever an Activity is moved to the pinned stack from another stack. */ - void onActivityPinned(String packageName); + void onActivityPinned(String packageName, int taskId); /** Called whenever an Activity is moved from the pinned stack to another stack. */ void onActivityUnpinned(); diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java index 2df011fb856ee..a52ca0a64cd2c 100644 --- a/core/java/android/app/TaskStackListener.java +++ b/core/java/android/app/TaskStackListener.java @@ -31,7 +31,7 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { } @Override - public void onActivityPinned(String packageName) throws RemoteException { + public void onActivityPinned(String packageName, int taskId) throws RemoteException { } @Override 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 da2d38f03caa6..0da468114890e 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.IWindowManager; 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 @@ public class PipManager implements BasePipManager { */ 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 @@ public class PipManager implements BasePipManager { mInputConsumerController); mNotificationController = new PipNotificationController(context, mActivityManager, mTouchHandler.getMotionHelper()); + EventBus.getDefault().register(this); } /** @@ -195,6 +200,26 @@ public class PipManager implements BasePipManager { mTouchHandler.onConfigurationChanged(); } + /** + * 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. */ 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 766914c5a99a9..fbb97b65f9589 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 android.widget.LinearLayout; 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 @@ public class PipMenuActivity extends Activity { super.onStop(); cancelDelayedFinish(); + EventBus.getDefault().unregister(this); } @Override @@ -290,6 +293,19 @@ public class PipMenuActivity extends Activity { // 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; @@ -373,11 +389,16 @@ public class PipMenuActivity extends Activity { 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 5afa53f557ba2..1ccea6f684dfa 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.util.Log; 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 @@ public class PipMenuActivityController { // 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 @@ public class PipMenuActivityController { 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 @@ public class PipMenuActivityController { mActivityManager = activityManager; mMediaController = mediaController; mInputConsumerController = inputConsumerController; + + EventBus.getDefault().register(this); } public void onActivityPinned() { @@ -435,6 +445,15 @@ public class PipMenuActivityController { 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 198bbe5c37094..1c5da4d12b0c6 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -612,7 +612,7 @@ public class PipManager implements BasePipManager { } @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 c0e4b99b856ec..4eb07484a65b4 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.LaunchTaskFailedEvent; 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 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD } 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(); } } } @@ -395,6 +400,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD * 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(); @@ -501,28 +507,7 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD 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 @@ -821,6 +806,41 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD 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 a7f6b70d281e1..5b8ed94d5df09 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 class RecentsActivityLaunchState { 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 @@ public class RecentsActivityLaunchState { 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 2522d0f9107ac..cde865eec1188 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.LaunchMostRecentTaskRequestE 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 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener // previous one. VisibilityReport visibilityReport; synchronized (mDummyStackView) { + mDummyStackView.getStack().removeAllTasks(false /* notifyStackChanges */); mDummyStackView.setTasks(plan.getTaskStack(), false /* allowNotify */); updateDummyStackViewLayout(plan.getTaskStack(), getWindowRect(null /* windowRectOverride */)); @@ -150,6 +154,23 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } } + @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)); @@ -157,6 +178,8 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener } protected static RecentsTaskLoadPlan sInstanceLoadPlan; + // Stores the last pinned task time + protected static long sLastPipTime = -1; protected Context mContext; protected Handler mHandler; @@ -594,6 +617,20 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener return plan; } + /** + * @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. */ @@ -676,6 +713,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener 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) { @@ -921,6 +959,9 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener 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; @@ -988,12 +1029,16 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener | 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 a7375051e3385..d7abb387c278f 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 @@ public class EventBus extends BroadcastReceiver { private void queueEvent(final Event event) { ArrayList 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 0000000000000..75bfd7bde66c5 --- /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 0000000000000..f4d2fcff96726 --- /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 0000000000000..48c5f0b60f515 --- /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 0000000000000..8fe4975f1a2a2 --- /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 0000000000000..ce4f207aa1d7d --- /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 1dc3c8212de44..f85fb34a9749e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -163,7 +163,7 @@ public class SystemServicesProxy { 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() { } @@ -218,9 +218,9 @@ public class SystemServicesProxy { } @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 @@ -449,9 +449,18 @@ public class SystemServicesProxy { * Returns the top running task. */ public ActivityManager.RunningTaskInfo getRunningTask() { - List tasks = mAm.getRunningTasks(1); + // Note: The set of running tasks from the system is ordered by recency + List 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; } @@ -1314,7 +1323,7 @@ public class SystemServicesProxy { } 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 9b25ef8faafd1..6e3be09b2ae9c 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 @@ public class TaskStack { * 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 @@ public class TaskStack { * 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 @@ public class TaskStack { /** * Removes all tasks from the stack. */ - public void removeAllTasks() { + public void removeAllTasks(boolean notifyStackChanges) { ArrayList 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 @@ public class TaskStack { 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 @@ -853,22 +872,47 @@ public class TaskStack { return null; } + /** + * 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 e06c40c6cfe76..a0ad782bd347b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -55,7 +55,9 @@ import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationC 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; @@ -250,6 +252,12 @@ public class RecentsView extends FrameLayout { /** 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) { @@ -635,6 +643,10 @@ public class RecentsView extends FrameLayout { 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 7ba705e47cf6d..8135034319624 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 @@ public class TaskStackLayoutAlgorithm { 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 @@ public class TaskStackLayoutAlgorithm { 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 @@ public class TaskStackLayoutAlgorithm { 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 18a9bab740844..5f9a8f5cdd7c3 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.RecentsActivity; 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.LaunchTaskEvent; 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ @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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal 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 class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + 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(); } /** diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 8210c07b7678b..79ea7baa14429 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2998,7 +2998,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */, true /* schedulePipModeChangedOnAnimationEnd */); - mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName); + mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName, + r.getTask().taskId); } /** Move activity with its stack to front and make the stack focused. */ diff --git a/services/core/java/com/android/server/am/TaskChangeNotificationController.java b/services/core/java/com/android/server/am/TaskChangeNotificationController.java index f5d7b6858b83d..ea9ff592d6e1d 100644 --- a/services/core/java/com/android/server/am/TaskChangeNotificationController.java +++ b/services/core/java/com/android/server/am/TaskChangeNotificationController.java @@ -96,7 +96,7 @@ class TaskChangeNotificationController { }; private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> { - l.onActivityPinned((String) m.obj); + l.onActivityPinned((String) m.obj, m.arg1); }; private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> { @@ -279,10 +279,10 @@ class TaskChangeNotificationController { } /** Notifies all listeners when an Activity is pinned. */ - void notifyActivityPinned(String packageName) { + void notifyActivityPinned(String packageName, int taskId) { mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG); final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG, - packageName); + taskId, 0, packageName); forAllLocalListeners(mNotifyActivityPinned, msg); msg.sendToTarget(); }