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