diff options
9 files changed, 521 insertions, 10 deletions
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index cdbac338e0be..9ab9ad7c8b6f 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -998,6 +998,16 @@ flag { } flag { + name: "communal_widget_trampoline_fix" + namespace: "systemui" + description: "fixes activity starts caused by non-activity trampolines from widgets." + bug: "350468769" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "app_clips_backlinks" namespace: "systemui" description: "Enables Backlinks improvement feature in App Clips" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt new file mode 100644 index 000000000000..b3ffc7159f7c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt @@ -0,0 +1,220 @@ +/* + * 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.domain.interactor + +import android.app.ActivityManager.RunningTaskInfo +import android.app.usage.UsageEvents +import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.usagestats.data.repository.fakeUsageStatsRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.activityStarter +import com.android.systemui.settings.fakeUserTracker +import com.android.systemui.shared.system.taskStackChangeListeners +import com.android.systemui.testKosmos +import com.android.systemui.util.time.fakeSystemClock +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.currentTime +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.times +import org.mockito.kotlin.verify + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class WidgetTrampolineInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val activityStarter = kosmos.activityStarter + private val usageStatsRepository = kosmos.fakeUsageStatsRepository + private val taskStackChangeListeners = kosmos.taskStackChangeListeners + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val userTracker = kosmos.fakeUserTracker + private val systemClock = kosmos.fakeSystemClock + + private val underTest = kosmos.widgetTrampolineInteractor + + @Before + fun setUp() { + userTracker.set(listOf(MAIN_USER), 0) + systemClock.setCurrentTimeMillis(testScope.currentTime) + } + + @Test + fun testNewTaskStartsWhileOnHub_triggersUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + moveTaskToFront() + + verify(activityStarter).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testNewTaskStartsAfterExitingHub_doesNotTriggerUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + transition(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN) + moveTaskToFront() + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testNewTaskStartsAfterTimeout_doesNotTriggerUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + advanceTime(2.seconds) + moveTaskToFront() + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testActivityResumedWhileOnHub_triggersUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED) + advanceTime(1.seconds) + + verify(activityStarter).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testActivityResumedAfterExitingHub_doesNotTriggerUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + transition(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN) + addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED) + advanceTime(1.seconds) + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testActivityDestroyed_doesNotTriggerUnlock() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + addActivityEvent(UsageEvents.Event.ACTIVITY_DESTROYED) + advanceTime(1.seconds) + + verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + @Test + fun testMultipleActivityEvents_triggersUnlockOnlyOnce() = + testScope.runTest { + transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB) + backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() } + runCurrent() + + addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED) + advanceTime(10.milliseconds) + addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED) + advanceTime(1.seconds) + + verify(activityStarter, times(1)).dismissKeyguardThenExecute(any(), anyOrNull(), any()) + } + + private fun TestScope.advanceTime(duration: Duration) { + systemClock.advanceTime(duration.inWholeMilliseconds) + advanceTimeBy(duration) + } + + private fun TestScope.addActivityEvent(type: Int) { + usageStatsRepository.addEvent( + instanceId = 1, + user = MAIN_USER.userHandle, + packageName = "pkg.test", + timestamp = systemClock.currentTimeMillis(), + type = type, + ) + runCurrent() + } + + private fun TestScope.moveTaskToFront() { + taskStackChangeListeners.listenerImpl.onTaskMovedToFront(mock<RunningTaskInfo>()) + runCurrent() + } + + private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) { + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = from, + to = to, + value = 0.1f, + transitionState = TransitionState.STARTED, + ownerName = "test", + ), + TransitionStep( + from = from, + to = to, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "test", + ), + ), + testScope + ) + runCurrent() + } + + private companion object { + val MAIN_USER: UserInfo = UserInfo(0, "primary", UserInfo.FLAG_MAIN) + } +} 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 023de52b2460..400f736ba882 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 @@ -27,7 +27,9 @@ 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.domain.interactor.widgetTrampolineInteractor import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.ActivityStarter @@ -67,9 +69,11 @@ class WidgetInteractionHandlerTest : SysuiTestCase() { with(kosmos) { underTest = WidgetInteractionHandler( + applicationScope = applicationCoroutineScope, activityStarter = activityStarter, communalSceneInteractor = communalSceneInteractor, logBuffer = logcatLogBuffer(), + widgetTrampolineInteractor = widgetTrampolineInteractor, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt new file mode 100644 index 000000000000..7453368d0ee7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt @@ -0,0 +1,140 @@ +/* + * 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.domain.interactor + +import android.app.ActivityManager +import com.android.systemui.common.usagestats.domain.UsageStatsInteractor +import com.android.systemui.common.usagestats.shared.model.ActivityEventModel +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger +import com.android.systemui.log.dagger.CommunalLog +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shared.system.TaskStackChangeListener +import com.android.systemui.shared.system.TaskStackChangeListeners +import com.android.systemui.util.kotlin.race +import com.android.systemui.util.time.SystemClock +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeout + +/** + * Detects activity starts that occur while the communal hub is showing, within a short delay of a + * widget interaction occurring. Used for detecting non-activity trampolines which otherwise would + * not prompt the user for authentication. + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class WidgetTrampolineInteractor +@Inject +constructor( + private val activityStarter: ActivityStarter, + private val systemClock: SystemClock, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val taskStackChangeListeners: TaskStackChangeListeners, + private val usageStatsInteractor: UsageStatsInteractor, + @CommunalLog logBuffer: LogBuffer, +) { + private companion object { + const val TAG = "WidgetTrampolineInteractor" + } + + private val logger = Logger(logBuffer, TAG) + + /** Waits for a new task to be moved to the foreground. */ + private suspend fun waitForNewForegroundTask() = suspendCancellableCoroutine { cont -> + val listener = + object : TaskStackChangeListener { + override fun onTaskMovedToFront(taskInfo: ActivityManager.RunningTaskInfo) { + if (!cont.isCompleted) { + cont.resume(Unit, null) + } + } + } + taskStackChangeListeners.registerTaskStackListener(listener) + cont.invokeOnCancellation { taskStackChangeListeners.unregisterTaskStackListener(listener) } + } + + /** + * Waits for an activity to enter a [ActivityEventModel.Lifecycle.RESUMED] state by periodically + * polling the system to see if any activities have started. + */ + private suspend fun waitForActivityStartByPolling(startTime: Long): Boolean { + while (true) { + val events = usageStatsInteractor.queryActivityEvents(startTime = startTime) + if (events.any { event -> event.lifecycle == ActivityEventModel.Lifecycle.RESUMED }) { + return true + } else { + // Poll again in the future to check if an activity started. + delay(200.milliseconds) + } + } + } + + /** Waits for a transition away from the hub to occur. */ + private suspend fun waitForTransitionAwayFromHub() { + keyguardTransitionInteractor + .isFinishedIn(Scenes.Communal, KeyguardState.GLANCEABLE_HUB) + .takeWhile { it } + .collect {} + } + + private suspend fun waitForActivityStartWhileOnHub(): Boolean { + val startTime = systemClock.currentTimeMillis() + return try { + return withTimeout(1.seconds) { + race( + { + waitForNewForegroundTask() + true + }, + { waitForActivityStartByPolling(startTime) }, + { + waitForTransitionAwayFromHub() + false + }, + ) + } + } catch (e: TimeoutCancellationException) { + false + } + } + + /** + * Checks if an activity starts while on the glanceable hub and dismisses the keyguard if it + * does. This can detect activities started due to broadcast trampolines from widgets. + */ + suspend fun waitForActivityStartAndDismissKeyguard() { + if (waitForActivityStartWhileOnHub()) { + logger.d("Detected trampoline, requesting unlock") + activityStarter.dismissKeyguardThenExecute( + /* action= */ { false }, + /* cancel= */ null, + /* afterKeyguardGone= */ false + ) + } + } +} 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 c4edcac1a2a1..99e3232a70c2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt @@ -48,7 +48,17 @@ constructor( InteractionHandlerDelegate( communalSceneInteractor, findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView }, - intentStarter = this::startIntent, + intentStarter = + object : InteractionHandlerDelegate.IntentStarter { + override fun startActivity( + intent: PendingIntent, + fillInIntent: Intent, + activityOptions: ActivityOptions, + controller: ActivityTransitionAnimator.Controller? + ): Boolean { + return startIntent(intent, fillInIntent, activityOptions, controller) + } + }, logger = Logger(logBuffer, TAG), ) 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 d2029d50bf63..5e21afacf9f3 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal.util import android.app.ActivityOptions import android.app.PendingIntent import android.content.Intent +import android.util.Pair as UtilPair import android.view.View import android.widget.RemoteViews import androidx.core.util.component1 @@ -36,14 +37,28 @@ class InteractionHandlerDelegate( private val logger: Logger, ) : RemoteViews.InteractionHandler { - /** Responsible for starting the pending intent for launching activities. */ - fun interface IntentStarter { - fun startPendingIntent( + interface IntentStarter { + /** Responsible for starting the pending intent for launching activities. */ + fun startActivity( intent: PendingIntent, fillInIntent: Intent, activityOptions: ActivityOptions, controller: ActivityTransitionAnimator.Controller?, ): Boolean + + /** Responsible for starting the pending intent for non-activity launches. */ + fun startPendingIntent( + view: View, + pendingIntent: PendingIntent, + fillInIntent: Intent, + activityOptions: ActivityOptions, + ): Boolean { + return RemoteViews.startPendingIntent( + view, + pendingIntent, + UtilPair(fillInIntent, activityOptions), + ) + } } override fun onInteraction( @@ -55,7 +70,7 @@ class InteractionHandlerDelegate( str1 = pendingIntent.toLoggingString() str2 = pendingIntent.creatorPackage } - val launchOptions = response.getLaunchOptions(view) + val (fillInIntent, activityOptions) = response.getLaunchOptions(view) return when { pendingIntent.isActivity -> { // Forward the fill-in intent and activity options retrieved from the response @@ -67,15 +82,15 @@ class InteractionHandlerDelegate( communalSceneInteractor.setIsLaunchingWidget(true) CommunalTransitionAnimatorController(it, communalSceneInteractor) } - val (fillInIntent, activityOptions) = launchOptions - intentStarter.startPendingIntent( + intentStarter.startActivity( pendingIntent, fillInIntent, activityOptions, animationController ) } - else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions) + else -> + intentStarter.startPendingIntent(view, pendingIntent, fillInIntent, activityOptions) } } 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 0eeb506ccc09..121b4a304c3a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt @@ -21,22 +21,30 @@ import android.app.PendingIntent import android.content.Intent import android.view.View import android.widget.RemoteViews +import com.android.app.tracing.coroutines.launch +import com.android.systemui.Flags.communalWidgetTrampolineFix import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.communal.domain.interactor.WidgetTrampolineInteractor import com.android.systemui.communal.util.InteractionHandlerDelegate import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog import com.android.systemui.plugins.ActivityStarter import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job @SysUISingleton class WidgetInteractionHandler @Inject constructor( + @Application applicationScope: CoroutineScope, private val activityStarter: ActivityStarter, communalSceneInteractor: CommunalSceneInteractor, + private val widgetTrampolineInteractor: WidgetTrampolineInteractor, @CommunalLog val logBuffer: LogBuffer, ) : RemoteViews.InteractionHandler { @@ -48,7 +56,52 @@ constructor( InteractionHandlerDelegate( communalSceneInteractor, findViewToAnimate = { view -> view is CommunalAppWidgetHostView }, - intentStarter = this::startIntent, + intentStarter = + object : InteractionHandlerDelegate.IntentStarter { + private var job: Job? = null + + override fun startActivity( + intent: PendingIntent, + fillInIntent: Intent, + activityOptions: ActivityOptions, + controller: ActivityTransitionAnimator.Controller? + ): Boolean { + cancelTrampolineMonitoring() + return startActivityIntent( + intent, + fillInIntent, + activityOptions, + controller + ) + } + + override fun startPendingIntent( + view: View, + pendingIntent: PendingIntent, + fillInIntent: Intent, + activityOptions: ActivityOptions + ): Boolean { + cancelTrampolineMonitoring() + if (communalWidgetTrampolineFix()) { + job = + applicationScope.launch("$TAG#monitorForActivityStart") { + widgetTrampolineInteractor + .waitForActivityStartAndDismissKeyguard() + } + } + return super.startPendingIntent( + view, + pendingIntent, + fillInIntent, + activityOptions + ) + } + + private fun cancelTrampolineMonitoring() { + job?.cancel() + job = null + } + }, logger = Logger(logBuffer, TAG), ) @@ -58,7 +111,7 @@ constructor( response: RemoteViews.RemoteResponse ): Boolean = delegate.onInteraction(view, pendingIntent, response) - private fun startIntent( + private fun startActivityIntent( pendingIntent: PendingIntent, fillInIntent: Intent, extraOptions: ActivityOptions, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt new file mode 100644 index 000000000000..81242244b7a6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt @@ -0,0 +1,37 @@ +/* + * 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.domain.interactor + +import com.android.systemui.common.usagestats.domain.interactor.usageStatsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.plugins.activityStarter +import com.android.systemui.shared.system.taskStackChangeListeners +import com.android.systemui.util.time.fakeSystemClock + +val Kosmos.widgetTrampolineInteractor: WidgetTrampolineInteractor by + Kosmos.Fixture { + WidgetTrampolineInteractor( + activityStarter = activityStarter, + systemClock = fakeSystemClock, + keyguardTransitionInteractor = keyguardTransitionInteractor, + taskStackChangeListeners = taskStackChangeListeners, + usageStatsInteractor = usageStatsInteractor, + logBuffer = logcatLogBuffer("WidgetTrampolineInteractor"), + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt new file mode 100644 index 000000000000..67f611a040cf --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.shared.system + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.taskStackChangeListeners: TaskStackChangeListeners by + Kosmos.Fixture { TaskStackChangeListeners.getTestInstance() } |