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