Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +23 −1 Original line number Diff line number Diff line Loading @@ -61,6 +61,7 @@ import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; Loading Loading @@ -677,7 +678,11 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Optional<DesktopMode> provideDesktopMode( Optional<DesktopModeController> desktopModeController) { Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController) { if (DesktopModeStatus.isProto2Enabled()) { return desktopTasksController.map(DesktopTasksController::asDesktopMode); } return desktopModeController.map(DesktopModeController::asDesktopMode); } Loading @@ -698,6 +703,23 @@ public abstract class WMShellBaseModule { return Optional.empty(); } @BindsOptionalOf @DynamicOverride abstract DesktopTasksController optionalDesktopTasksController(); @WMSingleton @Provides static Optional<DesktopTasksController> providesDesktopTasksController( @DynamicOverride Optional<Lazy<DesktopTasksController>> desktopTasksController) { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. if (DesktopModeStatus.isProto2Enabled()) { return desktopTasksController.map(Lazy::get); } return Optional.empty(); } @BindsOptionalOf @DynamicOverride abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository(); Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +17 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; Loading Loading @@ -613,6 +614,22 @@ public abstract class WMShellModule { mainExecutor); } @WMSingleton @Provides @DynamicOverride static DesktopTasksController provideDesktopTasksController( Context context, ShellInit shellInit, ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, @ShellMainThread ShellExecutor mainExecutor ) { return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer, transitions, desktopModeTaskRepository, mainExecutor); } @WMSingleton @Provides @DynamicOverride Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +5 −1 Original line number Diff line number Diff line Loading @@ -70,9 +70,13 @@ public class DesktopModeStatus { * @return {@code true} if active */ public static boolean isActive(Context context) { if (!IS_SUPPORTED) { if (!isAnyEnabled()) { return false; } if (isProto2Enabled()) { // Desktop mode is always active in prototype 2 return true; } try { int result = Settings.System.getIntForUser(context.getContentResolver(), Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT); Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt 0 → 100644 +116 −0 Original line number Diff line number Diff line /* * 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.wm.shell.desktopmode import android.content.Context import androidx.annotation.BinderThread import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.ExternalInterfaceBinder import com.android.wm.shell.common.RemoteCallable import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.ShellSharedConstants import com.android.wm.shell.transition.Transitions import java.util.concurrent.Executor /** Handles moving tasks in and out of desktop */ class DesktopTasksController( private val context: Context, shellInit: ShellInit, private val shellController: ShellController, private val shellTaskOrganizer: ShellTaskOrganizer, private val transitions: Transitions, private val desktopModeTaskRepository: DesktopModeTaskRepository, @ShellMainThread private val mainExecutor: ShellExecutor ) : RemoteCallable<DesktopTasksController> { private val desktopMode: DesktopModeImpl init { desktopMode = DesktopModeImpl() if (DesktopModeStatus.isProto2Enabled()) { shellInit.addInitCallback({ onInit() }, this) } } private fun onInit() { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") shellController.addExternalInterface( ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE, { createExternalInterface() }, this ) } override fun getContext(): Context { return context } override fun getRemoteCallExecutor(): ShellExecutor { return mainExecutor } /** Creates a new instance of the external interface to pass to another process. */ private fun createExternalInterface(): ExternalInterfaceBinder { return IDesktopModeImpl(this) } /** Get connection interface between sysui and shell */ fun asDesktopMode(): DesktopMode { return desktopMode } /** * Adds a listener to find out about changes in the visibility of freeform tasks. * * @param listener the listener to add. * @param callbackExecutor the executor to call the listener on. */ fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) { desktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor) } /** The interface for calls from outside the shell, within the host process. */ @ExternalThread private inner class DesktopModeImpl : DesktopMode { override fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) { mainExecutor.execute { this@DesktopTasksController.addListener(listener, callbackExecutor) } } } /** The interface for calls from outside the host process. */ @BinderThread private class IDesktopModeImpl(private var controller: DesktopTasksController?) : IDesktopMode.Stub(), ExternalInterfaceBinder { /** Invalidates this instance, preventing future calls from updating the controller. */ override fun invalidate() { controller = null } override fun showDesktopApps() { // TODO } } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt 0 → 100644 +98 −0 Original line number Diff line number Diff line /* * 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.wm.shell.desktopmode import android.testing.AndroidTestingRunner 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.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.any import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var shellController: ShellController @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer @Mock lateinit var transitions: Transitions lateinit var mockitoSession: StaticMockitoSession lateinit var controller: DesktopTasksController lateinit var shellInit: ShellInit lateinit var desktopModeTaskRepository: DesktopModeTaskRepository @Before fun setUp() { mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking() whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(true) shellInit = Mockito.spy(ShellInit(testExecutor)) desktopModeTaskRepository = DesktopModeTaskRepository() controller = createController() shellInit.init() } private fun createController(): DesktopTasksController { return DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer, transitions, desktopModeTaskRepository, TestShellExecutor()) } @After fun tearDown() { mockitoSession.finishMocking() } @Test fun instantiate_addInitCallback() { verify(shellInit).addInitCallback(any(), any<DesktopTasksController>()) } @Test fun instantiate_flagOff_doNotAddInitCallback() { whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(false) clearInvocations(shellInit) createController() verify(shellInit, never()).addInitCallback(any(), any<DesktopTasksController>()) } } No newline at end of file Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +23 −1 Original line number Diff line number Diff line Loading @@ -61,6 +61,7 @@ import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; Loading Loading @@ -677,7 +678,11 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Optional<DesktopMode> provideDesktopMode( Optional<DesktopModeController> desktopModeController) { Optional<DesktopModeController> desktopModeController, Optional<DesktopTasksController> desktopTasksController) { if (DesktopModeStatus.isProto2Enabled()) { return desktopTasksController.map(DesktopTasksController::asDesktopMode); } return desktopModeController.map(DesktopModeController::asDesktopMode); } Loading @@ -698,6 +703,23 @@ public abstract class WMShellBaseModule { return Optional.empty(); } @BindsOptionalOf @DynamicOverride abstract DesktopTasksController optionalDesktopTasksController(); @WMSingleton @Provides static Optional<DesktopTasksController> providesDesktopTasksController( @DynamicOverride Optional<Lazy<DesktopTasksController>> desktopTasksController) { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. if (DesktopModeStatus.isProto2Enabled()) { return desktopTasksController.map(Lazy::get); } return Optional.empty(); } @BindsOptionalOf @DynamicOverride abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository(); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +17 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformTaskListener; Loading Loading @@ -613,6 +614,22 @@ public abstract class WMShellModule { mainExecutor); } @WMSingleton @Provides @DynamicOverride static DesktopTasksController provideDesktopTasksController( Context context, ShellInit shellInit, ShellController shellController, ShellTaskOrganizer shellTaskOrganizer, Transitions transitions, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, @ShellMainThread ShellExecutor mainExecutor ) { return new DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer, transitions, desktopModeTaskRepository, mainExecutor); } @WMSingleton @Provides @DynamicOverride Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +5 −1 Original line number Diff line number Diff line Loading @@ -70,9 +70,13 @@ public class DesktopModeStatus { * @return {@code true} if active */ public static boolean isActive(Context context) { if (!IS_SUPPORTED) { if (!isAnyEnabled()) { return false; } if (isProto2Enabled()) { // Desktop mode is always active in prototype 2 return true; } try { int result = Settings.System.getIntForUser(context.getContentResolver(), Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt 0 → 100644 +116 −0 Original line number Diff line number Diff line /* * 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.wm.shell.desktopmode import android.content.Context import androidx.annotation.BinderThread import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.ExternalInterfaceBinder import com.android.wm.shell.common.RemoteCallable import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.sysui.ShellSharedConstants import com.android.wm.shell.transition.Transitions import java.util.concurrent.Executor /** Handles moving tasks in and out of desktop */ class DesktopTasksController( private val context: Context, shellInit: ShellInit, private val shellController: ShellController, private val shellTaskOrganizer: ShellTaskOrganizer, private val transitions: Transitions, private val desktopModeTaskRepository: DesktopModeTaskRepository, @ShellMainThread private val mainExecutor: ShellExecutor ) : RemoteCallable<DesktopTasksController> { private val desktopMode: DesktopModeImpl init { desktopMode = DesktopModeImpl() if (DesktopModeStatus.isProto2Enabled()) { shellInit.addInitCallback({ onInit() }, this) } } private fun onInit() { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController") shellController.addExternalInterface( ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE, { createExternalInterface() }, this ) } override fun getContext(): Context { return context } override fun getRemoteCallExecutor(): ShellExecutor { return mainExecutor } /** Creates a new instance of the external interface to pass to another process. */ private fun createExternalInterface(): ExternalInterfaceBinder { return IDesktopModeImpl(this) } /** Get connection interface between sysui and shell */ fun asDesktopMode(): DesktopMode { return desktopMode } /** * Adds a listener to find out about changes in the visibility of freeform tasks. * * @param listener the listener to add. * @param callbackExecutor the executor to call the listener on. */ fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) { desktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor) } /** The interface for calls from outside the shell, within the host process. */ @ExternalThread private inner class DesktopModeImpl : DesktopMode { override fun addListener(listener: VisibleTasksListener, callbackExecutor: Executor) { mainExecutor.execute { this@DesktopTasksController.addListener(listener, callbackExecutor) } } } /** The interface for calls from outside the host process. */ @BinderThread private class IDesktopModeImpl(private var controller: DesktopTasksController?) : IDesktopMode.Stub(), ExternalInterfaceBinder { /** Invalidates this instance, preventing future calls from updating the controller. */ override fun invalidate() { controller = null } override fun showDesktopApps() { // TODO } } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt 0 → 100644 +98 −0 Original line number Diff line number Diff line /* * 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.wm.shell.desktopmode import android.testing.AndroidTestingRunner 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.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestShellExecutor import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.sysui.ShellController import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.any import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var testExecutor: ShellExecutor @Mock lateinit var shellController: ShellController @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer @Mock lateinit var transitions: Transitions lateinit var mockitoSession: StaticMockitoSession lateinit var controller: DesktopTasksController lateinit var shellInit: ShellInit lateinit var desktopModeTaskRepository: DesktopModeTaskRepository @Before fun setUp() { mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking() whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(true) shellInit = Mockito.spy(ShellInit(testExecutor)) desktopModeTaskRepository = DesktopModeTaskRepository() controller = createController() shellInit.init() } private fun createController(): DesktopTasksController { return DesktopTasksController(context, shellInit, shellController, shellTaskOrganizer, transitions, desktopModeTaskRepository, TestShellExecutor()) } @After fun tearDown() { mockitoSession.finishMocking() } @Test fun instantiate_addInitCallback() { verify(shellInit).addInitCallback(any(), any<DesktopTasksController>()) } @Test fun instantiate_flagOff_doNotAddInitCallback() { whenever(DesktopModeStatus.isProto2Enabled()).thenReturn(false) clearInvocations(shellInit) createController() verify(shellInit, never()).addInitCallback(any(), any<DesktopTasksController>()) } } No newline at end of file