• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.ui.preview
19 
20 import android.content.BroadcastReceiver
21 import android.content.Context
22 import android.content.Intent
23 import android.content.IntentFilter
24 import android.hardware.display.DisplayManager
25 import android.os.Bundle
26 import android.os.IBinder
27 import android.view.LayoutInflater
28 import android.view.SurfaceControlViewHost
29 import android.view.View
30 import android.view.ViewGroup
31 import android.view.WindowManager
32 import android.widget.FrameLayout
33 import com.android.keyguard.ClockEventController
34 import com.android.keyguard.KeyguardClockSwitch
35 import com.android.systemui.R
36 import com.android.systemui.broadcast.BroadcastDispatcher
37 import com.android.systemui.dagger.qualifiers.Application
38 import com.android.systemui.dagger.qualifiers.Main
39 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
40 import com.android.systemui.shared.clocks.ClockRegistry
41 import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
42 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
43 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
44 import dagger.assisted.Assisted
45 import dagger.assisted.AssistedInject
46 import kotlinx.coroutines.CoroutineDispatcher
47 import kotlinx.coroutines.DisposableHandle
48 import kotlinx.coroutines.runBlocking
49 
50 /** Renders the preview of the lock screen. */
51 class KeyguardPreviewRenderer
52 @AssistedInject
53 constructor(
54     @Application private val context: Context,
55     @Main private val mainDispatcher: CoroutineDispatcher,
56     private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
57     displayManager: DisplayManager,
58     private val windowManager: WindowManager,
59     private val clockController: ClockEventController,
60     private val clockRegistry: ClockRegistry,
61     private val broadcastDispatcher: BroadcastDispatcher,
62     @Assisted bundle: Bundle,
63 ) {
64 
65     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
66     private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
67     private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
68     private val shouldHighlightSelectedAffordance: Boolean =
69         bundle.getBoolean(
70             KeyguardQuickAffordancePreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
71             false,
72         )
73     private val shouldHideClock: Boolean =
74         bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
75 
76     private var host: SurfaceControlViewHost
77 
78     val surfacePackage: SurfaceControlViewHost.SurfacePackage
79         get() = host.surfacePackage
80 
81     private var clockView: View? = null
82 
83     private val disposables = mutableSetOf<DisposableHandle>()
84     private var isDestroyed = false
85 
86     init {
87         bottomAreaViewModel.enablePreviewMode(
88             initiallySelectedSlotId =
89                 bundle.getString(
90                     KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
91                 ),
92             shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
93         )
<lambda>null94         runBlocking(mainDispatcher) {
95             host =
96                 SurfaceControlViewHost(
97                     context,
98                     displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID)),
99                     hostToken,
100                 )
101             disposables.add(DisposableHandle { host.release() })
102         }
103     }
104 
rendernull105     fun render() {
106         runBlocking(mainDispatcher) {
107             val rootView = FrameLayout(context)
108 
109             setUpBottomArea(rootView)
110             if (!shouldHideClock) {
111                 setUpClock(rootView)
112             }
113 
114             rootView.measure(
115                 View.MeasureSpec.makeMeasureSpec(
116                     windowManager.currentWindowMetrics.bounds.width(),
117                     View.MeasureSpec.EXACTLY
118                 ),
119                 View.MeasureSpec.makeMeasureSpec(
120                     windowManager.currentWindowMetrics.bounds.height(),
121                     View.MeasureSpec.EXACTLY
122                 ),
123             )
124             rootView.layout(0, 0, rootView.measuredWidth, rootView.measuredHeight)
125 
126             // This aspect scales the view to fit in the surface and centers it
127             val scale: Float =
128                 (width / rootView.measuredWidth.toFloat()).coerceAtMost(
129                     height / rootView.measuredHeight.toFloat()
130                 )
131 
132             rootView.scaleX = scale
133             rootView.scaleY = scale
134             rootView.pivotX = 0f
135             rootView.pivotY = 0f
136             rootView.translationX = (width - scale * rootView.width) / 2
137             rootView.translationY = (height - scale * rootView.height) / 2
138 
139             host.setView(rootView, rootView.measuredWidth, rootView.measuredHeight)
140         }
141     }
142 
onSlotSelectednull143     fun onSlotSelected(slotId: String) {
144         bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
145     }
146 
destroynull147     fun destroy() {
148         isDestroyed = true
149         disposables.forEach { it.dispose() }
150     }
151 
setUpBottomAreanull152     private fun setUpBottomArea(parentView: ViewGroup) {
153         val bottomAreaView =
154             LayoutInflater.from(context)
155                 .inflate(
156                     R.layout.keyguard_bottom_area,
157                     parentView,
158                     false,
159                 ) as KeyguardBottomAreaView
160         bottomAreaView.init(
161             viewModel = bottomAreaViewModel,
162         )
163         parentView.addView(
164             bottomAreaView,
165             FrameLayout.LayoutParams(
166                 FrameLayout.LayoutParams.MATCH_PARENT,
167                 FrameLayout.LayoutParams.MATCH_PARENT,
168             ),
169         )
170     }
171 
setUpClocknull172     private fun setUpClock(parentView: ViewGroup) {
173         val clockChangeListener =
174             object : ClockRegistry.ClockChangeListener {
175                 override fun onCurrentClockChanged() {
176                     onClockChanged(parentView)
177                 }
178             }
179         clockRegistry.registerClockChangeListener(clockChangeListener)
180         disposables.add(
181             DisposableHandle { clockRegistry.unregisterClockChangeListener(clockChangeListener) }
182         )
183 
184         clockController.registerListeners(parentView)
185         disposables.add(DisposableHandle { clockController.unregisterListeners() })
186 
187         val receiver =
188             object : BroadcastReceiver() {
189                 override fun onReceive(context: Context?, intent: Intent?) {
190                     clockController.clock?.smallClock?.events?.onTimeTick()
191                     clockController.clock?.largeClock?.events?.onTimeTick()
192                 }
193             }
194         broadcastDispatcher.registerReceiver(
195             receiver,
196             IntentFilter().apply {
197                 addAction(Intent.ACTION_TIME_TICK)
198                 addAction(Intent.ACTION_TIME_CHANGED)
199             },
200         )
201         disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
202 
203         onClockChanged(parentView)
204     }
205 
onClockChangednull206     private fun onClockChanged(parentView: ViewGroup) {
207         clockController.clock = clockRegistry.createCurrentClock()
208         clockController.clock
209             ?.largeClock
210             ?.events
211             ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
212         clockView?.let { parentView.removeView(it) }
213         clockView =
214             clockController.clock?.largeClock?.view?.apply {
215                 if (shouldHighlightSelectedAffordance) {
216                     alpha = DIM_ALPHA
217                 }
218                 parentView.addView(this)
219             }
220     }
221 
222     companion object {
223         private const val KEY_HOST_TOKEN = "host_token"
224         private const val KEY_VIEW_WIDTH = "width"
225         private const val KEY_VIEW_HEIGHT = "height"
226         private const val KEY_DISPLAY_ID = "display_id"
227 
228         private const val DIM_ALPHA = 0.3f
229     }
230 }
231