1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.dreams.homecontrols
18 
19 import android.content.Intent
20 import android.os.PowerManager
21 import android.service.controls.ControlsProviderService
22 import android.service.dreams.DreamService
23 import android.window.TaskFragmentInfo
24 import com.android.systemui.controls.settings.ControlsSettingsRepository
25 import com.android.systemui.dagger.qualifiers.Background
26 import com.android.systemui.dreams.DreamLogger
27 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor
28 import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
29 import com.android.systemui.log.LogBuffer
30 import com.android.systemui.log.dagger.DreamLog
31 import com.android.systemui.util.wakelock.WakeLock
32 import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
33 import javax.inject.Inject
34 import kotlin.time.Duration.Companion.milliseconds
35 import kotlin.time.Duration.Companion.seconds
36 import kotlinx.coroutines.CoroutineDispatcher
37 import kotlinx.coroutines.CoroutineScope
38 import kotlinx.coroutines.SupervisorJob
39 import kotlinx.coroutines.cancel
40 import kotlinx.coroutines.delay
41 import kotlinx.coroutines.launch
42 
43 class HomeControlsDreamService
44 @Inject
45 constructor(
46     private val controlsSettingsRepository: ControlsSettingsRepository,
47     private val taskFragmentFactory: TaskFragmentComponent.Factory,
48     private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
49     private val wakeLockBuilder: WakeLock.Builder,
50     private val dreamServiceDelegate: DreamServiceDelegate,
51     @Background private val bgDispatcher: CoroutineDispatcher,
52     @DreamLog logBuffer: LogBuffer
53 ) : DreamService() {
54 
55     private val serviceJob = SupervisorJob()
56     private val serviceScope = CoroutineScope(bgDispatcher + serviceJob)
57     private val logger = DreamLogger(logBuffer, TAG)
58     private lateinit var taskFragmentComponent: TaskFragmentComponent
<lambda>null59     private val wakeLock: WakeLock by lazy {
60         wakeLockBuilder
61             .setMaxTimeout(NO_TIMEOUT)
62             .setTag(TAG)
63             .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
64             .build()
65     }
66 
onAttachedToWindownull67     override fun onAttachedToWindow() {
68         super.onAttachedToWindow()
69         val activity = dreamServiceDelegate.getActivity(this)
70         if (activity == null) {
71             finish()
72             return
73         }
74 
75         // Start monitoring package updates to possibly restart the dream if the home controls
76         // package is updated while we are dreaming.
77         serviceScope.launch { homeControlsComponentInteractor.monitorUpdatesAndRestart() }
78 
79         taskFragmentComponent =
80             taskFragmentFactory
81                 .create(
82                     activity = activity,
83                     onCreateCallback = { launchActivity() },
84                     onInfoChangedCallback = this::onTaskFragmentInfoChanged,
85                     hide = { endDream(false) }
86                 )
87                 .apply { createTaskFragment() }
88 
89         wakeLock.acquire(TAG)
90     }
91 
onTaskFragmentInfoChangednull92     private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
93         if (taskFragmentInfo.isEmpty) {
94             logger.d("Finishing dream due to TaskFragment being empty")
95             endDream(true)
96         }
97     }
98 
endDreamnull99     private fun endDream(handleRedirect: Boolean) {
100         homeControlsComponentInteractor.onDreamEndUnexpectedly()
101         if (handleRedirect && dreamServiceDelegate.redirectWake(this)) {
102             dreamServiceDelegate.wakeUp(this)
103             serviceScope.launch {
104                 delay(ACTIVITY_RESTART_DELAY)
105                 launchActivity()
106             }
107         } else {
108             dreamServiceDelegate.finish(this)
109         }
110     }
111 
launchActivitynull112     private fun launchActivity() {
113         val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
114         val componentName = homeControlsComponentInteractor.panelComponent.value
115         logger.d("Starting embedding $componentName")
116         val intent =
117             Intent().apply {
118                 component = componentName
119                 putExtra(ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, setting)
120                 putExtra(
121                     ControlsProviderService.EXTRA_CONTROLS_SURFACE,
122                     ControlsProviderService.CONTROLS_SURFACE_DREAM
123                 )
124             }
125         taskFragmentComponent.startActivityInTaskFragment(intent)
126     }
127 
onDetachedFromWindownull128     override fun onDetachedFromWindow() {
129         super.onDetachedFromWindow()
130         wakeLock.release(TAG)
131         taskFragmentComponent.destroy()
132         serviceScope.launch {
133             delay(CANCELLATION_DELAY_AFTER_DETACHED)
134             serviceJob.cancel("Dream detached from window")
135         }
136     }
137 
138     private companion object {
139         /**
140          * Defines how long after the dream ends that we should keep monitoring for package updates
141          * to attempt a restart of the dream. This should be larger than
142          * [MAX_UPDATE_CORRELATION_DELAY] as it also includes the time the package update takes to
143          * complete.
144          */
145         val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds
146 
147         /**
148          * Defines the delay after wakeup where we should attempt to restart the embedded activity.
149          * When a wakeup is redirected, the dream service may keep running. In this case, we should
150          * restart the activity if it finished. This delays ensures the activity is only restarted
151          * after the wakeup transition has played.
152          */
153         val ACTIVITY_RESTART_DELAY = 334.milliseconds
154         const val TAG = "HomeControlsDreamService"
155     }
156 }
157