1 /* 2 * Copyright (C) 2023 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 18 package com.android.systemui.keyguard.domain.interactor 19 20 import android.content.Context 21 import androidx.annotation.DimenRes 22 import com.android.systemui.R 23 import com.android.systemui.common.ui.data.repository.ConfigurationRepository 24 import com.android.systemui.dagger.SysUISingleton 25 import com.android.systemui.dagger.qualifiers.Application 26 import com.android.systemui.doze.util.BurnInHelperWrapper 27 import javax.inject.Inject 28 import kotlinx.coroutines.CoroutineScope 29 import kotlinx.coroutines.ExperimentalCoroutinesApi 30 import kotlinx.coroutines.flow.SharingStarted 31 import kotlinx.coroutines.flow.StateFlow 32 import kotlinx.coroutines.flow.flatMapLatest 33 import kotlinx.coroutines.flow.mapLatest 34 import kotlinx.coroutines.flow.stateIn 35 36 /** Encapsulates business-logic related to Ambient Display burn-in offsets. */ 37 @ExperimentalCoroutinesApi 38 @SysUISingleton 39 class BurnInInteractor 40 @Inject 41 constructor( 42 private val context: Context, 43 private val burnInHelperWrapper: BurnInHelperWrapper, 44 @Application private val scope: CoroutineScope, 45 private val configurationRepository: ConfigurationRepository, 46 private val keyguardInteractor: KeyguardInteractor, 47 ) { 48 val udfpsBurnInXOffset: StateFlow<Int> = 49 burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true) 50 val udfpsBurnInYOffset: StateFlow<Int> = 51 burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false) 52 val udfpsBurnInProgress: StateFlow<Float> = 53 keyguardInteractor.dozeTimeTick 54 .mapLatest { burnInHelperWrapper.burnInProgressOffset() } 55 .stateIn(scope, SharingStarted.Lazily, burnInHelperWrapper.burnInProgressOffset()) 56 57 /** 58 * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the 59 * max burn-in offset on any configuration changes. If the max burn-in offset is specified in 60 * pixels, use [burnInOffsetDefinedInPixels]. 61 */ 62 private fun burnInOffset( 63 @DimenRes maxBurnInOffsetResourceId: Int, 64 isXAxis: Boolean, 65 ): StateFlow<Int> { 66 return configurationRepository.onAnyConfigurationChange 67 .flatMapLatest { 68 val maxBurnInOffsetPixels = 69 context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId) 70 keyguardInteractor.dozeTimeTick.mapLatest { 71 calculateOffset(maxBurnInOffsetPixels, isXAxis) 72 } 73 } 74 .stateIn( 75 scope, 76 SharingStarted.Lazily, 77 calculateOffset( 78 context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId), 79 isXAxis, 80 ) 81 ) 82 } 83 84 /** 85 * Use for max burn-in offBurn-in offsets that ARE specified in pixels. This flow will apply the 86 * a scale for any resolution changes. If the max burn-in offset is specified in dp, use 87 * [burnInOffset]. 88 */ 89 private fun burnInOffsetDefinedInPixels( 90 @DimenRes maxBurnInOffsetResourceId: Int, 91 isXAxis: Boolean, 92 ): StateFlow<Int> { 93 return configurationRepository.scaleForResolution 94 .flatMapLatest { scale -> 95 val maxBurnInOffsetPixels = 96 context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId) 97 keyguardInteractor.dozeTimeTick.mapLatest { 98 calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) 99 } 100 } 101 .stateIn( 102 scope, 103 SharingStarted.WhileSubscribed(), 104 calculateOffset( 105 context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId), 106 isXAxis, 107 configurationRepository.getResolutionScale(), 108 ) 109 ) 110 } 111 112 private fun calculateOffset( 113 maxBurnInOffsetPixels: Int, 114 isXAxis: Boolean, 115 scale: Float = 1f 116 ): Int { 117 return (burnInHelperWrapper.burnInOffset(maxBurnInOffsetPixels, isXAxis) * scale).toInt() 118 } 119 } 120