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

Commit e57c2641 authored by Ats Jenk's avatar Ats Jenk
Browse files

Create controller for desktop prototype 2

Create the controller class, set up dagger.
Ensure controller is not initialized when flag is off.

Bug: 260645044
Test: atest DesktopTasksControllerTest
Change-Id: I0435e07a22e379f403ae5d1b23f39a67a12e8a6d
parent 5e432142
Loading
Loading
Loading
Loading
+23 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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);
    }

@@ -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();
+17 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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
+5 −1
Original line number Diff line number Diff line
@@ -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);
+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
        }
    }
}
+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