Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 58c72885 authored by Mady Mellor's avatar Mady Mellor Committed by Android (Google) Code Review
Browse files

Merge "Replace onTaskMovedToFront with a transition observer in bubbles" into main

parents b9b392e3 3fdcd275
Loading
Loading
Loading
Loading
+15 −17
Original line number Diff line number Diff line
@@ -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;
@@ -182,6 +182,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 +283,7 @@ public class BubbleController implements ConfigurationChangeListener,
            @ShellMainThread Handler mainHandler,
            @ShellBackgroundThread ShellExecutor bgExecutor,
            TaskViewTransitions taskViewTransitions,
            Transitions transitions,
            SyncTransactionQueue syncQueue,
            IWindowManager wmService,
            BubbleProperties bubbleProperties) {
@@ -317,6 +319,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 +419,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) {
@@ -1961,6 +1950,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() {
+83 −0
Original line number Diff line number Diff line
/*
 * 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) {

    }
}
+3 −1
Original line number Diff line number Diff line
@@ -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);
    }

    //
+234 −0
Original line number Diff line number Diff line
/*
 * 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();
    }

}
+6 −0
Original line number Diff line number Diff line
@@ -419,6 +419,7 @@ public class BubblesTest extends SysuiTestCase {
                syncExecutor,
                mock(Handler.class),
                mTaskViewTransitions,
                mTransitions,
                mock(SyncTransactionQueue.class),
                mock(IWindowManager.class),
                mBubbleProperties);
@@ -509,6 +510,11 @@ public class BubblesTest extends SysuiTestCase {
        verify(mShellController, times(1)).addConfigurationChangeListener(any());
    }

    @Test
    public void instantiateController_registerTransitionObserver() {
        verify(mTransitions).registerObserver(any());
    }

    @Test
    public void testAddBubble() {
        mBubbleController.updateBubble(mBubbleEntry);
Loading