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 @file:OptIn(ExperimentalCoroutinesApi::class) 18 19 package com.android.systemui.keyguard.ui.viewmodel 20 21 import android.util.Log 22 import android.util.MathUtils 23 import com.android.app.animation.Interpolators 24 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor 25 import com.android.systemui.dagger.SysUISingleton 26 import com.android.systemui.keyguard.MigrateClocksToBlueprint 27 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor 28 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 29 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 30 import com.android.systemui.keyguard.shared.model.BurnInModel 31 import com.android.systemui.keyguard.shared.model.ClockSize 32 import com.android.systemui.keyguard.shared.model.KeyguardState 33 import com.android.systemui.keyguard.ui.StateToValue 34 import com.android.systemui.res.R 35 import javax.inject.Inject 36 import kotlin.math.max 37 import kotlinx.coroutines.ExperimentalCoroutinesApi 38 import kotlinx.coroutines.flow.Flow 39 import kotlinx.coroutines.flow.combine 40 import kotlinx.coroutines.flow.distinctUntilChanged 41 import kotlinx.coroutines.flow.flatMapLatest 42 import kotlinx.coroutines.flow.map 43 import kotlinx.coroutines.flow.onStart 44 45 /** 46 * Models UI state for elements that need to apply anti-burn-in tactics when showing in AOD 47 * (always-on display). 48 */ 49 @SysUISingleton 50 class AodBurnInViewModel 51 @Inject 52 constructor( 53 private val burnInInteractor: BurnInInteractor, 54 private val configurationInteractor: ConfigurationInteractor, 55 private val keyguardInteractor: KeyguardInteractor, 56 private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 57 private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel, 58 private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, 59 private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, 60 private val keyguardClockViewModel: KeyguardClockViewModel, 61 ) { 62 private val TAG = "AodBurnInViewModel" 63 64 /** All burn-in movement: x,y,scale, to shift items and prevent burn-in */ 65 fun movement( 66 burnInParams: BurnInParameters, 67 ): Flow<BurnInModel> { 68 val params = 69 if (burnInParams.minViewY < burnInParams.topInset) { 70 // minViewY should never be below the inset. Correct it if needed 71 Log.w(TAG, "minViewY is below topInset: $burnInParams") 72 burnInParams.copy(minViewY = burnInParams.topInset) 73 } else { 74 burnInParams 75 } 76 return configurationInteractor 77 .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y) 78 .flatMapLatest { enterFromTopAmount -> 79 combine( 80 keyguardInteractor.keyguardTranslationY.onStart { emit(0f) }, 81 burnIn(params).onStart { emit(BurnInModel()) }, 82 goneToAodTransitionViewModel 83 .enterFromTopTranslationY(enterFromTopAmount) 84 .onStart { emit(StateToValue()) }, 85 occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart { 86 emit(0f) 87 }, 88 aodToLockscreenTransitionViewModel.translationY(params.translationY).onStart { 89 emit(StateToValue()) 90 }, 91 ) { 92 keyguardTranslationY, 93 burnInModel, 94 goneToAod, 95 occludedToLockscreen, 96 aodToLockscreen -> 97 val translationY = 98 if (aodToLockscreen.transitionState.isTransitioning()) { 99 aodToLockscreen.value ?: 0f 100 } else if (goneToAod.transitionState.isTransitioning()) { 101 (goneToAod.value ?: 0f) + burnInModel.translationY 102 } else { 103 burnInModel.translationY + occludedToLockscreen + keyguardTranslationY 104 } 105 burnInModel.copy(translationY = translationY.toInt()) 106 } 107 } 108 .distinctUntilChanged() 109 } 110 111 private fun burnIn( 112 params: BurnInParameters, 113 ): Flow<BurnInModel> { 114 return combine( 115 keyguardTransitionInteractor.transitionValue(KeyguardState.AOD).map { 116 Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it) 117 }, 118 burnInInteractor.burnIn( 119 xDimenResourceId = R.dimen.burn_in_prevention_offset_x, 120 yDimenResourceId = R.dimen.burn_in_prevention_offset_y 121 ), 122 ) { interpolated, burnIn -> 123 val useAltAod = 124 keyguardClockViewModel.currentClock.value 125 ?.config 126 ?.useAlternateSmartspaceAODTransition == true 127 // Only scale large non-weather clocks 128 // elements in large weather clock will translate the same as smartspace 129 val useScaleOnly = 130 (!useAltAod) && keyguardClockViewModel.clockSize.value == ClockSize.LARGE 131 132 val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt() 133 val translationY = 134 if (MigrateClocksToBlueprint.isEnabled) { 135 max(params.topInset - params.minViewY, burnInY) 136 } else { 137 max(params.topInset, params.minViewY + burnInY) - params.minViewY 138 } 139 BurnInModel( 140 translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(), 141 translationY = translationY, 142 scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolated), 143 scaleClockOnly = useScaleOnly 144 ) 145 } 146 } 147 } 148 149 /** UI-sourced parameters to pass into the various methods of [AodBurnInViewModel]. */ 150 data class BurnInParameters( 151 /** System insets that keyguard needs to stay out of */ 152 val topInset: Int = 0, 153 /** The min y-value of the visible elements on lockscreen */ 154 val minViewY: Int = Int.MAX_VALUE, 155 /** The current y translation of the view */ <lambda>null156 val translationY: () -> Float? = { null } 157 ) 158 159 /** 160 * Models UI state of the scaling to apply to elements that need to be scaled for anti-burn-in 161 * purposes. 162 */ 163 data class BurnInScaleViewModel( 164 val scale: Float = 1f, 165 /** Whether the scale only applies to clock UI elements. */ 166 val scaleClockOnly: Boolean = false, 167 ) 168