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