| /* |
| * Copyright (C) 2022 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.car.carlauncher; |
| |
| import static android.app.ActivityTaskManager.INVALID_TASK_ID; |
| import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; |
| import static android.view.Display.DEFAULT_DISPLAY; |
| import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT; |
| |
| import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; |
| import static com.android.car.internal.common.CommonConstants.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| |
| import static org.junit.Assert.assertThrows; |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.anyInt; |
| import static org.mockito.ArgumentMatchers.anyLong; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.atLeastOnce; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyZeroInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import android.app.Activity; |
| import android.app.ActivityManager; |
| import android.app.ActivityTaskManager; |
| import android.app.TaskStackListener; |
| import android.car.Car; |
| import android.car.app.CarActivityManager; |
| import android.car.test.mocks.AbstractExtendedMockitoTestCase; |
| import android.car.user.CarUserManager; |
| import android.content.Intent; |
| import android.net.Uri; |
| import android.view.SurfaceControl; |
| import android.view.SurfaceHolder; |
| import android.window.TaskAppearedInfo; |
| import android.window.WindowContainerToken; |
| import android.window.WindowContainerTransaction; |
| |
| import androidx.annotation.NonNull; |
| import androidx.lifecycle.Lifecycle; |
| import androidx.test.core.app.ActivityScenario; |
| import androidx.test.ext.junit.rules.ActivityScenarioRule; |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| |
| import com.android.car.carlauncher.taskstack.TaskStackChangeListeners; |
| import com.android.dx.mockito.inline.extended.ExtendedMockito; |
| import com.android.wm.shell.ShellTaskOrganizer; |
| import com.android.wm.shell.TaskView; |
| import com.android.wm.shell.common.HandlerExecutor; |
| import com.android.wm.shell.common.SyncTransactionQueue; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.Captor; |
| import org.mockito.Mock; |
| import org.mockito.invocation.InvocationOnMock; |
| |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| @RunWith(AndroidJUnit4.class) |
| public class TaskViewManagerTest extends AbstractExtendedMockitoTestCase { |
| @Rule |
| public ActivityScenarioRule mActivityRule = new ActivityScenarioRule<>(TestActivity.class); |
| |
| @Mock |
| private ShellTaskOrganizer mOrganizer; |
| @Mock |
| private SyncTransactionQueue mSyncQueue; |
| @Mock |
| private HandlerExecutor mShellExecutor; |
| @Mock |
| private CarActivityManager mCarActivityManager; |
| @Mock |
| private Car mCar; |
| @Mock |
| private TaskStackChangeListeners mTaskStackChangeListeners; |
| @Mock |
| private CarUserManager mCarUserManager; |
| @Mock |
| private WindowContainerToken mToken; |
| |
| @Captor |
| private ArgumentCaptor<TaskStackListener> mTaskStackListenerArgumentCaptor; |
| @Captor |
| private ArgumentCaptor<CarUserManager.UserLifecycleListener> |
| mUserLifecycleListenerArgumentCaptor; |
| |
| private TestActivity mActivity; |
| private Car.CarServiceLifecycleListener mCarServiceLifecycleListener; |
| private ActivityTaskManager mSpyActivityTaskManager; |
| private CountDownLatch mIdleHandlerLatch = new CountDownLatch(1); |
| private SurfaceControl mLeash; |
| |
| @Override |
| protected void onSessionBuilder(@NonNull CustomMockitoSessionBuilder builder) { |
| builder.spyStatic(ActivityTaskManager.class); |
| builder.spyStatic(Car.class); |
| builder.spyStatic(TaskStackChangeListeners.class); |
| } |
| |
| @Before |
| public void setUp() { |
| ExtendedMockito.doAnswer(invocation -> { |
| mCarServiceLifecycleListener = invocation.getArgument(3); |
| return mCar; |
| }).when(() -> Car.createCar(any(), any(), anyLong(), any())); |
| when(mCar.getCarManager(eq(Car.CAR_ACTIVITY_SERVICE))).thenReturn(mCarActivityManager); |
| when(mCar.getCarManager(eq(Car.CAR_USER_SERVICE))).thenReturn(mCarUserManager); |
| |
| ExtendedMockito.doReturn(mTaskStackChangeListeners).when(() -> |
| TaskStackChangeListeners.getInstance()); |
| doNothing().when(mTaskStackChangeListeners).registerTaskStackListener( |
| mTaskStackListenerArgumentCaptor.capture()); |
| |
| doNothing().when(mCarUserManager).addListener(any(), any(), |
| mUserLifecycleListenerArgumentCaptor.capture()); |
| |
| mLeash = new SurfaceControl.Builder(null) |
| .setName("test") |
| .build(); |
| |
| doAnswer((InvocationOnMock invocationOnMock) -> { |
| SyncTransactionQueue.TransactionRunnable r = |
| invocationOnMock.getArgument(0); |
| r.runWithTransaction(new SurfaceControl.Transaction()); |
| return null; |
| }).when(mSyncQueue).runInSync(any()); |
| |
| doAnswer((InvocationOnMock invocationOnMock) -> { |
| Runnable r = invocationOnMock.getArgument(0); |
| r.run(); |
| return null; |
| }).when(mShellExecutor).execute(any()); |
| doReturn(mShellExecutor).when(mOrganizer).getExecutor(); |
| |
| mSpyActivityTaskManager = spy(ActivityTaskManager.getInstance()); |
| ExtendedMockito.doReturn(mSpyActivityTaskManager).when(() -> |
| ActivityTaskManager.getInstance()); |
| |
| ActivityScenario<TestActivity> scenario = mActivityRule.getScenario(); |
| scenario.onActivity(activity -> mActivity = activity); |
| } |
| |
| private TaskAppearedInfo createMultiWindowTask(int taskId) { |
| ActivityManager.RunningTaskInfo taskInfo = |
| new ActivityManager.RunningTaskInfo(); |
| taskInfo.taskId = taskId; |
| taskInfo.configuration.windowConfiguration.setWindowingMode( |
| WINDOWING_MODE_MULTI_WINDOW); |
| taskInfo.parentTaskId = INVALID_TASK_ID; |
| taskInfo.token = mToken; |
| return new TaskAppearedInfo(taskInfo, new SurfaceControl()); |
| } |
| |
| @Test |
| public void init_cleansUpExistingMultiWindowTasks() { |
| TaskAppearedInfo existingTask1 = createMultiWindowTask(/* taskId= */ 1); |
| TaskAppearedInfo existingTask2 = createMultiWindowTask(/* taskId= */ 2); |
| doReturn(ImmutableList.of(existingTask1, existingTask2)) |
| .when(mOrganizer).registerOrganizer(); |
| ExtendedMockito.doReturn(false).when(mSpyActivityTaskManager).removeTask(anyInt()); |
| |
| new TaskViewManager(mActivity, mShellExecutor, mOrganizer, mSyncQueue); |
| |
| verify(mSpyActivityTaskManager).removeTask(eq(1)); |
| verify(mSpyActivityTaskManager).removeTask(eq(2)); |
| } |
| |
| @Test |
| public void testCreateControlledTaskView() throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| Intent activityIntent = new Intent(); |
| Set<String> packagesThatCanRestart = ImmutableSet.of("com.random.package"); |
| ControlledCarTaskViewCallbacks controlledCarTaskViewCallbacks = mock( |
| ControlledCarTaskViewCallbacks.class); |
| when(controlledCarTaskViewCallbacks.getDependingPackageNames()) |
| .thenReturn(packagesThatCanRestart); |
| |
| taskViewManager.createControlledCarTaskView( |
| mActivity.getMainExecutor(), |
| activityIntent, |
| /* autoRestartOnCrash= */ false, |
| controlledCarTaskViewCallbacks |
| ); |
| |
| runOnMainAndWait(() -> {}); |
| verify(controlledCarTaskViewCallbacks).onTaskViewCreated(any()); |
| } |
| |
| @Test |
| public void testCreateControlledTaskView_callsOnReadyWhenVisible() throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, mOrganizer, |
| mSyncQueue); |
| Intent activityIntent = new Intent("ACTION_VIEW"); |
| Set<String> packagesThatCanRestart = ImmutableSet.of("com.random.package"); |
| ControlledCarTaskViewCallbacks controlledCarTaskViewCallbacks = mock( |
| ControlledCarTaskViewCallbacks.class); |
| when(controlledCarTaskViewCallbacks.getDependingPackageNames()) |
| .thenReturn(packagesThatCanRestart); |
| taskViewManager.createControlledCarTaskView( |
| mActivity.getMainExecutor(), |
| activityIntent, |
| /* autoRestartOnCrash= */ false, |
| controlledCarTaskViewCallbacks |
| ); |
| ControlledCarTaskView taskView = spy(taskViewManager.getControlledTaskViews().get(0)); |
| doNothing().when(taskView).startActivity(); |
| |
| taskView.surfaceCreated(mock(SurfaceHolder.class)); |
| |
| runOnMainAndWait(() -> {}); |
| verify(controlledCarTaskViewCallbacks).onTaskViewCreated(any()); |
| verify(controlledCarTaskViewCallbacks).onTaskViewReady(); |
| } |
| |
| @Test |
| public void testCreateLaunchRootTaskView() throws Exception { |
| LaunchRootCarTaskViewCallbacks taskViewCallbacks = |
| mock(LaunchRootCarTaskViewCallbacks.class); |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| |
| taskViewManager.createLaunchRootTaskView( |
| mActivity.getMainExecutor(), |
| taskViewCallbacks |
| ); |
| runOnMainAndWait(() -> {}); |
| TaskView taskView = taskViewManager.getLaunchRootCarTaskView(); |
| taskView.surfaceCreated(mock(SurfaceHolder.class)); |
| |
| runOnMainAndWait(() -> {}); |
| verify(taskViewCallbacks).onTaskViewCreated(any()); |
| verify(mOrganizer).createRootTask(eq(DEFAULT_DISPLAY), |
| eq(WINDOWING_MODE_MULTI_WINDOW), |
| any(ShellTaskOrganizer.TaskListener.class)); |
| } |
| |
| @Test |
| public void testCreateLaunchRootTaskView_callsOnReadyWhenVisible() throws Exception { |
| TaskAppearedInfo fakeLaunchRootTaskInfo = createMultiWindowTask(1); |
| LaunchRootCarTaskViewCallbacks taskViewCallbacks = |
| mock(LaunchRootCarTaskViewCallbacks.class); |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| doAnswer(invocation -> { |
| ShellTaskOrganizer.TaskListener listener = invocation.getArgument(2); |
| listener.onTaskAppeared(fakeLaunchRootTaskInfo.getTaskInfo(), mLeash); |
| return null; |
| }).when(mOrganizer).createRootTask(eq(DEFAULT_DISPLAY), |
| eq(WINDOWING_MODE_MULTI_WINDOW), |
| any(ShellTaskOrganizer.TaskListener.class)); |
| |
| taskViewManager.createLaunchRootTaskView( |
| mActivity.getMainExecutor(), |
| taskViewCallbacks |
| ); |
| runOnMainAndWait(() -> {}); |
| TaskView taskView = taskViewManager.getLaunchRootCarTaskView(); |
| taskView.surfaceCreated(mock(SurfaceHolder.class)); |
| |
| runOnMainAndWait(() -> {}); |
| verify(taskViewCallbacks).onTaskViewCreated(any()); |
| verify(taskViewCallbacks).onTaskViewReady(); |
| ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass( |
| WindowContainerTransaction.class); |
| verify(mSyncQueue, atLeastOnce()).queue(wctCaptor.capture()); |
| List<WindowContainerTransaction> wcts = wctCaptor.getAllValues(); |
| assertWithMessage("There must be a WindowContainerTransaction to set the" |
| + " root task as the launch root.") |
| .that(wcts.stream() |
| .flatMap(wct -> wct.getHierarchyOps().stream()) |
| .map(WindowContainerTransaction.HierarchyOp::getType) |
| .anyMatch(type -> type == HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT)) |
| .isTrue(); |
| } |
| |
| @Test |
| public void testCreateSemiControlledTaskView_launchRootTaskViewAbsent_throwsError() |
| throws Exception { |
| SemiControlledCarTaskViewCallbacks taskViewCallbacks = |
| mock(SemiControlledCarTaskViewCallbacks.class); |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| |
| // The exception happens in the current stack because mShellExecutor simply calls .run() |
| assertThrows(IllegalStateException.class, () -> { |
| taskViewManager.createSemiControlledTaskView( |
| mActivity.getMainExecutor(), |
| taskViewCallbacks |
| ); |
| }); |
| |
| runOnMainAndWait(() -> {}); |
| verifyZeroInteractions(taskViewCallbacks); |
| } |
| |
| @Test |
| public void testCreateSemiControlledTaskView() throws Exception { |
| SemiControlledCarTaskViewCallbacks taskViewCallbacks = |
| mock(SemiControlledCarTaskViewCallbacks.class); |
| when(taskViewCallbacks.shouldStartInTaskView(any())).thenReturn(true); |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| runOnMainAndWait(() -> {}); |
| AtomicReference<ShellTaskOrganizer.TaskListener> listener = new AtomicReference<>(); |
| setUpLaunchRootTaskView(taskViewManager, listener, /* rootTaskId = */ 1); |
| |
| taskViewManager.createSemiControlledTaskView( |
| mActivity.getMainExecutor(), |
| taskViewCallbacks |
| ); |
| runOnMainAndWait(() -> {}); |
| // Trigger surfaceCreated on SemiControlledTaskView so that taskView can get into the |
| // initialized state. |
| SemiControlledCarTaskView semiControlledCarTaskView = |
| taskViewManager.getSemiControlledTaskViews().get(0); |
| semiControlledCarTaskView.surfaceCreated(mock(SurfaceHolder.class)); |
| runOnMainAndWait(() -> {}); |
| |
| verify(taskViewCallbacks).onTaskViewCreated(any()); |
| verify(taskViewCallbacks).onTaskViewReady(); |
| } |
| |
| @Test |
| public void testSemiControlledTaskAppeared_reparentedCorrectly() throws Exception { |
| SemiControlledCarTaskViewCallbacks taskViewCallbacks = |
| mock(SemiControlledCarTaskViewCallbacks.class); |
| when(taskViewCallbacks.shouldStartInTaskView(any())).thenReturn(true); |
| |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| runOnMainAndWait(() -> {}); |
| // Set up a LaunchRootTaskView |
| AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>(); |
| setUpLaunchRootTaskView(taskViewManager, rootTaskListener, /* rootTaskId = */ 1); |
| // Set up a SemiControlledTaskView |
| taskViewManager.createSemiControlledTaskView( |
| mActivity.getMainExecutor(), |
| taskViewCallbacks |
| ); |
| runOnMainAndWait(() -> {}); |
| SemiControlledCarTaskView semiControlledCarTaskView = |
| taskViewManager.getSemiControlledTaskViews().get(0); |
| semiControlledCarTaskView.surfaceCreated(mock(SurfaceHolder.class)); |
| runOnMainAndWait(() -> {}); |
| TaskView.Listener mockListener = mock(TaskView.Listener.class); |
| semiControlledCarTaskView.setListener(mActivity.getMainExecutor(), mockListener); |
| |
| // Act |
| // Trigger a taskAppeared on the launch root task to mimic the task appearance. |
| rootTaskListener.get().onTaskAppeared(createMultiWindowTask(2).getTaskInfo(), mLeash); |
| runOnMainAndWait(() -> {}); |
| |
| // Assert |
| // Verify if the task was reparented in the SemiControlledTaskView |
| verify(mockListener).onTaskCreated(eq(2), any()); |
| } |
| |
| @Test |
| public void testSemiControlledTaskVanished_reparentedCorrectly() throws Exception { |
| SemiControlledCarTaskViewCallbacks taskViewCallbacks = |
| mock(SemiControlledCarTaskViewCallbacks.class); |
| when(taskViewCallbacks.shouldStartInTaskView(any())).thenReturn(true); |
| |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| runOnMainAndWait(() -> {}); |
| // Set up a LaunchRootTaskView |
| AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>(); |
| setUpLaunchRootTaskView(taskViewManager, rootTaskListener, /* rootTaskId = */ 1); |
| // Set up a SemiControlledTaskView |
| taskViewManager.createSemiControlledTaskView( |
| mActivity.getMainExecutor(), |
| taskViewCallbacks |
| ); |
| runOnMainAndWait(() -> {}); |
| SemiControlledCarTaskView semiControlledCarTaskView = |
| taskViewManager.getSemiControlledTaskViews().get(0); |
| semiControlledCarTaskView.surfaceCreated(mock(SurfaceHolder.class)); |
| runOnMainAndWait(() -> {}); |
| TaskView.Listener mockListener = mock(TaskView.Listener.class); |
| semiControlledCarTaskView.setListener(mActivity.getMainExecutor(), mockListener); |
| |
| ActivityManager.RunningTaskInfo semiControlledTaskInfo = createMultiWindowTask(2) |
| .getTaskInfo(); |
| rootTaskListener.get().onTaskAppeared(semiControlledTaskInfo, mLeash); |
| runOnMainAndWait(() -> {}); |
| |
| // Act |
| // Trigger a taskVanished on the launch root task |
| rootTaskListener.get().onTaskVanished(semiControlledTaskInfo); |
| runOnMainAndWait(() -> {}); |
| |
| // Assert |
| // Verify if the task was removed from the SemiControlledTaskView |
| verify(mockListener).onTaskRemovalStarted(/* taskId = */ eq(2)); |
| } |
| |
| private void setUpLaunchRootTaskView(TaskViewManager taskViewManager, |
| AtomicReference<ShellTaskOrganizer.TaskListener> listener, |
| int rootTaskId) throws Exception { |
| doAnswer(invocation -> { |
| listener.set(invocation.getArgument(2)); |
| listener.get().onTaskAppeared(createMultiWindowTask(rootTaskId).getTaskInfo(), mLeash); |
| return null; |
| }).when(mOrganizer).createRootTask(eq(DEFAULT_DISPLAY), |
| eq(WINDOWING_MODE_MULTI_WINDOW), |
| any(ShellTaskOrganizer.TaskListener.class)); |
| taskViewManager.createLaunchRootTaskView( |
| mActivity.getMainExecutor(), |
| mock(LaunchRootCarTaskViewCallbacks.class) |
| ); |
| runOnMainAndWait(() -> {}); |
| LaunchRootCarTaskView launchRootCarTaskView = taskViewManager.getLaunchRootCarTaskView(); |
| launchRootCarTaskView.surfaceCreated(mock(SurfaceHolder.class)); |
| runOnMainAndWait(() -> {}); |
| } |
| |
| @Test |
| public void testInit_registersTaskMonitor() throws Exception { |
| new TaskViewManager(mActivity, mShellExecutor, mOrganizer, mSyncQueue); |
| runOnMainAndWait(() -> {}); |
| |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| |
| verify(mCarActivityManager).registerTaskMonitor(); |
| } |
| |
| @Test |
| public void testTaskAppeared_launchRootTaskView_updatesCarActivityManager() throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| runOnMainAndWait(() -> {}); |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| // Set up a LaunchRootTaskView |
| AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>(); |
| setUpLaunchRootTaskView(taskViewManager, rootTaskListener, /* rootTaskId = */ 1); |
| ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo(); |
| |
| // Act |
| rootTaskListener.get().onTaskAppeared(taskInfo, mLeash); |
| runOnMainAndWait(() -> {}); |
| |
| // Assert |
| verify(mCarActivityManager).onTaskAppeared(taskInfo); |
| } |
| |
| @Test |
| public void testTaskInfoChanged_launchRootTaskView_updatesCarActivityManager() |
| throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| runOnMainAndWait(() -> {}); |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| // Set up a LaunchRootTaskView |
| AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>(); |
| setUpLaunchRootTaskView(taskViewManager, rootTaskListener, /* rootTaskId = */ 1); |
| ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo(); |
| |
| // Act |
| rootTaskListener.get().onTaskInfoChanged(taskInfo); |
| runOnMainAndWait(() -> {}); |
| |
| // Assert |
| verify(mCarActivityManager).onTaskInfoChanged(taskInfo); |
| } |
| |
| @Test |
| public void testTaskVanished_launchRootTaskView_updatesCarActivityManager() throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| runOnMainAndWait(() -> {}); |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| // Set up a LaunchRootTaskView |
| AtomicReference<ShellTaskOrganizer.TaskListener> rootTaskListener = new AtomicReference<>(); |
| setUpLaunchRootTaskView(taskViewManager, rootTaskListener, /* rootTaskId = */ 1); |
| ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo(); |
| |
| // Act |
| rootTaskListener.get().onTaskVanished(taskInfo); |
| runOnMainAndWait(() -> {}); |
| |
| // Assert |
| verify(mCarActivityManager).onTaskVanished(taskInfo); |
| } |
| |
| @Test |
| public void testHostActivityDestroyed_releasesAllTaskViews() throws Exception { |
| testReleaseAllTaskViews(() -> { |
| ActivityScenario<TestActivity> scenario = mActivityRule.getScenario(); |
| scenario.moveToState(Lifecycle.State.DESTROYED); |
| }); |
| } |
| |
| private void setUpControlledTaskView(TaskViewManager taskViewManager, Intent activityIntent, |
| Set<String> packagesThatCanRestart) throws Exception { |
| ControlledCarTaskViewCallbacks controlledCarTaskViewCallbacks = mock( |
| ControlledCarTaskViewCallbacks.class); |
| when(controlledCarTaskViewCallbacks.getDependingPackageNames()) |
| .thenReturn(packagesThatCanRestart); |
| taskViewManager.createControlledCarTaskView( |
| mActivity.getMainExecutor(), |
| activityIntent, |
| false, |
| controlledCarTaskViewCallbacks |
| ); |
| |
| int lastIndex = Math.min(0, taskViewManager.getControlledTaskViews().size() - 1); |
| ControlledCarTaskView taskView = spy(taskViewManager.getControlledTaskViews() |
| .get(lastIndex)); |
| doNothing().when(taskView).startActivity(); |
| |
| taskView.surfaceCreated(mock(SurfaceHolder.class)); |
| runOnMainAndWait(() -> {}); |
| } |
| |
| @Test |
| public void testRestartControlledTask_whenHostActivityFocussed() throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| |
| // Send onTaskVanished to mimic the task removal behavior. |
| setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"), |
| ImmutableSet.of("com.random.package2")); |
| ControlledCarTaskView controlledCarTaskView = |
| taskViewManager.getControlledTaskViews().get(0); |
| ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo(); |
| controlledCarTaskView.onTaskAppeared(taskInfo, mLeash); |
| controlledCarTaskView.onTaskVanished(taskInfo); |
| assertThat(controlledCarTaskView.getTaskId()).isEqualTo(INVALID_TASK_ID); |
| |
| // Stub the taskview with a spy to assert on startActivity. |
| ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView); |
| doNothing().when(spiedTaskView).startActivity(); |
| taskViewManager.getControlledTaskViews().set(0, spiedTaskView); |
| |
| // Act |
| mTaskStackListenerArgumentCaptor.getValue().onTaskFocusChanged(mActivity.getTaskId(), |
| /* focused = */ true); |
| |
| // Assert |
| verify(spiedTaskView).startActivity(); |
| } |
| |
| @Test |
| public void testControlledTaskNotRestarted_ifAlreadyRunning_whenHostActivityFocussed() |
| throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| |
| setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"), |
| ImmutableSet.of("com.random.package2")); |
| ControlledCarTaskView controlledCarTaskView = |
| taskViewManager.getControlledTaskViews().get(0); |
| ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo(); |
| controlledCarTaskView.onTaskAppeared(taskInfo, mLeash); |
| |
| // Stub the taskview with a spy to assert on startActivity. |
| ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView); |
| doNothing().when(spiedTaskView).startActivity(); |
| taskViewManager.getControlledTaskViews().set(0, spiedTaskView); |
| |
| // Act |
| mTaskStackListenerArgumentCaptor.getValue().onTaskFocusChanged(mActivity.getTaskId(), |
| /* focused = */ true); |
| |
| // Assert |
| verify(spiedTaskView, times(0)).startActivity(); |
| } |
| |
| @Test |
| public void testRestartControlledTask_whenHostActivityRestarted() throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| |
| // Send onTaskVanished to mimic the task removal behavior. |
| setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"), |
| ImmutableSet.of("com.random.package2")); |
| ControlledCarTaskView controlledCarTaskView = |
| taskViewManager.getControlledTaskViews().get(0); |
| ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo(); |
| controlledCarTaskView.onTaskAppeared(taskInfo, mLeash); |
| controlledCarTaskView.onTaskVanished(taskInfo); |
| assertThat(controlledCarTaskView.getTaskId()).isEqualTo(INVALID_TASK_ID); |
| |
| // Stub the taskview with a spy to assert on startActivity. |
| ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView); |
| doNothing().when(spiedTaskView).startActivity(); |
| taskViewManager.getControlledTaskViews().set(0, spiedTaskView); |
| |
| // Act |
| mTaskStackListenerArgumentCaptor.getValue().onActivityRestartAttempt( |
| createMultiWindowTask(mActivity.getTaskId()).getTaskInfo(), |
| /* homeTaskVisible = */ true, false, |
| /* focused = */ true); |
| |
| // Assert |
| verify(spiedTaskView).startActivity(); |
| } |
| |
| @Test |
| public void testRestartControlledTask_whenPackageThatCanRestartChanged() throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| |
| // Send onTaskVanished to mimic the task removal behavior. |
| setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"), |
| ImmutableSet.of("com.relevant.package")); |
| ControlledCarTaskView controlledCarTaskView = |
| taskViewManager.getControlledTaskViews().get(0); |
| ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo(); |
| controlledCarTaskView.onTaskAppeared(taskInfo, mLeash); |
| controlledCarTaskView.onTaskVanished(taskInfo); |
| assertThat(controlledCarTaskView.getTaskId()).isEqualTo(INVALID_TASK_ID); |
| |
| // Stub the taskview with a spy to assert on startActivity. |
| ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView); |
| doNothing().when(spiedTaskView).startActivity(); |
| taskViewManager.getControlledTaskViews().set(0, spiedTaskView); |
| |
| // Act |
| taskViewManager.getPackageBroadcastReceiver().onReceive(mActivity, |
| new Intent().setData(Uri.parse("package:com.relevant.package"))); |
| |
| // Assert |
| verify(spiedTaskView).startActivity(); |
| } |
| |
| @Test |
| public void testControlledTaskNotRestarted_whenARandomPackageChanged() throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| |
| // Send onTaskVanished to mimic the task removal behavior. |
| setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"), |
| ImmutableSet.of("com.relevant.package")); |
| ControlledCarTaskView controlledCarTaskView = |
| taskViewManager.getControlledTaskViews().get(0); |
| ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo(); |
| controlledCarTaskView.onTaskAppeared(taskInfo, mLeash); |
| controlledCarTaskView.onTaskVanished(taskInfo); |
| assertThat(controlledCarTaskView.getTaskId()).isEqualTo(INVALID_TASK_ID); |
| |
| // Stub the taskview with a spy to assert on startActivity. |
| ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView); |
| doNothing().when(spiedTaskView).startActivity(); |
| taskViewManager.getControlledTaskViews().set(0, spiedTaskView); |
| |
| // Act |
| taskViewManager.getPackageBroadcastReceiver().onReceive(mActivity, |
| new Intent().setData(Uri.parse("package:com.random.package"))); |
| |
| // Assert |
| verify(spiedTaskView, times(0)).startActivity(); |
| } |
| |
| // User switch related tests. |
| |
| @Test |
| public void testRestartControlledTask_onUserUnlocked() throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| |
| // Send onTaskVanished to mimic the task removal behavior. |
| setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"), |
| ImmutableSet.of("com.random.package2")); |
| ControlledCarTaskView controlledCarTaskView = |
| taskViewManager.getControlledTaskViews().get(0); |
| ActivityManager.RunningTaskInfo taskInfo = createMultiWindowTask(2).getTaskInfo(); |
| controlledCarTaskView.onTaskAppeared(taskInfo, mLeash); |
| controlledCarTaskView.onTaskVanished(taskInfo); |
| assertThat(controlledCarTaskView.getTaskId()).isEqualTo(INVALID_TASK_ID); |
| |
| // Stub the taskview with a spy to assert on startActivity. |
| ControlledCarTaskView spiedTaskView = spy(controlledCarTaskView); |
| doNothing().when(spiedTaskView).startActivity(); |
| taskViewManager.getControlledTaskViews().set(0, spiedTaskView); |
| |
| // Act |
| mUserLifecycleListenerArgumentCaptor.getValue().onEvent( |
| new CarUserManager.UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED, |
| mActivity.getUserId())); |
| |
| // Assert |
| verify(spiedTaskView).startActivity(); |
| } |
| |
| @Test |
| public void testUserSwitch_releasesAllTaskViews() throws Exception { |
| testReleaseAllTaskViews(/* actionBlock= */ () -> |
| mUserLifecycleListenerArgumentCaptor.getValue().onEvent( |
| new CarUserManager.UserLifecycleEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING, |
| /* from= */ mActivity.getUserId(), /* to= */ 20)) |
| ); |
| } |
| |
| private void testReleaseAllTaskViews(Runnable actionBlock) throws Exception { |
| TaskViewManager taskViewManager = new TaskViewManager(mActivity, mShellExecutor, |
| mOrganizer, mSyncQueue); |
| runOnMainAndWait(() -> {}); |
| mCarServiceLifecycleListener.onLifecycleChanged(mCar, true); |
| // Create a few TaskViews |
| AtomicReference<ShellTaskOrganizer.TaskListener> listener = new AtomicReference<>(); |
| setUpLaunchRootTaskView(taskViewManager, listener, /* rootTaskId = */ 1); |
| LaunchRootCarTaskView launchRootCarTaskView = taskViewManager.getLaunchRootCarTaskView(); |
| |
| setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"), |
| ImmutableSet.of("com.random.package")); |
| ControlledCarTaskView controlledCarTaskView = taskViewManager.getControlledTaskViews() |
| .get(0); |
| setUpControlledTaskView(taskViewManager, new Intent("ACTION_VIEW"), |
| ImmutableSet.of("com.random.package2")); |
| ControlledCarTaskView controlledCarTaskView2 = taskViewManager.getControlledTaskViews() |
| .get(1); |
| |
| SemiControlledCarTaskViewCallbacks taskViewCallbacks = |
| mock(SemiControlledCarTaskViewCallbacks.class); |
| when(taskViewCallbacks.shouldStartInTaskView(any())).thenReturn(true); |
| taskViewManager.createSemiControlledTaskView( |
| mActivity.getMainExecutor(), |
| taskViewCallbacks |
| ); |
| runOnMainAndWait(() -> {}); |
| taskViewManager.createSemiControlledTaskView( |
| mActivity.getMainExecutor(), |
| taskViewCallbacks |
| ); |
| runOnMainAndWait(() -> {}); |
| // Trigger surfaceCreated on SemiControlledTaskView. |
| SemiControlledCarTaskView semiControlledCarTaskView = |
| taskViewManager.getSemiControlledTaskViews().get(0); |
| semiControlledCarTaskView.surfaceCreated(mock(SurfaceHolder.class)); |
| runOnMainAndWait(() -> {}); |
| SemiControlledCarTaskView semiControlledCarTaskView2 = |
| taskViewManager.getSemiControlledTaskViews().get(1); |
| semiControlledCarTaskView2.surfaceCreated(mock(SurfaceHolder.class)); |
| runOnMainAndWait(() -> {}); |
| |
| // Act |
| actionBlock.run(); |
| |
| // Assert |
| assertThat(launchRootCarTaskView.isInitialized()).isFalse(); |
| assertThat(controlledCarTaskView.isInitialized()).isFalse(); |
| assertThat(controlledCarTaskView2.isInitialized()).isFalse(); |
| assertThat(semiControlledCarTaskView.isInitialized()).isFalse(); |
| assertThat(semiControlledCarTaskView2.isInitialized()).isFalse(); |
| |
| assertThat(taskViewManager.getSemiControlledTaskViews()).isEmpty(); |
| assertThat(taskViewManager.getLaunchRootCarTaskView()).isNull(); |
| assertThat(taskViewManager.getControlledTaskViews()).isEmpty(); |
| |
| verify(mOrganizer).unregisterOrganizer(); |
| } |
| |
| private void runOnMainAndWait(Runnable r) throws Exception { |
| mActivity.getMainExecutor().execute(() -> { |
| r.run(); |
| mIdleHandlerLatch.countDown(); |
| mIdleHandlerLatch = new CountDownLatch(1); |
| }); |
| mIdleHandlerLatch.await(5, TimeUnit.SECONDS); |
| } |
| |
| public static class TestActivity extends Activity {} |
| } |