1 /* <lambda>null2 * 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.common.ui.domain.interactor.ConfigurationInteractor 23 import com.android.systemui.dagger.SysUISingleton 24 import com.android.systemui.dagger.qualifiers.Application 25 import com.android.systemui.doze.util.BurnInHelperWrapper 26 import com.android.systemui.keyguard.shared.model.BurnInModel 27 import com.android.systemui.res.R 28 import javax.inject.Inject 29 import kotlinx.coroutines.CoroutineScope 30 import kotlinx.coroutines.ExperimentalCoroutinesApi 31 import kotlinx.coroutines.flow.Flow 32 import kotlinx.coroutines.flow.SharingStarted 33 import kotlinx.coroutines.flow.StateFlow 34 import kotlinx.coroutines.flow.combine 35 import kotlinx.coroutines.flow.distinctUntilChanged 36 import kotlinx.coroutines.flow.flatMapLatest 37 import kotlinx.coroutines.flow.map 38 import kotlinx.coroutines.flow.mapLatest 39 import kotlinx.coroutines.flow.stateIn 40 41 /** Encapsulates business-logic related to Ambient Display burn-in offsets. */ 42 @ExperimentalCoroutinesApi 43 @SysUISingleton 44 class BurnInInteractor 45 @Inject 46 constructor( 47 private val context: Context, 48 private val burnInHelperWrapper: BurnInHelperWrapper, 49 @Application private val scope: CoroutineScope, 50 private val configurationInteractor: ConfigurationInteractor, 51 private val keyguardInteractor: KeyguardInteractor, 52 ) { 53 val deviceEntryIconXOffset: StateFlow<Int> = 54 burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true) 55 .stateIn(scope, SharingStarted.WhileSubscribed(), 0) 56 val deviceEntryIconYOffset: StateFlow<Int> = 57 burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false) 58 .stateIn(scope, SharingStarted.WhileSubscribed(), 0) 59 val udfpsProgress: StateFlow<Float> = 60 keyguardInteractor.dozeTimeTick 61 .mapLatest { burnInHelperWrapper.burnInProgressOffset() } 62 .stateIn( 63 scope, 64 SharingStarted.WhileSubscribed(), 65 burnInHelperWrapper.burnInProgressOffset() 66 ) 67 68 /** Given the max x,y dimens, determine the current translation shifts. */ 69 fun burnIn(xDimenResourceId: Int, yDimenResourceId: Int): Flow<BurnInModel> { 70 return combine( 71 burnInOffset(xDimenResourceId, isXAxis = true), 72 burnInOffset(yDimenResourceId, isXAxis = false).map { 73 it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId) 74 } 75 ) { translationX, translationY -> 76 BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale()) 77 } 78 .distinctUntilChanged() 79 } 80 81 /** 82 * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the 83 * max burn-in offset on any configuration changes. If the max burn-in offset is specified in 84 * pixels, use [burnInOffsetDefinedInPixels]. 85 */ 86 private fun burnInOffset( 87 @DimenRes maxBurnInOffsetResourceId: Int, 88 isXAxis: Boolean, 89 ): Flow<Int> { 90 return configurationInteractor.onAnyConfigurationChange.flatMapLatest { 91 val maxBurnInOffsetPixels = 92 context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId) 93 keyguardInteractor.dozeTimeTick.mapLatest { 94 calculateOffset(maxBurnInOffsetPixels, isXAxis) 95 } 96 } 97 } 98 99 /** 100 * Use for max burn-in offBurn-in offsets that ARE specified in pixels. This flow will apply the 101 * a scale for any resolution changes. If the max burn-in offset is specified in dp, use 102 * [burnInOffset]. 103 */ 104 private fun burnInOffsetDefinedInPixels( 105 @DimenRes maxBurnInOffsetResourceId: Int, 106 isXAxis: Boolean, 107 ): Flow<Int> { 108 return configurationInteractor.scaleForResolution.flatMapLatest { scale -> 109 val maxBurnInOffsetPixels = 110 context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId) 111 keyguardInteractor.dozeTimeTick.mapLatest { 112 calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) 113 } 114 } 115 } 116 117 private fun calculateOffset( 118 maxBurnInOffsetPixels: Int, 119 isXAxis: Boolean, 120 scale: Float = 1f 121 ): Int { 122 return (burnInHelperWrapper.burnInOffset(maxBurnInOffsetPixels, isXAxis) * scale).toInt() 123 } 124 } 125