• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.SuppressLint
20 import android.content.Intent
21 import android.os.PowerManager
22 import android.service.controls.ControlsProviderService
23 import android.service.dreams.DreamService
24 import android.window.TaskFragmentInfo
25 import androidx.lifecycle.Lifecycle
26 import androidx.lifecycle.LifecycleOwner
27 import androidx.lifecycle.ServiceLifecycleDispatcher
28 import androidx.lifecycle.lifecycleScope
29 import com.android.systemui.dreams.DreamLogger
30 import com.android.systemui.dreams.homecontrols.service.TaskFragmentComponent
31 import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsDataSource
32 import com.android.systemui.log.LogBuffer
33 import com.android.systemui.log.dagger.DreamLog
34 import com.android.systemui.util.time.SystemClock
35 import com.android.systemui.util.wakelock.WakeLock
36 import dagger.assisted.Assisted
37 import dagger.assisted.AssistedFactory
38 import dagger.assisted.AssistedInject
39 import javax.inject.Inject
40 import kotlin.time.Duration.Companion.milliseconds
41 import kotlinx.coroutines.delay
42 import kotlinx.coroutines.flow.first
43 import kotlinx.coroutines.launch
44 
45 /**
46  * [DreamService] which embeds the user's chosen home controls app to allow it to display as a
47  * screensaver. This service will run in the foreground user context.
48  */
49 class HomeControlsDreamService
50 @Inject
51 constructor(private val factory: HomeControlsDreamServiceImpl.Factory) :
52     DreamService(), LifecycleOwner {
53 
54     private val dispatcher = ServiceLifecycleDispatcher(this)
55     override val lifecycle: Lifecycle
56         get() = dispatcher.lifecycle
57 
<lambda>null58     private val impl: HomeControlsDreamServiceImpl by lazy { factory.create(this, this) }
59 
onCreatenull60     override fun onCreate() {
61         dispatcher.onServicePreSuperOnCreate()
62         super.onCreate()
63     }
64 
onDreamingStartednull65     override fun onDreamingStarted() {
66         dispatcher.onServicePreSuperOnStart()
67         super.onDreamingStarted()
68     }
69 
onAttachedToWindownull70     override fun onAttachedToWindow() {
71         super.onAttachedToWindow()
72         impl.onAttachedToWindow()
73     }
74 
onDetachedFromWindownull75     override fun onDetachedFromWindow() {
76         dispatcher.onServicePreSuperOnDestroy()
77         super.onDetachedFromWindow()
78         impl.onDetachedFromWindow()
79     }
80 }
81 
82 /**
83  * Implementation of the home controls dream service, which allows for injecting a [DreamService]
84  * and [LifecycleOwner] for testing.
85  */
86 class HomeControlsDreamServiceImpl
87 @AssistedInject
88 constructor(
89     private val taskFragmentFactory: TaskFragmentComponent.Factory,
90     private val wakeLockBuilder: WakeLock.Builder,
91     private val powerManager: PowerManager,
92     private val systemClock: SystemClock,
93     private val dataSource: HomeControlsDataSource,
94     @DreamLog logBuffer: LogBuffer,
95     @Assisted private val service: DreamService,
96     @Assisted lifecycleOwner: LifecycleOwner,
97 ) : LifecycleOwner by lifecycleOwner {
98 
99     private val logger = DreamLogger(logBuffer, TAG)
100     private lateinit var taskFragmentComponent: TaskFragmentComponent
<lambda>null101     private val wakeLock: WakeLock by lazy {
102         wakeLockBuilder
103             .setMaxTimeout(WakeLock.Builder.NO_TIMEOUT)
104             .setTag(TAG)
105             .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
106             .build()
107     }
108 
onAttachedToWindownull109     fun onAttachedToWindow() {
110         val activity = service.activity
111         if (activity == null) {
112             service.finish()
113             return
114         }
115         taskFragmentComponent =
116             taskFragmentFactory
117                 .create(
118                     activity = activity,
119                     onCreateCallback = { launchActivity() },
120                     onInfoChangedCallback = this::onTaskFragmentInfoChanged,
121                     hide = { endDream(false) },
122                 )
123                 .apply { createTaskFragment() }
124 
125         wakeLock.acquire(TAG)
126     }
127 
onTaskFragmentInfoChangednull128     private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
129         if (taskFragmentInfo.isEmpty) {
130             logger.d("Finishing dream due to TaskFragment being empty")
131             endDream(true)
132         }
133     }
134 
endDreamnull135     private fun endDream(handleRedirect: Boolean) {
136         pokeUserActivity()
137         if (handleRedirect && service.redirectWake) {
138             service.wakeUp()
139             lifecycleScope.launch {
140                 delay(ACTIVITY_RESTART_DELAY)
141                 launchActivity()
142             }
143         } else {
144             service.finish()
145         }
146     }
147 
launchActivitynull148     private fun launchActivity() {
149         lifecycleScope.launch {
150             val (componentName, setting) = dataSource.componentInfo.first()
151             logger.d("Starting embedding $componentName")
152             val intent =
153                 Intent().apply {
154                     component = componentName
155                     putExtra(
156                         ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
157                         setting,
158                     )
159                     putExtra(
160                         ControlsProviderService.EXTRA_CONTROLS_SURFACE,
161                         ControlsProviderService.CONTROLS_SURFACE_DREAM,
162                     )
163                 }
164             taskFragmentComponent.startActivityInTaskFragment(intent)
165         }
166     }
167 
onDetachedFromWindownull168     fun onDetachedFromWindow() {
169         wakeLock.release(TAG)
170         taskFragmentComponent.destroy()
171     }
172 
173     @SuppressLint("MissingPermission")
pokeUserActivitynull174     private fun pokeUserActivity() {
175         powerManager.userActivity(
176             systemClock.uptimeMillis(),
177             PowerManager.USER_ACTIVITY_EVENT_OTHER,
178             PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
179         )
180     }
181 
182     @AssistedFactory
183     interface Factory {
createnull184         fun create(
185             service: DreamService,
186             lifecycleOwner: LifecycleOwner,
187         ): HomeControlsDreamServiceImpl
188     }
189 
190     companion object {
191         /**
192          * Defines the delay after wakeup where we should attempt to restart the embedded activity.
193          * When a wakeup is redirected, the dream service may keep running. In this case, we should
194          * restart the activity if it finished. This delays ensures the activity is only restarted
195          * after the wakeup transition has played.
196          */
197         val ACTIVITY_RESTART_DELAY = 334.milliseconds
198         private const val TAG = "HomeControlsDreamServiceImpl"
199     }
200 }
201