Update HomeControlsDreamService to support HSUM

The service will always run in the foreground user, and will no longer
depend on the interactor directly. Instead, it will use a data source -
which will be a remote data source when the flag is enabled.

Bug: 370691405
Test: atest HomeControlsDreamServiceTest
Flag: com.android.systemui.home_controls_dream_hsum
Change-Id: I263379251895e8dacbd668dc4c51d445c9514620
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
index 9300db9..4317b9f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -16,25 +16,37 @@
 package com.android.systemui.dreams.homecontrols
 
 import android.app.Activity
+import android.content.ComponentName
 import android.content.Intent
+import android.os.powerManager
 import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL
 import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_DREAM
 import android.service.controls.ControlsProviderService.EXTRA_CONTROLS_SURFACE
+import android.service.dreams.DreamService
 import android.window.TaskFragmentInfo
+import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.controls.settings.FakeControlsSettingsRepository
+import com.android.systemui.dreams.homecontrols.service.TaskFragmentComponent
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.dreams.homecontrols.shared.model.fakeHomeControlsDataSource
+import com.android.systemui.dreams.homecontrols.shared.model.homeControlsDataSource
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
 import com.android.systemui.util.wakelock.WakeLockFake
 import com.google.common.truth.Truth.assertThat
-import java.util.Optional
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -47,7 +59,6 @@
 import org.mockito.kotlin.never
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -62,13 +73,18 @@
         WakeLockFake.Builder(context).apply { setWakeLock(fakeWakeLock) }
     }
 
+    private val lifecycleOwner = TestLifecycleOwner(coroutineDispatcher = kosmos.testDispatcher)
+
     private val taskFragmentComponent = mock<TaskFragmentComponent>()
     private val activity = mock<Activity>()
     private val onCreateCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>()
     private val onInfoChangedCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>()
     private val hideCallback = argumentCaptor<() -> Unit>()
-    private val dreamServiceDelegate =
-        mock<DreamServiceDelegate> { on { getActivity(any()) } doReturn activity }
+    private var dreamService =
+        mock<DreamService> {
+            on { activity } doReturn activity
+            on { redirectWake } doReturn false
+        }
 
     private val taskFragmentComponentFactory =
         mock<TaskFragmentComponent.Factory> {
@@ -82,12 +98,32 @@
             } doReturn taskFragmentComponent
         }
 
-    private val underTest: HomeControlsDreamService by lazy { buildService() }
+    private val underTest: HomeControlsDreamServiceImpl by lazy {
+        with(kosmos) {
+            HomeControlsDreamServiceImpl(
+                taskFragmentFactory = taskFragmentComponentFactory,
+                wakeLockBuilder = fakeWakeLockBuilder,
+                powerManager = powerManager,
+                systemClock = fakeSystemClock,
+                dataSource = homeControlsDataSource,
+                logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest"),
+                service = dreamService,
+                lifecycleOwner = lifecycleOwner,
+            )
+        }
+    }
 
     @Before
     fun setup() {
-        whenever(kosmos.controlsComponent.getControlsListingController())
-            .thenReturn(Optional.of(kosmos.controlsListingController))
+        Dispatchers.setMain(kosmos.testDispatcher)
+        kosmos.fakeHomeControlsDataSource.setComponentInfo(
+            HomeControlsComponentInfo(PANEL_COMPONENT, true)
+        )
+    }
+
+    @After
+    fun tearDown() {
+        Dispatchers.resetMain()
     }
 
     @Test
@@ -108,13 +144,10 @@
     @Test
     fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() =
         testScope.runTest {
-            val serviceWithNullActivity =
-                buildService(
-                    mock<DreamServiceDelegate> { on { getActivity(underTest) } doReturn null }
-                )
-
-            serviceWithNullActivity.onAttachedToWindow()
+            dreamService = mock<DreamService> { on { activity } doReturn null }
+            underTest.onAttachedToWindow()
             verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
+            verify(dreamService).finish()
         }
 
     @Test
@@ -137,9 +170,9 @@
     @Test
     fun testFinishesDreamWithoutRestartingActivityWhenNotRedirectingWakes() =
         testScope.runTest {
-            whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(false)
             underTest.onAttachedToWindow()
             onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>())
+            runCurrent()
             verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
 
             // Task fragment becomes empty
@@ -149,16 +182,21 @@
             advanceUntilIdle()
             // Dream is finished and activity is not restarted
             verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
-            verify(dreamServiceDelegate, never()).wakeUp(any())
-            verify(dreamServiceDelegate).finish(any())
+            verify(dreamService, never()).wakeUp()
+            verify(dreamService).finish()
         }
 
     @Test
     fun testRestartsActivityWhenRedirectingWakes() =
         testScope.runTest {
-            whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(true)
+            dreamService =
+                mock<DreamService> {
+                    on { activity } doReturn activity
+                    on { redirectWake } doReturn true
+                }
             underTest.onAttachedToWindow()
             onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>())
+            runCurrent()
             verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
 
             // Task fragment becomes empty
@@ -166,30 +204,20 @@
                 mock<TaskFragmentInfo> { on { isEmpty } doReturn true }
             )
             advanceUntilIdle()
+
             // Activity is restarted instead of finishing the dream.
             verify(taskFragmentComponent, times(2)).startActivityInTaskFragment(intentMatcher())
-            verify(dreamServiceDelegate).wakeUp(any())
-            verify(dreamServiceDelegate, never()).finish(any())
+            verify(dreamService).wakeUp()
+            verify(dreamService, never()).finish()
         }
 
     private fun intentMatcher() =
         argThat<Intent> {
             getIntExtra(EXTRA_CONTROLS_SURFACE, CONTROLS_SURFACE_ACTIVITY_PANEL) ==
-                CONTROLS_SURFACE_DREAM
+                CONTROLS_SURFACE_DREAM && component == PANEL_COMPONENT
         }
 
-    private fun buildService(
-        activityProvider: DreamServiceDelegate = dreamServiceDelegate
-    ): HomeControlsDreamService =
-        with(kosmos) {
-            return HomeControlsDreamService(
-                controlsSettingsRepository = FakeControlsSettingsRepository(),
-                taskFragmentFactory = taskFragmentComponentFactory,
-                homeControlsComponentInteractor = homeControlsComponentInteractor,
-                wakeLockBuilder = fakeWakeLockBuilder,
-                dreamServiceDelegate = activityProvider,
-                bgDispatcher = testDispatcher,
-                logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest")
-            )
-        }
+    private companion object {
+        val PANEL_COMPONENT = ComponentName("test.pkg", "test.panel")
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index a45ad15..3171bbc 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -33,9 +33,10 @@
 import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dreams.SystemDialogsCloser;
 import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
-import com.android.systemui.dreams.homecontrols.DreamServiceDelegate;
-import com.android.systemui.dreams.homecontrols.DreamServiceDelegateImpl;
 import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
+import com.android.systemui.dreams.homecontrols.dagger.HomeControlsDataSourceModule;
+import com.android.systemui.dreams.homecontrols.dagger.HomeControlsRemoteServiceComponent;
+import com.android.systemui.dreams.homecontrols.system.HomeControlsRemoteService;
 import com.android.systemui.qs.QsEventLogger;
 import com.android.systemui.qs.pipeline.shared.TileSpec;
 import com.android.systemui.qs.shared.model.TileCategory;
@@ -61,13 +62,15 @@
  * Dagger Module providing Dream-related functionality.
  */
 @Module(includes = {
-            RegisteredComplicationsModule.class,
-            LowLightDreamModule.class,
-            ScrimModule.class
-        },
+        RegisteredComplicationsModule.class,
+        LowLightDreamModule.class,
+        ScrimModule.class,
+        HomeControlsDataSourceModule.class,
+},
         subcomponents = {
-            ComplicationComponent.class,
-            DreamOverlayComponent.class,
+                ComplicationComponent.class,
+                DreamOverlayComponent.class,
+                HomeControlsRemoteServiceComponent.class,
         })
 public interface DreamModule {
     String DREAM_ONLY_ENABLED_FOR_DOCK_USER = "dream_only_enabled_for_dock_user";
@@ -113,6 +116,15 @@
             HomeControlsDreamService service);
 
     /**
+     * Provides Home Controls Remote Service
+     */
+    @Binds
+    @IntoMap
+    @ClassKey(HomeControlsRemoteService.class)
+    Service bindHomeControlsRemoteService(
+            HomeControlsRemoteService service);
+
+    /**
      * Provides a touch inset manager for dreams.
      */
     @Provides
@@ -202,10 +214,4 @@
                 QSTilePolicy.NoRestrictions.INSTANCE
                 );
     }
-
-
-    /** Provides delegate to allow for testing of dream service */
-    @Binds
-    DreamServiceDelegate bindDreamDelegate(DreamServiceDelegateImpl impl);
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt
deleted file mode 100644
index 2cfb02e..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.dreams.homecontrols
-
-import android.app.Activity
-import android.service.dreams.DreamService
-
-/** Provides abstraction for [DreamService] methods, so they can be mocked in tests. */
-interface DreamServiceDelegate {
-    /** Wrapper for [DreamService.getActivity] which can be mocked in tests. */
-    fun getActivity(dreamService: DreamService): Activity?
-
-    /** Wrapper for [DreamService.wakeUp] which can be mocked in tests. */
-    fun wakeUp(dreamService: DreamService)
-
-    /** Wrapper for [DreamService.finish] which can be mocked in tests. */
-    fun finish(dreamService: DreamService)
-
-    /** Wrapper for [DreamService.getRedirectWake] which can be mocked in tests. */
-    fun redirectWake(dreamService: DreamService): Boolean
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt
deleted file mode 100644
index 7dc5434..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.dreams.homecontrols
-
-import android.app.Activity
-import android.service.dreams.DreamService
-import javax.inject.Inject
-
-class DreamServiceDelegateImpl @Inject constructor() : DreamServiceDelegate {
-    override fun getActivity(dreamService: DreamService): Activity {
-        return dreamService.activity
-    }
-
-    override fun finish(dreamService: DreamService) {
-        dreamService.finish()
-    }
-
-    override fun wakeUp(dreamService: DreamService) {
-        dreamService.wakeUp()
-    }
-
-    override fun redirectWake(dreamService: DreamService): Boolean {
-        return dreamService.redirectWake
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index 6b14ebf..9eec1b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -16,75 +16,109 @@
 
 package com.android.systemui.dreams.homecontrols
 
+import android.annotation.SuppressLint
 import android.content.Intent
 import android.os.PowerManager
 import android.service.controls.ControlsProviderService
 import android.service.dreams.DreamService
 import android.window.TaskFragmentInfo
-import com.android.systemui.controls.settings.ControlsSettingsRepository
-import com.android.systemui.coroutines.newTracingContext
-import com.android.systemui.dagger.qualifiers.Background
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ServiceLifecycleDispatcher
+import androidx.lifecycle.lifecycleScope
 import com.android.systemui.dreams.DreamLogger
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
-import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
+import com.android.systemui.dreams.homecontrols.service.TaskFragmentComponent
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsDataSource
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.dagger.DreamLog
+import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.wakelock.WakeLock
-import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.delay
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
 
+/**
+ * [DreamService] which embeds the user's chosen home controls app to allow it to display as a
+ * screensaver. This service will run in the foreground user context.
+ */
 class HomeControlsDreamService
 @Inject
-constructor(
-    private val controlsSettingsRepository: ControlsSettingsRepository,
-    private val taskFragmentFactory: TaskFragmentComponent.Factory,
-    private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
-    private val wakeLockBuilder: WakeLock.Builder,
-    private val dreamServiceDelegate: DreamServiceDelegate,
-    @Background private val bgDispatcher: CoroutineDispatcher,
-    @DreamLog logBuffer: LogBuffer
-) : DreamService() {
+constructor(private val factory: HomeControlsDreamServiceImpl.Factory) :
+    DreamService(), LifecycleOwner {
 
-    private val serviceJob = SupervisorJob()
-    private val serviceScope =
-        CoroutineScope(bgDispatcher + serviceJob + newTracingContext("HomeControlsDreamService"))
+    private val dispatcher = ServiceLifecycleDispatcher(this)
+    override val lifecycle: Lifecycle
+        get() = dispatcher.lifecycle
+
+    private val impl: HomeControlsDreamServiceImpl by lazy { factory.create(this, this) }
+
+    override fun onCreate() {
+        dispatcher.onServicePreSuperOnCreate()
+        super.onCreate()
+    }
+
+    override fun onDreamingStarted() {
+        dispatcher.onServicePreSuperOnStart()
+        super.onDreamingStarted()
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        impl.onAttachedToWindow()
+    }
+
+    override fun onDetachedFromWindow() {
+        dispatcher.onServicePreSuperOnDestroy()
+        super.onDetachedFromWindow()
+        impl.onDetachedFromWindow()
+    }
+}
+
+/**
+ * Implementation of the home controls dream service, which allows for injecting a [DreamService]
+ * and [LifecycleOwner] for testing.
+ */
+class HomeControlsDreamServiceImpl
+@AssistedInject
+constructor(
+    private val taskFragmentFactory: TaskFragmentComponent.Factory,
+    private val wakeLockBuilder: WakeLock.Builder,
+    private val powerManager: PowerManager,
+    private val systemClock: SystemClock,
+    private val dataSource: HomeControlsDataSource,
+    @DreamLog logBuffer: LogBuffer,
+    @Assisted private val service: DreamService,
+    @Assisted lifecycleOwner: LifecycleOwner,
+) : LifecycleOwner by lifecycleOwner {
+
     private val logger = DreamLogger(logBuffer, TAG)
     private lateinit var taskFragmentComponent: TaskFragmentComponent
     private val wakeLock: WakeLock by lazy {
         wakeLockBuilder
-            .setMaxTimeout(NO_TIMEOUT)
+            .setMaxTimeout(WakeLock.Builder.NO_TIMEOUT)
             .setTag(TAG)
             .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
             .build()
     }
 
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        val activity = dreamServiceDelegate.getActivity(this)
+    fun onAttachedToWindow() {
+        val activity = service.activity
         if (activity == null) {
-            finish()
+            service.finish()
             return
         }
-
-        // Start monitoring package updates to possibly restart the dream if the home controls
-        // package is updated while we are dreaming.
-        serviceScope.launch { homeControlsComponentInteractor.monitorUpdatesAndRestart() }
-
         taskFragmentComponent =
             taskFragmentFactory
                 .create(
                     activity = activity,
                     onCreateCallback = { launchActivity() },
                     onInfoChangedCallback = this::onTaskFragmentInfoChanged,
-                    hide = { endDream(false) }
+                    hide = { endDream(false) },
                 )
                 .apply { createTaskFragment() }
 
@@ -99,53 +133,61 @@
     }
 
     private fun endDream(handleRedirect: Boolean) {
-        homeControlsComponentInteractor.onDreamEndUnexpectedly()
-        if (handleRedirect && dreamServiceDelegate.redirectWake(this)) {
-            dreamServiceDelegate.wakeUp(this)
-            serviceScope.launch {
+        pokeUserActivity()
+        if (handleRedirect && service.redirectWake) {
+            service.wakeUp()
+            lifecycleScope.launch {
                 delay(ACTIVITY_RESTART_DELAY)
                 launchActivity()
             }
         } else {
-            dreamServiceDelegate.finish(this)
+            service.finish()
         }
     }
 
     private fun launchActivity() {
-        val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
-        val componentName = homeControlsComponentInteractor.panelComponent.value
-        logger.d("Starting embedding $componentName")
-        val intent =
-            Intent().apply {
-                component = componentName
-                putExtra(ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, setting)
-                putExtra(
-                    ControlsProviderService.EXTRA_CONTROLS_SURFACE,
-                    ControlsProviderService.CONTROLS_SURFACE_DREAM
-                )
-            }
-        taskFragmentComponent.startActivityInTaskFragment(intent)
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        wakeLock.release(TAG)
-        taskFragmentComponent.destroy()
-        serviceScope.launch {
-            delay(CANCELLATION_DELAY_AFTER_DETACHED)
-            serviceJob.cancel("Dream detached from window")
+        lifecycleScope.launch {
+            val (componentName, setting) = dataSource.componentInfo.first()
+            logger.d("Starting embedding $componentName")
+            val intent =
+                Intent().apply {
+                    component = componentName
+                    putExtra(
+                        ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+                        setting,
+                    )
+                    putExtra(
+                        ControlsProviderService.EXTRA_CONTROLS_SURFACE,
+                        ControlsProviderService.CONTROLS_SURFACE_DREAM,
+                    )
+                }
+            taskFragmentComponent.startActivityInTaskFragment(intent)
         }
     }
 
-    private companion object {
-        /**
-         * Defines how long after the dream ends that we should keep monitoring for package updates
-         * to attempt a restart of the dream. This should be larger than
-         * [MAX_UPDATE_CORRELATION_DELAY] as it also includes the time the package update takes to
-         * complete.
-         */
-        val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds
+    fun onDetachedFromWindow() {
+        wakeLock.release(TAG)
+        taskFragmentComponent.destroy()
+    }
 
+    @SuppressLint("MissingPermission")
+    private fun pokeUserActivity() {
+        powerManager.userActivity(
+            systemClock.uptimeMillis(),
+            PowerManager.USER_ACTIVITY_EVENT_OTHER,
+            PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
+        )
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            service: DreamService,
+            lifecycleOwner: LifecycleOwner,
+        ): HomeControlsDreamServiceImpl
+    }
+
+    companion object {
         /**
          * Defines the delay after wakeup where we should attempt to restart the embedded activity.
          * When a wakeup is redirected, the dream service may keep running. In this case, we should
@@ -153,6 +195,6 @@
          * after the wakeup transition has played.
          */
         val ACTIVITY_RESTART_DELAY = 334.milliseconds
-        const val TAG = "HomeControlsDreamService"
+        private const val TAG = "HomeControlsDreamServiceImpl"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
rename to packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt
index d547de2..67de30c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/TaskFragmentComponent.kt
@@ -14,29 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.homecontrols
+package com.android.systemui.dreams.homecontrols.service
 
 import android.app.Activity
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration
 import android.content.Intent
 import android.graphics.Rect
 import android.os.Binder
 import android.window.TaskFragmentCreationParams
 import android.window.TaskFragmentInfo
 import android.window.TaskFragmentOperation
-import android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT
-import android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK
 import android.window.TaskFragmentOrganizer
-import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE
-import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE
-import android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN
 import android.window.TaskFragmentTransaction
-import android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK
-import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED
-import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR
-import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED
-import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED
-import android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED
 import android.window.WindowContainerTransaction
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -65,7 +54,7 @@
             activity: Activity,
             @Assisted("onCreateCallback") onCreateCallback: FragmentInfoCallback,
             @Assisted("onInfoChangedCallback") onInfoChangedCallback: FragmentInfoCallback,
-            hide: () -> Unit
+            hide: () -> Unit,
         ): TaskFragmentComponent
     }
 
@@ -90,26 +79,28 @@
             change.taskFragmentInfo?.let { taskFragmentInfo ->
                 if (taskFragmentInfo.fragmentToken == fragmentToken) {
                     when (change.type) {
-                        TYPE_TASK_FRAGMENT_APPEARED -> {
+                        TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED -> {
                             resultT.addTaskFragmentOperation(
                                 fragmentToken,
-                                TaskFragmentOperation.Builder(OP_TYPE_REORDER_TO_TOP_OF_TASK)
-                                    .build()
+                                TaskFragmentOperation.Builder(
+                                        TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK
+                                    )
+                                    .build(),
                             )
 
                             onCreateCallback(taskFragmentInfo)
                         }
-                        TYPE_TASK_FRAGMENT_INFO_CHANGED -> {
+                        TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED -> {
                             onInfoChangedCallback(taskFragmentInfo)
                         }
-                        TYPE_TASK_FRAGMENT_VANISHED -> {
+                        TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED -> {
                             hide()
                         }
-                        TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED -> {}
-                        TYPE_TASK_FRAGMENT_ERROR -> {
+                        TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED -> {}
+                        TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR -> {
                             hide()
                         }
-                        TYPE_ACTIVITY_REPARENTED_TO_TASK -> {}
+                        TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK -> {}
                         else ->
                             throw IllegalArgumentException(
                                 "Unknown TaskFragmentEvent=" + change.type
@@ -121,8 +112,8 @@
         organizer.onTransactionHandled(
             transaction.transactionToken,
             resultT,
-            TASK_FRAGMENT_TRANSIT_CHANGE,
-            false
+            TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+            false,
         )
     }
 
@@ -132,15 +123,15 @@
             TaskFragmentCreationParams.Builder(
                     organizer.organizerToken,
                     fragmentToken,
-                    activity.activityToken!!
+                    activity.activityToken!!,
                 )
                 .setInitialRelativeBounds(Rect())
-                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
                 .build()
         organizer.applyTransaction(
             WindowContainerTransaction().createTaskFragment(fragmentOptions),
-            TASK_FRAGMENT_TRANSIT_CHANGE,
-            false
+            TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+            false,
         )
     }
 
@@ -151,8 +142,8 @@
     fun startActivityInTaskFragment(intent: Intent) {
         organizer.applyTransaction(
             WindowContainerTransaction().startActivity(intent),
-            TASK_FRAGMENT_TRANSIT_OPEN,
-            false
+            TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN,
+            false,
         )
     }
 
@@ -162,10 +153,13 @@
             WindowContainerTransaction()
                 .addTaskFragmentOperation(
                     fragmentToken,
-                    TaskFragmentOperation.Builder(OP_TYPE_DELETE_TASK_FRAGMENT).build()
+                    TaskFragmentOperation.Builder(
+                            TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT
+                        )
+                        .build(),
                 ),
-            TASK_FRAGMENT_TRANSIT_CLOSE,
-            false
+            TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE,
+            false,
         )
         organizer.unregisterOrganizer()
     }