1 /*
2  * 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.binder
18 
19 import android.annotation.DrawableRes
20 import android.view.View
21 import android.view.ViewGroup
22 import androidx.lifecycle.Lifecycle
23 import androidx.lifecycle.repeatOnLifecycle
24 import com.android.app.animation.Interpolators
25 import com.android.systemui.R
26 import com.android.systemui.common.shared.model.Icon
27 import com.android.systemui.common.shared.model.Text
28 import com.android.systemui.common.shared.model.TintedIcon
29 import com.android.systemui.flags.FeatureFlags
30 import com.android.systemui.flags.Flags
31 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
32 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
33 import com.android.systemui.lifecycle.repeatWhenAttached
34 import com.android.systemui.statusbar.StatusBarState
35 import com.android.systemui.statusbar.policy.KeyguardStateController
36 import com.android.systemui.temporarydisplay.ViewPriority
37 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
38 import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
39 import kotlinx.coroutines.DisposableHandle
40 import kotlinx.coroutines.ExperimentalCoroutinesApi
41 import kotlinx.coroutines.launch
42 
43 /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
44 @ExperimentalCoroutinesApi
45 object KeyguardRootViewBinder {
46     @JvmStatic
47     fun bind(
48         view: ViewGroup,
49         viewModel: KeyguardRootViewModel,
50         featureFlags: FeatureFlags,
51         occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
52         chipbarCoordinator: ChipbarCoordinator,
53         keyguardStateController: KeyguardStateController,
54     ): DisposableHandle {
55         val disposableHandle =
56             view.repeatWhenAttached {
57                 if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
58                     repeatOnLifecycle(Lifecycle.State.CREATED) {
59                         launch {
60                             occludingAppDeviceEntryMessageViewModel.message.collect {
61                                 biometricMessage ->
62                                 if (biometricMessage?.message != null) {
63                                     chipbarCoordinator.displayView(
64                                         createChipbarInfo(
65                                             biometricMessage.message,
66                                             R.drawable.ic_lock,
67                                         )
68                                     )
69                                 } else {
70                                     chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
71                                 }
72                             }
73                         }
74                     }
75                 }
76 
77                 if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
78                     repeatOnLifecycle(Lifecycle.State.STARTED) {
79                         launch {
80                             viewModel.keyguardRootViewVisibilityState.collect { visibilityState ->
81                                 view.animate().cancel()
82                                 val goingToFullShade = visibilityState.goingToFullShade
83                                 val statusBarState = visibilityState.statusBarState
84                                 val isOcclusionTransitionRunning =
85                                     visibilityState.occlusionTransitionRunning
86                                 if (goingToFullShade) {
87                                     view.animate().alpha(0f).setStartDelay(
88                                         keyguardStateController.keyguardFadingAwayDelay
89                                     ).setDuration(
90                                         keyguardStateController.shortenedFadingAwayDuration
91                                     ).setInterpolator(
92                                         Interpolators.ALPHA_OUT
93                                     ).withEndAction { view.visibility = View.GONE }.start()
94                                 } else if (
95                                     statusBarState == StatusBarState.KEYGUARD ||
96                                     statusBarState == StatusBarState.SHADE_LOCKED
97                                 ) {
98                                     view.visibility = View.VISIBLE
99                                     if (!isOcclusionTransitionRunning) {
100                                         view.alpha = 1f
101                                     }
102                                 } else {
103                                     view.visibility = View.GONE
104                                 }
105                             }
106                         }
107 
108                         launch {
109                             viewModel.alpha.collect { alpha ->
110                                 view.alpha = alpha
111                             }
112                         }
113                     }
114                 }
115             }
116         return disposableHandle
117     }
118 
119     /**
120      * Creates an instance of [ChipbarInfo] that can be sent to [ChipbarCoordinator] for display.
121      */
122     private fun createChipbarInfo(message: String, @DrawableRes icon: Int): ChipbarInfo {
123         return ChipbarInfo(
124             startIcon =
125             TintedIcon(
126                 Icon.Resource(icon, null),
127                 ChipbarInfo.DEFAULT_ICON_TINT,
128             ),
129             text = Text.Loaded(message),
130             endItem = null,
131             vibrationEffect = null,
132             windowTitle = "OccludingAppUnlockMsgChip",
133             wakeReason = "OCCLUDING_APP_UNLOCK_MSG_CHIP",
134             timeoutMs = 3500,
135             id = ID,
136             priority = ViewPriority.CRITICAL,
137             instanceId = null,
138         )
139     }
140 
141     private const val ID = "occluding_app_device_entry_unlock_msg"
142 }
143