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 package com.android.systemui.keyguard.ui.viewmodel 18 19 import android.content.res.Resources 20 import androidx.compose.runtime.getValue 21 import androidx.compose.ui.Alignment 22 import com.android.app.tracing.coroutines.launchTraced as launch 23 import com.android.systemui.biometrics.AuthController 24 import com.android.systemui.customization.R as customR 25 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor 26 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor 27 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor 28 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor 29 import com.android.systemui.keyguard.shared.model.ClockSize 30 import com.android.systemui.keyguard.shared.model.KeyguardState 31 import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallback 32 import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallbackDelegator 33 import com.android.systemui.lifecycle.ExclusiveActivatable 34 import com.android.systemui.lifecycle.Hydrator 35 import com.android.systemui.res.R 36 import com.android.systemui.shade.domain.interactor.ShadeModeInteractor 37 import com.android.systemui.shade.shared.model.ShadeMode 38 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor 39 import dagger.assisted.Assisted 40 import dagger.assisted.AssistedFactory 41 import dagger.assisted.AssistedInject 42 import kotlinx.coroutines.awaitCancellation 43 import kotlinx.coroutines.coroutineScope 44 import kotlinx.coroutines.flow.combine 45 import kotlinx.coroutines.flow.distinctUntilChanged 46 import kotlinx.coroutines.flow.map 47 48 class LockscreenContentViewModel 49 @AssistedInject 50 constructor( 51 private val clockInteractor: KeyguardClockInteractor, 52 interactor: KeyguardBlueprintInteractor, 53 private val authController: AuthController, 54 val touchHandling: KeyguardTouchHandlingViewModel, 55 shadeModeInteractor: ShadeModeInteractor, 56 unfoldTransitionInteractor: UnfoldTransitionInteractor, 57 deviceEntryInteractor: DeviceEntryInteractor, 58 transitionInteractor: KeyguardTransitionInteractor, 59 private val keyguardTransitionAnimationCallbackDelegator: 60 KeyguardTransitionAnimationCallbackDelegator, 61 @Assisted private val keyguardTransitionAnimationCallback: KeyguardTransitionAnimationCallback, 62 ) : ExclusiveActivatable() { 63 64 private val hydrator = Hydrator("LockscreenContentViewModel.hydrator") 65 66 val isUdfpsVisible: Boolean 67 get() = authController.isUdfpsSupported 68 69 /** Where to place the notifications stack on the lockscreen. */ 70 val notificationsPlacement: NotificationsPlacement by 71 hydrator.hydratedStateOf( 72 traceName = "notificationsPlacement", 73 initialValue = NotificationsPlacement.BelowClock, 74 source = 75 combine(shadeModeInteractor.shadeMode, clockInteractor.clockSize) { 76 shadeMode, 77 clockSize -> 78 if (shadeMode is ShadeMode.Split) { 79 NotificationsPlacement.BesideClock(alignment = Alignment.TopEnd) 80 } else if (clockSize == ClockSize.SMALL) { 81 NotificationsPlacement.BelowClock 82 } else { 83 NotificationsPlacement.BesideClock(alignment = Alignment.TopStart) 84 } 85 }, 86 ) 87 88 /** Amount of horizontal translation that should be applied to elements in the scene. */ 89 val unfoldTranslations: UnfoldTranslations by 90 hydrator.hydratedStateOf( 91 traceName = "unfoldTranslations", 92 initialValue = UnfoldTranslations(), 93 source = 94 combine( 95 unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true), 96 unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false), 97 ::UnfoldTranslations, 98 ), 99 ) 100 101 /** Whether the content of the scene UI should be shown. */ 102 val isContentVisible: Boolean by 103 hydrator.hydratedStateOf( 104 traceName = "isContentVisible", 105 initialValue = true, 106 // Content is visible unless we're OCCLUDED. Currently, we don't have nice animations 107 // into and out of OCCLUDED, so the lockscreen/AOD content is hidden immediately upon 108 // entering/exiting OCCLUDED. 109 source = transitionInteractor.transitionValue(KeyguardState.OCCLUDED).map { it == 0f }, 110 ) 111 112 /** Indicates whether lockscreen notifications should be rendered. */ 113 val areNotificationsVisible: Boolean by 114 hydrator.hydratedStateOf( 115 traceName = "areNotificationsVisible", 116 initialValue = false, 117 // Content is visible unless we're OCCLUDED. Currently, we don't have nice animations 118 // into and out of OCCLUDED, so the lockscreen/AOD content is hidden immediately upon 119 // entering/exiting OCCLUDED. 120 source = 121 combine(clockInteractor.clockSize, shadeModeInteractor.isShadeLayoutWide) { 122 clockSize, 123 isShadeLayoutWide -> 124 clockSize == ClockSize.SMALL || isShadeLayoutWide 125 }, 126 ) 127 128 /** @see DeviceEntryInteractor.isBypassEnabled */ 129 val isBypassEnabled: Boolean by 130 hydrator.hydratedStateOf( 131 traceName = "isBypassEnabled", 132 source = deviceEntryInteractor.isBypassEnabled, 133 ) 134 135 val blueprintId: String by 136 hydrator.hydratedStateOf( 137 traceName = "blueprintId", 138 initialValue = interactor.getCurrentBlueprint().id, 139 source = interactor.blueprint.map { it.id }.distinctUntilChanged(), 140 ) 141 142 override suspend fun onActivated(): Nothing { 143 coroutineScope { 144 try { 145 launch { hydrator.activate() } 146 147 keyguardTransitionAnimationCallbackDelegator.delegate = 148 keyguardTransitionAnimationCallback 149 150 awaitCancellation() 151 } finally { 152 keyguardTransitionAnimationCallbackDelegator.delegate = null 153 } 154 } 155 } 156 157 fun getSmartSpacePaddingTop(resources: Resources): Int { 158 return if (clockInteractor.clockSize.value == ClockSize.LARGE) { 159 resources.getDimensionPixelSize(customR.dimen.keyguard_smartspace_top_offset) + 160 resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) 161 } else { 162 0 163 } 164 } 165 166 data class UnfoldTranslations( 167 168 /** 169 * Amount of horizontal translation to apply to elements that are aligned to the start side 170 * (left in left-to-right layouts). Can also be used as horizontal padding for elements that 171 * need horizontal padding on both side. In pixels. 172 */ 173 val start: Float = 0f, 174 175 /** 176 * Amount of horizontal translation to apply to elements that are aligned to the end side 177 * (right in left-to-right layouts). In pixels. 178 */ 179 val end: Float = 0f, 180 ) 181 182 /** Where to place the notifications stack on the lockscreen. */ 183 sealed interface NotificationsPlacement { 184 /** Show notifications below the lockscreen clock. */ 185 data object BelowClock : NotificationsPlacement 186 187 /** Show notifications side-by-side with the clock. */ 188 data class BesideClock(val alignment: Alignment) : NotificationsPlacement 189 } 190 191 @AssistedFactory 192 interface Factory { 193 fun create( 194 keyguardTransitionAnimationCallback: KeyguardTransitionAnimationCallback 195 ): LockscreenContentViewModel 196 } 197 } 198