Register transitions for Desktop app launch + minimize animations

In other CLs we pass specific RemoteTransitions to Shell for app
launches and unminimize actions. Those CLs cover Launcher actions like
the keyboard alt-tab shortcut, or launching an app from Taskbar.
This CL covers launches that don't originate from Launcher, e.g.
launches through intents.

Test: DesktopAppLaunchTransitionManagerTest
Bug: 369966416
Flag: com.android.window.flags.enable_desktop_app_launch_transitions
Change-Id: I82fd72f870c1e38efe5a8ce533a60f282c10203c
This commit is contained in:
Gustav Sennton
2024-10-29 13:48:44 +00:00
parent 8bd76f5b06
commit 39c006e792
4 changed files with 208 additions and 0 deletions
@@ -143,6 +143,7 @@ class DesktopAppLaunchTransition(
}
companion object {
/** Change modes that represent a task becoming visible / launching in Desktop mode. */
val LAUNCH_CHANGE_MODES = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)
private val launchBoundsAnimationDef =
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2024 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.launcher3.desktop
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.window.DesktopModeFlags
import android.window.RemoteTransition
import android.window.TransitionFilter
import android.window.TransitionFilter.CONTAINER_ORDER_TOP
import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.quickstep.SystemUiProxy
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
/** Manages transitions related to app launches in Desktop Mode. */
class DesktopAppLaunchTransitionManager(
private val context: Context,
private val systemUiProxy: SystemUiProxy,
) {
private var remoteWindowLimitUnminimizeTransition: RemoteTransition? = null
/**
* Register a [RemoteTransition] supporting Desktop app launches, and window limit
* minimizations.
*/
fun registerTransitions() {
if (!shouldRegisterTransitions()) {
return
}
remoteWindowLimitUnminimizeTransition =
RemoteTransition(
DesktopAppLaunchTransition(context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE)
)
systemUiProxy.registerRemoteTransition(
remoteWindowLimitUnminimizeTransition,
buildAppLaunchFilter(),
)
}
/**
* Unregister the [RemoteTransition] supporting Desktop app launches and window limit
* minimizations.
*/
fun unregisterTransitions() {
if (!shouldRegisterTransitions()) {
return
}
systemUiProxy.unregisterRemoteTransition(remoteWindowLimitUnminimizeTransition)
remoteWindowLimitUnminimizeTransition = null
}
private fun shouldRegisterTransitions(): Boolean =
DesktopModeStatus.canEnterDesktopMode(context) &&
DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue
companion object {
private fun buildAppLaunchFilter(): TransitionFilter {
val openRequirement =
TransitionFilter.Requirement().apply {
mActivityType = ACTIVITY_TYPE_STANDARD
mWindowingMode = WINDOWING_MODE_FREEFORM
mModes = DesktopAppLaunchTransition.LAUNCH_CHANGE_MODES
mMustBeTask = true
mOrder = CONTAINER_ORDER_TOP
}
return TransitionFilter().apply {
mTypeSet = DesktopAppLaunchTransition.LAUNCH_CHANGE_MODES
mRequirements = arrayOf(openRequirement)
}
}
}
}
@@ -85,6 +85,7 @@ import com.android.launcher3.EncryptionType;
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.desktop.DesktopAppLaunchTransitionManager;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -662,6 +663,7 @@ public class TouchInteractionService extends Service {
private NavigationMode mGestureStartNavMode = null;
private DesktopVisibilityController mDesktopVisibilityController;
private DesktopAppLaunchTransitionManager mDesktopAppLaunchTransitionManager;
@Override
public void onCreate() {
@@ -686,6 +688,9 @@ public class TouchInteractionService extends Service {
mDesktopVisibilityController = new DesktopVisibilityController(this);
mTaskbarManager = new TaskbarManager(
this, mAllAppsActionManager, mNavCallbacks, mDesktopVisibilityController);
mDesktopAppLaunchTransitionManager =
new DesktopAppLaunchTransitionManager(this, SystemUiProxy.INSTANCE.get(this));
mDesktopAppLaunchTransitionManager.registerTransitions();
if (Flags.enableLauncherOverviewInWindow() || Flags.enableFallbackOverviewInWindow()) {
mRecentsWindowManager = new RecentsWindowManager(this);
}
@@ -851,6 +856,10 @@ public class TouchInteractionService extends Service {
if (mRecentsWindowManager != null) {
mRecentsWindowManager.destroy();
}
if (mDesktopAppLaunchTransitionManager != null) {
mDesktopAppLaunchTransitionManager.unregisterTransitions();
}
mDesktopAppLaunchTransitionManager = null;
mDesktopVisibilityController.onDestroy();
sConnected = false;
@@ -0,0 +1,110 @@
/*
* Copyright (C) 2024 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.launcher3.desktop
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
import android.window.TransitionFilter
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.quickstep.SystemUiProxy
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@SmallTest
@RunWith(AndroidJUnit4::class)
class DesktopAppLaunchTransitionManagerTest {
@get:Rule val mSetFlagsRule = SetFlagsRule()
private val mockitoSession =
mockitoSession()
.strictness(Strictness.LENIENT)
.mockStatic(DesktopModeStatus::class.java)
.startMocking()
private val context = mock<Context>()
private val systemUiProxy = mock<SystemUiProxy>()
private lateinit var transitionManager: DesktopAppLaunchTransitionManager
@Before
fun setUp() {
whenever(context.resources).thenReturn(mock())
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
transitionManager = DesktopAppLaunchTransitionManager(context, systemUiProxy)
}
@After
fun tearDown() {
mockitoSession.finishMocking()
}
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
fun registerTransitions_appLaunchFlagEnabled_registersTransition() {
transitionManager.registerTransitions()
verify(systemUiProxy, times(1)).registerRemoteTransition(any(), any())
}
@Test
@DisableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
fun registerTransitions_appLaunchFlagDisabled_doesntRegisterTransition() {
transitionManager.registerTransitions()
verify(systemUiProxy, times(0)).registerRemoteTransition(any(), any())
}
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
fun registerTransitions_usesCorrectFilter() {
transitionManager.registerTransitions()
val filterArgumentCaptor = argumentCaptor<TransitionFilter>()
verify(systemUiProxy, times(1))
.registerRemoteTransition(any(), filterArgumentCaptor.capture())
assertThat(filterArgumentCaptor.lastValue).isNotNull()
assertThat(filterArgumentCaptor.lastValue.mTypeSet)
.isEqualTo(intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT))
assertThat(filterArgumentCaptor.lastValue.mRequirements).hasLength(1)
val launchRequirement = filterArgumentCaptor.lastValue.mRequirements!![0]
assertThat(launchRequirement.mModes).isEqualTo(intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT))
assertThat(launchRequirement.mActivityType).isEqualTo(ACTIVITY_TYPE_STANDARD)
assertThat(launchRequirement.mWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
}
}