• 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 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