• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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