diff options
| author | 2023-08-29 23:26:41 +0000 | |
|---|---|---|
| committer | 2023-08-29 23:26:41 +0000 | |
| commit | 1633a6522096f76f7de1caa91ba544259b0ef4cf (patch) | |
| tree | 304069e0627e02caab55db2a3304743bcb17d248 | |
| parent | 959ab87e2f460e9b012361779bc63d074f7d7f94 (diff) | |
| parent | dd2b9e6c8aa7f55cb854e7b8dfd413dd1105bce4 (diff) | |
Merge "Replace onTaskMovedToFront with a transition observer in bubbles" into main
6 files changed, 379 insertions, 21 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 8400ddec0af5..645a96153080 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -16,7 +16,6 @@ package com.android.wm.shell.bubbles; -import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; import static android.service.notification.NotificationListenerService.REASON_CANCEL; @@ -115,6 +114,7 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.taskview.TaskView; import com.android.wm.shell.taskview.TaskViewTransitions; +import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; import java.util.ArrayList; @@ -143,6 +143,8 @@ public class BubbleController implements ConfigurationChangeListener, // Should match with PhoneWindowManager private static final String SYSTEM_DIALOG_REASON_KEY = "reason"; private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav"; + private static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; + private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; /** * Common interface to send updates to bubble views. @@ -182,6 +184,7 @@ public class BubbleController implements ConfigurationChangeListener, private final ShellTaskOrganizer mTaskOrganizer; private final DisplayController mDisplayController; private final TaskViewTransitions mTaskViewTransitions; + private final Transitions mTransitions; private final SyncTransactionQueue mSyncQueue; private final ShellController mShellController; private final ShellCommandHandler mShellCommandHandler; @@ -282,6 +285,7 @@ public class BubbleController implements ConfigurationChangeListener, @ShellMainThread Handler mainHandler, @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewTransitions taskViewTransitions, + Transitions transitions, SyncTransactionQueue syncQueue, IWindowManager wmService, BubbleProperties bubbleProperties) { @@ -317,6 +321,7 @@ public class BubbleController implements ConfigurationChangeListener, com.android.internal.R.dimen.importance_ring_stroke_width)); mDisplayController = displayController; mTaskViewTransitions = taskViewTransitions; + mTransitions = transitions; mOneHandedOptional = oneHandedOptional; mDragAndDropController = dragAndDropController; mSyncQueue = syncQueue; @@ -416,23 +421,9 @@ public class BubbleController implements ConfigurationChangeListener, } }, mMainHandler); - mTaskStackListener.addListener(new TaskStackListenerCallback() { - @Override - public void onTaskMovedToFront(int taskId) { - mMainExecutor.execute(() -> { - int expandedId = INVALID_TASK_ID; - if (mStackView != null && mStackView.getExpandedBubble() != null - && isStackExpanded() - && !mStackView.isExpansionAnimating() - && !mStackView.isSwitchAnimating()) { - expandedId = mStackView.getExpandedBubble().getTaskId(); - } - if (expandedId != INVALID_TASK_ID && expandedId != taskId) { - mBubbleData.setExpanded(false); - } - }); - } + mTransitions.registerObserver(new BubblesTransitionObserver(this, mBubbleData)); + mTaskStackListener.addListener(new TaskStackListenerCallback() { @Override public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { @@ -883,8 +874,10 @@ public class BubbleController implements ConfigurationChangeListener, String action = intent.getAction(); String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); - if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) - && SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason)) + boolean validReasonToCollapse = SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason) + || SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason) + || SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason); + if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) && validReasonToCollapse) || Intent.ACTION_SCREEN_OFF.equals(action)) { mMainExecutor.execute(() -> collapseStack()); } @@ -1961,6 +1954,15 @@ public class BubbleController implements ConfigurationChangeListener, } } + /** + * Returns whether the stack is animating or not. + */ + public boolean isStackAnimating() { + return mStackView != null + && (mStackView.isExpansionAnimating() + || mStackView.isSwitchAnimating()); + } + @VisibleForTesting @Nullable public BubbleStackView getStackView() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java new file mode 100644 index 000000000000..9e8a385262e4 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesTransitionObserver.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 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.wm.shell.bubbles; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; + +import android.app.ActivityManager; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +import androidx.annotation.NonNull; + +import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; + +/** + * Observer used to identify tasks that are opening or moving to front. If a bubble activity is + * currently opened when this happens, we'll collapse the bubbles. + */ +public class BubblesTransitionObserver implements Transitions.TransitionObserver { + + private BubbleController mBubbleController; + private BubbleData mBubbleData; + + public BubblesTransitionObserver(BubbleController controller, + BubbleData bubbleData) { + mBubbleController = controller; + mBubbleData = bubbleData; + } + + @Override + public void onTransitionReady(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction) { + for (TransitionInfo.Change change : info.getChanges()) { + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + // We only care about opens / move to fronts when bubbles are expanded & not animating. + if (taskInfo == null + || taskInfo.taskId == INVALID_TASK_ID + || !TransitionUtil.isOpeningType(change.getMode()) + || mBubbleController.isStackAnimating() + || !mBubbleData.isExpanded() + || mBubbleData.getSelectedBubble() == null) { + continue; + } + int expandedId = mBubbleData.getSelectedBubble().getTaskId(); + // If the task id that's opening is the same as the expanded bubble, skip collapsing + // because it is our bubble that is opening. + if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) { + mBubbleData.setExpanded(false); + } + } + } + + @Override + public void onTransitionStarting(@NonNull IBinder transition) { + + } + + @Override + public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) { + + } + + @Override + public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) { + + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 93ce91fa2d9c..c641e87e2055 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -167,6 +167,7 @@ public abstract class WMShellModule { @ShellMainThread Handler mainHandler, @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewTransitions taskViewTransitions, + Transitions transitions, SyncTransactionQueue syncQueue, IWindowManager wmService) { return new BubbleController(context, shellInit, shellCommandHandler, shellController, data, @@ -176,7 +177,8 @@ public abstract class WMShellModule { statusBarService, windowManager, windowManagerShellWrapper, userManager, launcherApps, logger, taskStackListener, organizer, positioner, displayController, oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor, - taskViewTransitions, syncQueue, wmService, ProdBubbleProperties.INSTANCE); + taskViewTransitions, transitions, syncQueue, wmService, + ProdBubbleProperties.INSTANCE); } // diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java new file mode 100644 index 000000000000..9655f97cb7cc --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTransitionObserverTest.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2023 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.wm.shell.bubbles; + +import static android.app.ActivityTaskManager.INVALID_TASK_ID; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_CHANGE; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.IWindowContainerToken; +import android.window.TransitionInfo; +import android.window.WindowContainerToken; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.TransitionInfoBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests of {@link BubblesTransitionObserver}. + */ +@SmallTest +public class BubblesTransitionObserverTest { + + @Mock + private BubbleController mBubbleController; + @Mock + private BubbleData mBubbleData; + + @Mock + private IBinder mTransition; + @Mock + private SurfaceControl.Transaction mStartT; + @Mock + private SurfaceControl.Transaction mFinishT; + + @Mock + private Bubble mBubble; + + private BubblesTransitionObserver mTransitionObserver; + + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mTransitionObserver = new BubblesTransitionObserver(mBubbleController, mBubbleData); + } + + @Test + public void testOnTransitionReady_open_collapsesStack() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_toFront_collapsesStack() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_noTaskInfo_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + // Null task info + TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, null /* taskInfo */); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_noTaskId_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + // Invalid task id + TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, + createTaskInfo(INVALID_TASK_ID)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_notOpening_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + // Transits that aren't opening + TransitionInfo info = createTransitionInfo(TRANSIT_CHANGE, createTaskInfo(2)); + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + info = createTransitionInfo(TRANSIT_CLOSE, createTaskInfo(3)); + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + info = createTransitionInfo(TRANSIT_TO_BACK, createTaskInfo(4)); + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_stackAnimating_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(true); // Stack is animating + + TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_stackNotExpanded_skip() { + when(mBubbleData.isExpanded()).thenReturn(false); // Stack is not expanded + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(2)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_noSelectedBubble_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(null); // No selected bubble + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + TransitionInfo info = createTransitionInfo(TRANSIT_OPEN, createTaskInfo(2)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + @Test + public void testOnTransitionReady_openingMatchesExpanded_skip() { + when(mBubbleData.isExpanded()).thenReturn(true); + when(mBubbleData.getSelectedBubble()).thenReturn(mBubble); + when(mBubble.getTaskId()).thenReturn(1); + when(mBubbleController.isStackAnimating()).thenReturn(false); + + // What's moving to front is same as the opened bubble + TransitionInfo info = createTransitionInfo(TRANSIT_TO_FRONT, createTaskInfo(1)); + + mTransitionObserver.onTransitionReady(mTransition, info, mStartT, mFinishT); + + verify(mBubbleData, never()).setExpanded(eq(false)); + } + + private ActivityManager.RunningTaskInfo createTaskInfo(int taskId) { + final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + return taskInfo; + } + + private TransitionInfo createTransitionInfo(int changeType, + ActivityManager.RunningTaskInfo info) { + final TransitionInfo.Change change = new TransitionInfo.Change( + new WindowContainerToken(mock(IWindowContainerToken.class)), + mock(SurfaceControl.class)); + change.setMode(changeType); + change.setTaskInfo(info); + + return new TransitionInfoBuilder(TRANSIT_OPEN, 0) + .addChange(change).build(); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index ac9cfb3cfb23..1b623a3de7bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -420,6 +420,7 @@ public class BubblesTest extends SysuiTestCase { syncExecutor, mock(Handler.class), mTaskViewTransitions, + mTransitions, mock(SyncTransactionQueue.class), mock(IWindowManager.class), mBubbleProperties); @@ -511,6 +512,11 @@ public class BubblesTest extends SysuiTestCase { } @Test + public void instantiateController_registerTransitionObserver() { + verify(mTransitions).registerObserver(any()); + } + + @Test public void testAddBubble() { mBubbleController.updateBubble(mBubbleEntry); assertTrue(mBubbleController.hasBubbles()); @@ -1470,6 +1476,34 @@ public class BubblesTest extends SysuiTestCase { } @Test + public void testBroadcastReceiverCloseDialogs_reasonHomeKey() { + spyOn(mContext); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleData.setExpanded(true); + + verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), + mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED)); + Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + i.putExtra("reason", "homekey"); + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i); + assertStackCollapsed(); + } + + @Test + public void testBroadcastReceiverCloseDialogs_reasonRecentsKey() { + spyOn(mContext); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleData.setExpanded(true); + + verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), + mFilterArgumentCaptor.capture(), eq(Context.RECEIVER_EXPORTED)); + Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + i.putExtra("reason", "recentapps"); + mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i); + assertStackCollapsed(); + } + + @Test public void testBroadcastReceiver_screenOff() { spyOn(mContext); mBubbleController.updateBubble(mBubbleEntry); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java index 5855347c203b..9ad234e16bb3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java @@ -43,6 +43,7 @@ import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.taskview.TaskViewTransitions; +import com.android.wm.shell.transition.Transitions; import java.util.Optional; @@ -74,6 +75,7 @@ public class TestableBubbleController extends BubbleController { ShellExecutor shellMainExecutor, Handler shellMainHandler, TaskViewTransitions taskViewTransitions, + Transitions transitions, SyncTransactionQueue syncQueue, IWindowManager wmService, BubbleProperties bubbleProperties) { @@ -82,7 +84,8 @@ public class TestableBubbleController extends BubbleController { windowManagerShellWrapper, userManager, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController, oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler, - new SyncExecutor(), taskViewTransitions, syncQueue, wmService, bubbleProperties); + new SyncExecutor(), taskViewTransitions, transitions, + syncQueue, wmService, bubbleProperties); setInflateSynchronously(true); onInit(); } |