diff options
| author | 2024-07-15 13:24:39 +0000 | |
|---|---|---|
| committer | 2024-07-15 13:24:39 +0000 | |
| commit | 83e90f0ea1ccc23053405c7698af47c16d75f434 (patch) | |
| tree | fbb6027e4701a4fa19a046a213ff135b7a84b5a1 | |
| parent | baf2c31abd492a5b5804fb0277c461c57f991898 (diff) | |
| parent | 194fbdec67091d3bdb737253a058a4a0cd65712d (diff) | |
Merge "Enable origin animations when launching widgets from Communal." into main
12 files changed, 424 insertions, 64 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index 0de036988337..cee8ae95de6e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -98,7 +98,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { } @Test - fun keyguardGoesAway_forceBlankScene() = + fun keyguardGoesAway_whenLaunchingWidget_doNotForceBlankScene() = with(kosmos) { testScope.runTest { val scene by collectLastValue(communalSceneInteractor.currentScene) @@ -106,6 +106,27 @@ class CommunalSceneStartableTest : SysuiTestCase() { communalSceneInteractor.changeScene(CommunalScenes.Communal) assertThat(scene).isEqualTo(CommunalScenes.Communal) + communalSceneInteractor.setIsLaunchingWidget(true) + fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.GONE, + testScope = this + ) + + assertThat(scene).isEqualTo(CommunalScenes.Communal) + } + } + + @Test + fun keyguardGoesAway_whenNotLaunchingWidget_forceBlankScene() = + with(kosmos) { + testScope.runTest { + val scene by collectLastValue(communalSceneInteractor.currentScene) + + communalSceneInteractor.changeScene(CommunalScenes.Communal) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + communalSceneInteractor.setIsLaunchingWidget(false) fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt index 0cd3fb28e299..d51d3567dd8d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandlerTest.kt @@ -26,14 +26,23 @@ import androidx.core.util.component2 import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.testKosmos +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.isNull import org.mockito.kotlin.mock -import org.mockito.kotlin.notNull import org.mockito.kotlin.refEq import org.mockito.kotlin.verify @@ -41,6 +50,7 @@ import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class SmartspaceInteractionHandlerTest : SysuiTestCase() { private val activityStarter = mock<ActivityStarter>() + private val kosmos = testKosmos() private val testIntent = PendingIntent.getActivity( @@ -51,29 +61,43 @@ class SmartspaceInteractionHandlerTest : SysuiTestCase() { ) private val testResponse = RemoteResponse.fromPendingIntent(testIntent) - private val underTest: SmartspaceInteractionHandler by lazy { - SmartspaceInteractionHandler(activityStarter) + private lateinit var underTest: SmartspaceInteractionHandler + + @Before + fun setUp() { + with(kosmos) { + underTest = SmartspaceInteractionHandler(activityStarter, communalSceneInteractor) + } } @Test fun launchAnimatorIsUsedForSmartspaceView() { - val parent = FrameLayout(context) - val view = SmartspaceAppWidgetHostView(context) - parent.addView(view) - val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view) + with(kosmos) { + testScope.runTest { + val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget) + assertFalse(launching!!) - underTest.onInteraction(view, testIntent, testResponse) + val parent = FrameLayout(context) + val view = SmartspaceAppWidgetHostView(context) + parent.addView(view) + val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view) - // Verify that we pass in a non-null animation controller - verify(activityStarter) - .startPendingIntentWithoutDismissing( - /* intent = */ eq(testIntent), - /* dismissShade = */ eq(false), - /* intentSentUiThreadCallback = */ isNull(), - /* animationController = */ notNull(), - /* fillInIntent = */ refEq(fillInIntent), - /* extraOptions = */ refEq(activityOptions.toBundle()), - ) + underTest.onInteraction(view, testIntent, testResponse) + + // Verify that we set the state correctly + assertTrue(launching!!) + // Verify that we pass in a non-null Communal animation controller + verify(activityStarter) + .startPendingIntentWithoutDismissing( + /* intent = */ eq(testIntent), + /* dismissShade = */ eq(false), + /* intentSentUiThreadCallback = */ isNull(), + /* animationController = */ any<CommunalTransitionAnimatorController>(), + /* fillInIntent = */ refEq(fillInIntent), + /* extraOptions = */ refEq(activityOptions.toBundle()), + ) + } + } } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt new file mode 100644 index 000000000000..ac50db483301 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt @@ -0,0 +1,115 @@ +/* + * 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. + */ + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Truth +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class CommunalTransitionAnimatorControllerTest : SysuiTestCase() { + private val controller = mock<ActivityTransitionAnimator.Controller>() + private val kosmos = testKosmos() + + private lateinit var underTest: CommunalTransitionAnimatorController + + @Before + fun setUp() { + with(kosmos) { + underTest = CommunalTransitionAnimatorController(controller, communalSceneInteractor) + } + } + + @Test + fun doNotAnimate_launchingWidgetStateIsCleared() { + with(kosmos) { + testScope.runTest { + val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget) + + communalSceneInteractor.setIsLaunchingWidget(true) + assertTrue(launching!!) + + underTest.onIntentStarted(willAnimate = false) + assertFalse(launching!!) + verify(controller).onIntentStarted(willAnimate = false) + } + } + } + + @Test + fun animationCancelled_launchingWidgetStateIsClearedAndSceneIsNotChanged() { + with(kosmos) { + testScope.runTest { + val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget) + val scene by collectLastValue(communalSceneInteractor.currentScene) + + communalSceneInteractor.changeScene(CommunalScenes.Communal) + Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal) + communalSceneInteractor.setIsLaunchingWidget(true) + assertTrue(launching!!) + + underTest.onIntentStarted(willAnimate = true) + assertTrue(launching!!) + verify(controller).onIntentStarted(willAnimate = true) + + underTest.onTransitionAnimationCancelled(newKeyguardOccludedState = true) + assertFalse(launching!!) + Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal) + verify(controller).onTransitionAnimationCancelled(newKeyguardOccludedState = true) + } + } + } + + @Test + fun animationComplete_launchingWidgetStateIsClearedAndSceneIsChanged() { + with(kosmos) { + testScope.runTest { + val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget) + val scene by collectLastValue(communalSceneInteractor.currentScene) + + communalSceneInteractor.changeScene(CommunalScenes.Communal) + Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal) + communalSceneInteractor.setIsLaunchingWidget(true) + assertTrue(launching!!) + + underTest.onIntentStarted(willAnimate = true) + assertTrue(launching!!) + verify(controller).onIntentStarted(willAnimate = true) + + underTest.onTransitionAnimationEnd(isExpandingFullyAbove = true) + assertFalse(launching!!) + Truth.assertThat(scene).isEqualTo(CommunalScenes.Blank) + verify(controller).onTransitionAnimationEnd(isExpandingFullyAbove = true) + } + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt index 70448955eff0..ea8b5ab70cd9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt @@ -26,13 +26,21 @@ import androidx.core.util.component2 import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.testKosmos +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any import org.mockito.kotlin.eq import org.mockito.kotlin.isNull import org.mockito.kotlin.mock -import org.mockito.kotlin.notNull import org.mockito.kotlin.refEq import org.mockito.kotlin.verify @@ -40,6 +48,7 @@ import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class WidgetInteractionHandlerTest : SysuiTestCase() { private val activityStarter = mock<ActivityStarter>() + private val kosmos = testKosmos() private val testIntent = PendingIntent.getActivity( @@ -50,30 +59,44 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { ) private val testResponse = RemoteResponse.fromPendingIntent(testIntent) - private val underTest: WidgetInteractionHandler by lazy { - WidgetInteractionHandler(activityStarter) + private lateinit var underTest: WidgetInteractionHandler + + @Before + fun setUp() { + with(kosmos) { + underTest = WidgetInteractionHandler(activityStarter, communalSceneInteractor) + } } @Test fun launchAnimatorIsUsedForWidgetView() { - val parent = FrameLayout(context) - val view = CommunalAppWidgetHostView(context) - parent.addView(view) - val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view) + with(kosmos) { + testScope.runTest { + val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget) + assertFalse(launching!!) - underTest.onInteraction(view, testIntent, testResponse) + val parent = FrameLayout(context) + val view = CommunalAppWidgetHostView(context) + parent.addView(view) + val (fillInIntent, activityOptions) = testResponse.getLaunchOptions(view) - // Verify that we pass in a non-null animation controller - verify(activityStarter) - .startPendingIntentMaybeDismissingKeyguard( - /* intent = */ eq(testIntent), - /* dismissShade = */ eq(false), - /* intentSentUiThreadCallback = */ isNull(), - /* animationController = */ notNull(), - /* fillInIntent = */ refEq(fillInIntent), - /* extraOptions = */ refEq(activityOptions.toBundle()), - /* customMessage */ isNull(), - ) + underTest.onInteraction(view, testIntent, testResponse) + + // Verify that we set the state correctly + assertTrue(launching!!) + // Verify that we pass in a non-null Communal animation controller + verify(activityStarter) + .startPendingIntentMaybeDismissingKeyguard( + /* intent = */ eq(testIntent), + /* dismissShade = */ eq(false), + /* intentSentUiThreadCallback = */ isNull(), + /* animationController = */ any<CommunalTransitionAnimatorController>(), + /* fillInIntent = */ refEq(fillInIntent), + /* extraOptions = */ refEq(activityOptions.toBundle()), + /* customMessage */ isNull(), + ) + } + } } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt index ccd78ee82169..59e8ea63ecee 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt @@ -40,6 +40,7 @@ import com.android.systemui.assist.AssistManager import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.plugins.ActivityStarter.OnDismissAction import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeController import com.android.systemui.shade.data.repository.FakeShadeRepository @@ -136,7 +137,9 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { communalSceneInteractor = communalSceneInteractor, ) `when`(userTracker.userHandle).thenReturn(UserHandle.OWNER) + `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(false)) `when`(communalSceneInteractor.isIdleOnCommunal).thenReturn(MutableStateFlow(false)) + `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(false)) } @Test @@ -335,6 +338,102 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { ) } + @EnableFlags(Flags.FLAG_COMMUNAL_HUB) + @Test + fun startPendingIntentDismissingKeyguard_transitionAnimator_animateCommunal() { + val parent = FrameLayout(context) + val view = + object : View(context), LaunchableView { + override fun setShouldBlockVisibilityChanges(block: Boolean) {} + } + parent.addView(view) + val controller = ActivityTransitionAnimator.Controller.fromView(view) + val pendingIntent = mock(PendingIntent::class.java) + `when`(pendingIntent.isActivity).thenReturn(true) + `when`(keyguardStateController.isShowing).thenReturn(true) + `when`(keyguardStateController.isOccluded).thenReturn(true) + `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true)) + `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(true)) + `when`(activityIntentHelper.wouldPendingLaunchResolverActivity(eq(pendingIntent), anyInt())) + .thenReturn(false) + `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt())) + .thenReturn(false) + + underTest.startPendingIntentDismissingKeyguard( + intent = pendingIntent, + dismissShade = false, + animationController = controller, + showOverLockscreen = true, + skipLockscreenChecks = false + ) + mainExecutor.runAllReady() + + val actionCaptor = argumentCaptor<OnDismissAction>() + verify(statusBarKeyguardViewManager) + .dismissWithAction(actionCaptor.capture(), eq(null), anyBoolean(), eq(null)) + actionCaptor.firstValue.onDismiss() + mainExecutor.runAllReady() + + verify(activityTransitionAnimator) + .startPendingIntentWithAnimation( + nullable(ActivityTransitionAnimator.Controller::class.java), + eq(true), + nullable(String::class.java), + eq(false), + any(), + ) + } + + @DisableFlags(Flags.FLAG_COMMUNAL_HUB) + @Test + fun startPendingIntentDismissingKeyguard_transitionAnimator_doNotAnimateCommunal() { + val parent = FrameLayout(context) + val view = + object : View(context), LaunchableView { + override fun setShouldBlockVisibilityChanges(block: Boolean) {} + } + parent.addView(view) + val controller = ActivityTransitionAnimator.Controller.fromView(view) + val pendingIntent = mock(PendingIntent::class.java) + `when`(pendingIntent.isActivity).thenReturn(true) + `when`(keyguardStateController.isShowing).thenReturn(true) + `when`(keyguardStateController.isOccluded).thenReturn(true) + `when`(communalSceneInteractor.isCommunalVisible).thenReturn(MutableStateFlow(true)) + `when`(communalSceneInteractor.isLaunchingWidget).thenReturn(MutableStateFlow(true)) + `when`(activityIntentHelper.wouldPendingLaunchResolverActivity(eq(pendingIntent), anyInt())) + .thenReturn(false) + `when`(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt())) + .thenReturn(false) + + underTest.startPendingIntentDismissingKeyguard( + intent = pendingIntent, + dismissShade = false, + animationController = controller, + showOverLockscreen = true, + skipLockscreenChecks = false + ) + mainExecutor.runAllReady() + + val actionCaptor = argumentCaptor<OnDismissAction>() + verify(statusBarKeyguardViewManager) + .dismissWithAction(actionCaptor.capture(), eq(null), anyBoolean(), eq(null)) + actionCaptor.firstValue.onDismiss() + mainExecutor.runAllReady() + + val runnableCaptor = argumentCaptor<Runnable>() + verify(statusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(runnableCaptor.capture()) + runnableCaptor.firstValue.run() + + verify(activityTransitionAnimator) + .startPendingIntentWithAnimation( + nullable(ActivityTransitionAnimator.Controller::class.java), + eq(false), + nullable(String::class.java), + eq(false), + any(), + ) + } + @Test fun startActivity_noUserHandleProvided_getUserHandle() { val intent = mock(Intent::class.java) diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index bde6f42b16af..6b58c07b1706 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -105,7 +105,13 @@ constructor( .mapLatest(::determineSceneAfterTransition) .filterNotNull() .onEach { (nextScene, nextTransition) -> - communalSceneInteractor.changeScene(nextScene, nextTransition) + if (!communalSceneInteractor.isLaunchingWidget.value) { + // When launching a widget, we don't want to animate the scene change or the + // Communal Hub will reveal the wallpaper even though it shouldn't. Instead we + // snap to the new scene as part of the launch animation, once the activity + // launch is done, so we don't change scene here. + communalSceneInteractor.changeScene(nextScene, nextTransition) + } } .launchIn(applicationScope) diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index fd540c4b6708..122f964713a9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -48,6 +48,15 @@ constructor( @Application private val applicationScope: CoroutineScope, private val communalSceneRepository: CommunalSceneRepository, ) { + val _isLaunchingWidget = MutableStateFlow(false) + + /** Whether a widget launch is currently in progress. */ + val isLaunchingWidget: StateFlow<Boolean> = _isLaunchingWidget.asStateFlow() + + fun setIsLaunchingWidget(launching: Boolean) { + _isLaunchingWidget.value = launching + } + /** * Asks for an asynchronous scene witch to [newScene], which will use the corresponding * installed transition or the one specified by [transitionKey], if provided. diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt index a88b777be785..4e3d3ff8a265 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt @@ -22,21 +22,25 @@ import android.content.Intent import android.view.View import android.widget.RemoteViews import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.util.InteractionHandlerDelegate import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView import com.android.systemui.plugins.ActivityStarter import javax.inject.Inject -/** - * Handles interactions on smartspace elements on the hub. - */ -class SmartspaceInteractionHandler @Inject constructor( +/** Handles interactions on smartspace elements on the hub. */ +class SmartspaceInteractionHandler +@Inject +constructor( private val activityStarter: ActivityStarter, + communalSceneInteractor: CommunalSceneInteractor, ) : RemoteViews.InteractionHandler { - private val delegate = InteractionHandlerDelegate( - findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView }, - intentStarter = this::startIntent, - ) + private val delegate = + InteractionHandlerDelegate( + communalSceneInteractor, + findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView }, + intentStarter = this::startIntent, + ) override fun onInteraction( view: View, diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt index 40b182dba817..51a5fcd6b873 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt @@ -24,17 +24,17 @@ import android.widget.RemoteViews import androidx.core.util.component1 import androidx.core.util.component2 import com.android.systemui.animation.ActivityTransitionAnimator - +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.communal.widgets.CommunalTransitionAnimatorController /** A delegate that can be used to launch activities from [RemoteViews] */ class InteractionHandlerDelegate( + private val communalSceneInteractor: CommunalSceneInteractor, private val findViewToAnimate: (View) -> Boolean, private val intentStarter: IntentStarter, ) : RemoteViews.InteractionHandler { - /** - * Responsible for starting the pending intent for launching activities. - */ + /** Responsible for starting the pending intent for launching activities. */ fun interface IntentStarter { fun startPendingIntent( intent: PendingIntent, @@ -57,7 +57,10 @@ class InteractionHandlerDelegate( // activities. val hostView = getNearestParent(view) val animationController = - hostView?.let(ActivityTransitionAnimator.Controller::fromView) + hostView?.let(ActivityTransitionAnimator.Controller::fromView)?.let { + communalSceneInteractor.setIsLaunchingWidget(true) + CommunalTransitionAnimatorController(it, communalSceneInteractor) + } val (fillInIntent, activityOptions) = launchOptions intentStarter.startPendingIntent( pendingIntent, @@ -66,7 +69,6 @@ class InteractionHandlerDelegate( animationController ) } - else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt new file mode 100644 index 000000000000..4efaf878f33f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt @@ -0,0 +1,50 @@ +/* + * 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.systemui.communal.widgets + +import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.animation.DelegateTransitionAnimatorController +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.communal.shared.model.CommunalScenes + +/** + * An [ActivityTransitionAnimator.Controller] that takes care of updating the state of the Communal + * Hub at the right time. + */ +class CommunalTransitionAnimatorController( + delegate: ActivityTransitionAnimator.Controller, + private val communalSceneInteractor: CommunalSceneInteractor, +) : DelegateTransitionAnimatorController(delegate) { + override fun onIntentStarted(willAnimate: Boolean) { + if (!willAnimate) { + // Other callbacks won't happen, so reset the state here. + communalSceneInteractor.setIsLaunchingWidget(false) + } + delegate.onIntentStarted(willAnimate) + } + + override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) { + communalSceneInteractor.setIsLaunchingWidget(false) + delegate.onTransitionAnimationCancelled(newKeyguardOccludedState) + } + + override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { + communalSceneInteractor.snapToScene(CommunalScenes.Blank) + communalSceneInteractor.setIsLaunchingWidget(false) + delegate.onTransitionAnimationEnd(isExpandingFullyAbove) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt index 72f9180c51d2..519903e55c50 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt @@ -22,6 +22,7 @@ import android.content.Intent import android.view.View import android.widget.RemoteViews import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.util.InteractionHandlerDelegate import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter @@ -32,10 +33,12 @@ class WidgetInteractionHandler @Inject constructor( private val activityStarter: ActivityStarter, + private val communalSceneInteractor: CommunalSceneInteractor ) : RemoteViews.InteractionHandler { private val delegate = InteractionHandlerDelegate( + communalSceneInteractor, findViewToAnimate = { view -> view is CommunalAppWidgetHostView }, intentStarter = this::startIntent, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index bcb613fe2b8c..de76b10db23b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -276,10 +276,11 @@ constructor( statusBarController } + val isCommunalDismissLaunch = isCommunalWidgetLaunch() && !actuallyShowOverLockscreen // If we animate, don't collapse the shade and defer the keyguard dismiss (in case we // run the animation on the keyguard). The animation will take care of (instantly) // collapsing the shade and hiding the keyguard once it is done. - val collapse = dismissShade && !animate + val collapse = (dismissShade || isCommunalDismissLaunch) && !animate val runnable = Runnable { try { activityTransitionAnimator.startPendingIntentWithAnimation( @@ -338,8 +339,9 @@ constructor( postOnUiThread(delay = 0) { executeRunnableDismissingKeyguard( runnable = runnable, - afterKeyguardGone = willLaunchResolverActivity, dismissShade = collapse, + afterKeyguardGone = willLaunchResolverActivity, + deferred = isCommunalDismissLaunch, willAnimateOnKeyguard = animate, customMessage = customMessage, ) @@ -461,7 +463,9 @@ constructor( override fun onDismiss(): Boolean { if (runnable != null) { if ( - keyguardStateController.isShowing && keyguardStateController.isOccluded + keyguardStateController.isShowing && + keyguardStateController.isOccluded && + !isCommunalWidgetLaunch() ) { statusBarKeyguardViewManagerLazy .get() @@ -473,17 +477,10 @@ constructor( if (dismissShade) { shadeControllerLazy.get().collapseShadeForActivityStart() } - if (communalHub()) { - communalSceneInteractor.changeSceneForActivityStartOnDismissKeyguard() - } return deferred } override fun willRunAnimationOnKeyguard(): Boolean { - if (communalHub() && communalSceneInteractor.isIdleOnCommunal.value) { - // Override to false when launching activity over the hub that requires auth - return false - } return willAnimateOnKeyguard } } @@ -639,7 +636,8 @@ constructor( showOverLockscreen: Boolean, ): Boolean { // TODO(b/294418322): always support launch animations when occluded. - val ignoreOcclusion = showOverLockscreen && mediaLockscreenLaunchAnimation() + val ignoreOcclusion = + (showOverLockscreen && mediaLockscreenLaunchAnimation()) || isCommunalWidgetLaunch() if (keyguardStateController.isOccluded && !ignoreOcclusion) { return false } @@ -659,6 +657,12 @@ constructor( return shouldAnimateLaunch(isActivityIntent, false) } + private fun isCommunalWidgetLaunch(): Boolean { + return communalHub() && + communalSceneInteractor.isCommunalVisible.value && + communalSceneInteractor.isLaunchingWidget.value + } + private fun postOnUiThread(delay: Int = 0, runnable: Runnable) { mainExecutor.executeDelayed(runnable, delay.toLong()) } |