Loading libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt 0 → 100644 +90 −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.common import android.window.WindowContainerToken import android.window.WindowContainerTransaction import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG import com.android.wm.shell.util.KtProtoLog /** * Controller to manage behavior of activities launched with * [android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT]. */ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { /** Allows to temporarily disable launch adjacent handling */ var launchAdjacentEnabled: Boolean = true set(value) { if (field != value) { KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value) field = value container?.let { c -> if (value) { enableContainer(c) } else { disableContainer((c)) } } } } private var container: WindowContainerToken? = null /** * Set [container] as the new launch adjacent flag root container. * * If launch adjacent handling is disabled through [setLaunchAdjacentEnabled], won't set the * container until after it is enabled again. * * @see WindowContainerTransaction.setLaunchAdjacentFlagRoot */ fun setLaunchAdjacentRoot(container: WindowContainerToken) { KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container") this.container = container if (launchAdjacentEnabled) { enableContainer(container) } } /** * Clear a container previously set through [setLaunchAdjacentRoot]. * * Always clears the container, regardless of [launchAdjacentEnabled] value. * * @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot */ fun clearLaunchAdjacentRoot() { KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container") container?.let { disableContainer(it) container = null } } private fun enableContainer(container: WindowContainerToken) { KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container") val wct = WindowContainerTransaction() wct.setLaunchAdjacentFlagRoot(container) syncQueue.queue(wct) } private fun disableContainer(container: WindowContainerToken) { KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container") val wct = WindowContainerTransaction() wct.clearLaunchAdjacentFlagRoot(container) syncQueue.queue(wct) } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java→libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java +4 −4 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ * limitations under the License. */ package com.android.wm.shell.desktopmode; package com.android.wm.shell; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; Loading @@ -25,16 +25,16 @@ import android.window.WindowContainerToken; /** * {@link WindowContainerToken} wrapper that supports a mock binder */ class MockToken { public class MockToken { private final WindowContainerToken mToken; MockToken() { public MockToken() { mToken = mock(WindowContainerToken.class); IBinder binder = mock(IBinder.class); when(mToken.asBinder()).thenReturn(binder); } WindowContainerToken token() { public WindowContainerToken token() { return mToken; } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt 0 → 100644 +172 −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.common import android.os.IBinder import android.testing.AndroidTestingRunner import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp import androidx.test.filters.SmallTest import com.android.wm.shell.MockToken import com.android.wm.shell.ShellTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidTestingRunner::class) class LaunchAdjacentControllerTest : ShellTestCase() { private lateinit var controller: LaunchAdjacentController @Mock private lateinit var syncQueue: SyncTransactionQueue @Before fun setUp() { controller = LaunchAdjacentController(syncQueue) } @Test fun newInstance_enabledByDefault() { assertThat(controller.launchAdjacentEnabled).isTrue() } @Test fun setLaunchAdjacentRoot_launchAdjacentEnabled_setsFlagRoot() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) val wct = getLatestTransactionOrFail() assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) } @Test fun setLaunchAdjacentRoot_launchAdjacentDisabled_doesNotUpdateFlagRoot() { val token = MockToken().token() controller.launchAdjacentEnabled = false controller.setLaunchAdjacentRoot(token) verify(syncQueue, never()).queue(any()) } @Test fun clearLaunchAdjacentRoot_launchAdjacentEnabled_clearsFlagRoot() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) controller.clearLaunchAdjacentRoot() val wct = getLatestTransactionOrFail() assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) } @Test fun clearLaunchAdjacentRoot_launchAdjacentDisabled_clearsFlagRoot() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) controller.launchAdjacentEnabled = false clearInvocations(syncQueue) controller.clearLaunchAdjacentRoot() val wct = getLatestTransactionOrFail() assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) } @Test fun setLaunchAdjacentEnabled_wasDisabledWithContainerSet_setsFlagRoot() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) controller.launchAdjacentEnabled = false clearInvocations(syncQueue) controller.launchAdjacentEnabled = true val wct = getLatestTransactionOrFail() assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) } @Test fun setLaunchAdjacentEnabled_containerNotSet_doesNotUpdateFlagRoot() { controller.launchAdjacentEnabled = false controller.launchAdjacentEnabled = true verify(syncQueue, never()).queue(any()) } @Test fun setLaunchAdjacentEnabled_multipleTimes_setsFlagRootOnce() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) controller.launchAdjacentEnabled = true controller.launchAdjacentEnabled = true // Only execute once verify(syncQueue).queue(any()) } @Test fun setLaunchAdjacentDisabled_containerSet_clearsFlagRoot() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) controller.launchAdjacentEnabled = false val wct = getLatestTransactionOrFail() assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) } @Test fun setLaunchAdjacentDisabled_containerNotSet_doesNotUpdateFlagRoot() { controller.launchAdjacentEnabled = false verify(syncQueue, never()).queue(any()) } @Test fun setLaunchAdjacentDisabled_multipleTimes_setsFlagRootOnce() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) clearInvocations(syncQueue) controller.launchAdjacentEnabled = false controller.launchAdjacentEnabled = false // Only execute once verify(syncQueue).queue(any()) } private fun getLatestTransactionOrFail(): WindowContainerTransaction { val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(syncQueue, atLeastOnce()).queue(arg.capture()) return arg.allValues.last().also { assertThat(it).isNotNull() } } } private fun WindowContainerTransaction.getSetLaunchAdjacentFlagRootContainer(): IBinder { return hierarchyOps // Find the operation with the correct type .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT } // For set flag root operation, toTop is false .filter { op -> !op.toTop } .map { it.container } .first() } private fun WindowContainerTransaction.getClearLaunchAdjacentFlagRootContainer(): IBinder { return hierarchyOps // Find the operation with the correct type .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT } // For clear flag root operation, toTop is true .filter { op -> op.toTop } .map { it.container } .first() } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +1 −0 Original line number Diff line number Diff line Loading @@ -59,6 +59,7 @@ import android.window.WindowContainerTransaction.HierarchyOp; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.MockToken; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +1 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/common/LaunchAdjacentController.kt 0 → 100644 +90 −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.common import android.window.WindowContainerToken import android.window.WindowContainerTransaction import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG import com.android.wm.shell.util.KtProtoLog /** * Controller to manage behavior of activities launched with * [android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT]. */ class LaunchAdjacentController(private val syncQueue: SyncTransactionQueue) { /** Allows to temporarily disable launch adjacent handling */ var launchAdjacentEnabled: Boolean = true set(value) { if (field != value) { KtProtoLog.d(WM_SHELL_TASK_ORG, "set launch adjacent flag root enabled=%b", value) field = value container?.let { c -> if (value) { enableContainer(c) } else { disableContainer((c)) } } } } private var container: WindowContainerToken? = null /** * Set [container] as the new launch adjacent flag root container. * * If launch adjacent handling is disabled through [setLaunchAdjacentEnabled], won't set the * container until after it is enabled again. * * @see WindowContainerTransaction.setLaunchAdjacentFlagRoot */ fun setLaunchAdjacentRoot(container: WindowContainerToken) { KtProtoLog.d(WM_SHELL_TASK_ORG, "set new launch adjacent flag root container") this.container = container if (launchAdjacentEnabled) { enableContainer(container) } } /** * Clear a container previously set through [setLaunchAdjacentRoot]. * * Always clears the container, regardless of [launchAdjacentEnabled] value. * * @see WindowContainerTransaction.clearLaunchAdjacentFlagRoot */ fun clearLaunchAdjacentRoot() { KtProtoLog.d(WM_SHELL_TASK_ORG, "clear launch adjacent flag root container") container?.let { disableContainer(it) container = null } } private fun enableContainer(container: WindowContainerToken) { KtProtoLog.v(WM_SHELL_TASK_ORG, "enable launch adjacent flag root container") val wct = WindowContainerTransaction() wct.setLaunchAdjacentFlagRoot(container) syncQueue.queue(wct) } private fun disableContainer(container: WindowContainerToken) { KtProtoLog.v(WM_SHELL_TASK_ORG, "disable launch adjacent flag root container") val wct = WindowContainerTransaction() wct.clearLaunchAdjacentFlagRoot(container) syncQueue.queue(wct) } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/MockToken.java→libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockToken.java +4 −4 Original line number Diff line number Diff line Loading @@ -14,7 +14,7 @@ * limitations under the License. */ package com.android.wm.shell.desktopmode; package com.android.wm.shell; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; Loading @@ -25,16 +25,16 @@ import android.window.WindowContainerToken; /** * {@link WindowContainerToken} wrapper that supports a mock binder */ class MockToken { public class MockToken { private final WindowContainerToken mToken; MockToken() { public MockToken() { mToken = mock(WindowContainerToken.class); IBinder binder = mock(IBinder.class); when(mToken.asBinder()).thenReturn(binder); } WindowContainerToken token() { public WindowContainerToken token() { return mToken; } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/LaunchAdjacentControllerTest.kt 0 → 100644 +172 −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.common import android.os.IBinder import android.testing.AndroidTestingRunner import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp import androidx.test.filters.SmallTest import com.android.wm.shell.MockToken import com.android.wm.shell.ShellTestCase import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidTestingRunner::class) class LaunchAdjacentControllerTest : ShellTestCase() { private lateinit var controller: LaunchAdjacentController @Mock private lateinit var syncQueue: SyncTransactionQueue @Before fun setUp() { controller = LaunchAdjacentController(syncQueue) } @Test fun newInstance_enabledByDefault() { assertThat(controller.launchAdjacentEnabled).isTrue() } @Test fun setLaunchAdjacentRoot_launchAdjacentEnabled_setsFlagRoot() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) val wct = getLatestTransactionOrFail() assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) } @Test fun setLaunchAdjacentRoot_launchAdjacentDisabled_doesNotUpdateFlagRoot() { val token = MockToken().token() controller.launchAdjacentEnabled = false controller.setLaunchAdjacentRoot(token) verify(syncQueue, never()).queue(any()) } @Test fun clearLaunchAdjacentRoot_launchAdjacentEnabled_clearsFlagRoot() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) controller.clearLaunchAdjacentRoot() val wct = getLatestTransactionOrFail() assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) } @Test fun clearLaunchAdjacentRoot_launchAdjacentDisabled_clearsFlagRoot() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) controller.launchAdjacentEnabled = false clearInvocations(syncQueue) controller.clearLaunchAdjacentRoot() val wct = getLatestTransactionOrFail() assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) } @Test fun setLaunchAdjacentEnabled_wasDisabledWithContainerSet_setsFlagRoot() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) controller.launchAdjacentEnabled = false clearInvocations(syncQueue) controller.launchAdjacentEnabled = true val wct = getLatestTransactionOrFail() assertThat(wct.getSetLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) } @Test fun setLaunchAdjacentEnabled_containerNotSet_doesNotUpdateFlagRoot() { controller.launchAdjacentEnabled = false controller.launchAdjacentEnabled = true verify(syncQueue, never()).queue(any()) } @Test fun setLaunchAdjacentEnabled_multipleTimes_setsFlagRootOnce() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) controller.launchAdjacentEnabled = true controller.launchAdjacentEnabled = true // Only execute once verify(syncQueue).queue(any()) } @Test fun setLaunchAdjacentDisabled_containerSet_clearsFlagRoot() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) controller.launchAdjacentEnabled = false val wct = getLatestTransactionOrFail() assertThat(wct.getClearLaunchAdjacentFlagRootContainer()).isEqualTo(token.asBinder()) } @Test fun setLaunchAdjacentDisabled_containerNotSet_doesNotUpdateFlagRoot() { controller.launchAdjacentEnabled = false verify(syncQueue, never()).queue(any()) } @Test fun setLaunchAdjacentDisabled_multipleTimes_setsFlagRootOnce() { val token = MockToken().token() controller.setLaunchAdjacentRoot(token) clearInvocations(syncQueue) controller.launchAdjacentEnabled = false controller.launchAdjacentEnabled = false // Only execute once verify(syncQueue).queue(any()) } private fun getLatestTransactionOrFail(): WindowContainerTransaction { val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) verify(syncQueue, atLeastOnce()).queue(arg.capture()) return arg.allValues.last().also { assertThat(it).isNotNull() } } } private fun WindowContainerTransaction.getSetLaunchAdjacentFlagRootContainer(): IBinder { return hierarchyOps // Find the operation with the correct type .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT } // For set flag root operation, toTop is false .filter { op -> !op.toTop } .map { it.container } .first() } private fun WindowContainerTransaction.getClearLaunchAdjacentFlagRootContainer(): IBinder { return hierarchyOps // Find the operation with the correct type .filter { op -> op.type == HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT } // For clear flag root operation, toTop is true .filter { op -> op.toTop } .map { it.container } .first() }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java +1 −0 Original line number Diff line number Diff line Loading @@ -59,6 +59,7 @@ import android.window.WindowContainerTransaction.HierarchyOp; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.MockToken; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +1 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase Loading