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.app.WallpaperColors
21 import android.content.BroadcastReceiver
22 import android.content.Context
23 import android.content.Intent
24 import android.content.IntentFilter
25 import android.content.res.Resources
26 import android.graphics.Rect
27 import android.hardware.display.DisplayManager
28 import android.os.Bundle
29 import android.os.Handler
30 import android.os.IBinder
31 import android.view.LayoutInflater
32 import android.view.SurfaceControlViewHost
33 import android.view.View
34 import android.view.ViewGroup
35 import android.view.WindowManager
36 import android.widget.FrameLayout
37 import androidx.core.view.isInvisible
38 import com.android.keyguard.ClockEventController
39 import com.android.keyguard.KeyguardClockSwitch
40 import com.android.systemui.R
41 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
42 import com.android.systemui.broadcast.BroadcastDispatcher
43 import com.android.systemui.dagger.qualifiers.Application
44 import com.android.systemui.dagger.qualifiers.Main
45 import com.android.systemui.flags.FeatureFlags
46 import com.android.systemui.flags.Flags
47 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
48 import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
49 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
50 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
51 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
52 import com.android.systemui.keyguard.ui.view.KeyguardRootView
53 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
54 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
55 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
56 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
57 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
58 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
59 import com.android.systemui.monet.ColorScheme
60 import com.android.systemui.plugins.ClockController
61 import com.android.systemui.plugins.FalsingManager
62 import com.android.systemui.shared.clocks.ClockRegistry
63 import com.android.systemui.shared.clocks.DefaultClockController
64 import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
65 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
66 import com.android.systemui.statusbar.KeyguardIndicationController
67 import com.android.systemui.statusbar.VibratorHelper
68 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
69 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
70 import dagger.assisted.Assisted
71 import dagger.assisted.AssistedInject
72 import kotlinx.coroutines.CoroutineDispatcher
73 import kotlinx.coroutines.DisposableHandle
74 import kotlinx.coroutines.runBlocking
75 
76 /** Renders the preview of the lock screen. */
77 class KeyguardPreviewRenderer
78 @AssistedInject
79 constructor(
80     @Application private val context: Context,
81     @Main private val mainDispatcher: CoroutineDispatcher,
82     @Main private val mainHandler: Handler,
83     private val clockViewModel: KeyguardPreviewClockViewModel,
84     private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
85     private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
86     private val quickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
87     displayManager: DisplayManager,
88     private val windowManager: WindowManager,
89     private val clockController: ClockEventController,
90     private val clockRegistry: ClockRegistry,
91     private val broadcastDispatcher: BroadcastDispatcher,
92     private val lockscreenSmartspaceController: LockscreenSmartspaceController,
93     private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
94     private val featureFlags: FeatureFlags,
95     private val falsingManager: FalsingManager,
96     private val vibratorHelper: VibratorHelper,
97     private val indicationController: KeyguardIndicationController,
98     private val keyguardRootViewModel: KeyguardRootViewModel,
99     @Assisted bundle: Bundle,
100     private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
101     private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
102 ) {
103 
104     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
105     private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
106     private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
107     private val shouldHighlightSelectedAffordance: Boolean =
108         bundle.getBoolean(
109             KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
110             false,
111         )
112     /** [shouldHideClock] here means that we never create and bind the clock views */
113     private val shouldHideClock: Boolean =
114         bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
115     private val wallpaperColors: WallpaperColors? = bundle.getParcelable(KEY_COLORS)
116 
117     private var host: SurfaceControlViewHost
118 
119     val surfacePackage: SurfaceControlViewHost.SurfacePackage
120         get() = checkNotNull(host.surfacePackage)
121 
122     private lateinit var largeClockHostView: FrameLayout
123     private lateinit var smallClockHostView: FrameLayout
124     private var smartSpaceView: View? = null
125 
126     private val disposables = mutableSetOf<DisposableHandle>()
127     private var isDestroyed = false
128 
129     private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
130 
131     init {
132         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
133             keyguardRootViewModel.enablePreviewMode(
134                 initiallySelectedSlotId =
135                     bundle.getString(
136                         KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
137                     ),
138                 shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
139             )
140         } else {
141             bottomAreaViewModel.enablePreviewMode(
142                 initiallySelectedSlotId =
143                     bundle.getString(
144                         KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
145                     ),
146                 shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
147             )
148         }
149         runBlocking(mainDispatcher) {
150             host =
151                 SurfaceControlViewHost(
152                     context,
153                     displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID)),
154                     hostToken,
155                     "KeyguardPreviewRenderer"
156                 )
157             disposables.add(DisposableHandle { host.release() })
158         }
159     }
160 
161     fun render() {
162         mainHandler.post {
163             val rootView = FrameLayout(context)
164 
165             if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
166                 val keyguardRootView = KeyguardRootView(context, null)
167                 rootView.addView(
168                     keyguardRootView,
169                     FrameLayout.LayoutParams(
170                         FrameLayout.LayoutParams.MATCH_PARENT,
171                         FrameLayout.LayoutParams.MATCH_PARENT,
172                     ),
173                 )
174                 setupShortcuts(keyguardRootView)
175                 KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
176                 keyguardBlueprintInteractor.refreshBlueprint()
177             } else {
178                 setUpBottomArea(rootView)
179             }
180 
181             setUpSmartspace(rootView)
182             smartSpaceView?.let {
183                 KeyguardPreviewSmartspaceViewBinder.bind(it, smartspaceViewModel)
184             }
185 
186             setUpUdfps(rootView)
187 
188             if (!shouldHideClock) {
189                 setUpClock(rootView)
190                 KeyguardPreviewClockViewBinder.bind(
191                     largeClockHostView,
192                     smallClockHostView,
193                     clockViewModel,
194                 )
195             }
196 
197             rootView.measure(
198                 View.MeasureSpec.makeMeasureSpec(
199                     windowManager.currentWindowMetrics.bounds.width(),
200                     View.MeasureSpec.EXACTLY
201                 ),
202                 View.MeasureSpec.makeMeasureSpec(
203                     windowManager.currentWindowMetrics.bounds.height(),
204                     View.MeasureSpec.EXACTLY
205                 ),
206             )
207             rootView.layout(0, 0, rootView.measuredWidth, rootView.measuredHeight)
208 
209             // This aspect scales the view to fit in the surface and centers it
210             val scale: Float =
211                 (width / rootView.measuredWidth.toFloat()).coerceAtMost(
212                     height / rootView.measuredHeight.toFloat()
213                 )
214 
215             rootView.scaleX = scale
216             rootView.scaleY = scale
217             rootView.pivotX = 0f
218             rootView.pivotY = 0f
219             rootView.translationX = (width - scale * rootView.width) / 2
220             rootView.translationY = (height - scale * rootView.height) / 2
221 
222             if (isDestroyed) {
223                 return@post
224             }
225 
226             host.setView(rootView, rootView.measuredWidth, rootView.measuredHeight)
227         }
228     }
229 
230     fun onSlotSelected(slotId: String) {
231         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
232             quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
233         } else {
234             bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
235         }
236     }
237 
238     fun destroy() {
239         isDestroyed = true
240         lockscreenSmartspaceController.disconnect()
241         disposables.forEach { it.dispose() }
242         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
243             shortcutsBindings.forEach { it.destroy() }
244         }
245     }
246 
247     /**
248      * Hides or shows smartspace
249      *
250      * @param hide TRUE hides smartspace, FALSE shows smartspace
251      */
252     fun hideSmartspace(hide: Boolean) {
253         mainHandler.post { smartSpaceView?.visibility = if (hide) View.INVISIBLE else View.VISIBLE }
254     }
255 
256     /**
257      * This sets up and shows a non-interactive smart space
258      *
259      * The top padding is as follows: Status bar height + clock top margin + keyguard smart space
260      * top offset
261      *
262      * The start padding is as follows: Clock padding start + Below clock padding start
263      *
264      * The end padding is as follows: Below clock padding end
265      */
266     private fun setUpSmartspace(parentView: ViewGroup) {
267         if (
268             !lockscreenSmartspaceController.isEnabled() ||
269                 !lockscreenSmartspaceController.isDateWeatherDecoupled()
270         ) {
271             return
272         }
273 
274         smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
275 
276         val topPadding: Int =
277             KeyguardPreviewSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
278                 context.resources,
279             )
280 
281         val startPadding: Int =
282             with(context.resources) {
283                 getDimensionPixelSize(R.dimen.clock_padding_start) +
284                     getDimensionPixelSize(R.dimen.below_clock_padding_start)
285             }
286 
287         val endPadding: Int =
288             context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
289 
290         smartSpaceView?.let {
291             it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
292             it.isClickable = false
293             it.isInvisible = true
294             parentView.addView(
295                 it,
296                 FrameLayout.LayoutParams(
297                     FrameLayout.LayoutParams.MATCH_PARENT,
298                     FrameLayout.LayoutParams.WRAP_CONTENT,
299                 ),
300             )
301         }
302 
303         smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
304     }
305 
306     @Deprecated("Deprecated as part of b/278057014")
307     private fun setUpBottomArea(parentView: ViewGroup) {
308         val bottomAreaView =
309             LayoutInflater.from(context)
310                 .inflate(
311                     R.layout.keyguard_bottom_area,
312                     parentView,
313                     false,
314                 ) as KeyguardBottomAreaView
315         bottomAreaView.init(
316             viewModel = bottomAreaViewModel,
317         )
318         parentView.addView(
319             bottomAreaView,
320             FrameLayout.LayoutParams(
321                 FrameLayout.LayoutParams.MATCH_PARENT,
322                 FrameLayout.LayoutParams.MATCH_PARENT,
323             ),
324         )
325     }
326 
327     private fun setupShortcuts(keyguardRootView: KeyguardRootView) {
328         shortcutsBindings.add(
329             KeyguardQuickAffordanceViewBinder.bind(
330                 keyguardRootView.requireViewById(R.id.start_button),
331                 quickAffordancesCombinedViewModel.startButton,
332                 keyguardRootViewModel.alpha,
333                 falsingManager,
334                 vibratorHelper,
335             ) {
336                 indicationController.showTransientIndication(it)
337             }
338         )
339 
340         shortcutsBindings.add(
341             KeyguardQuickAffordanceViewBinder.bind(
342                 keyguardRootView.requireViewById(R.id.end_button),
343                 quickAffordancesCombinedViewModel.endButton,
344                 keyguardRootViewModel.alpha,
345                 falsingManager,
346                 vibratorHelper,
347             ) {
348                 indicationController.showTransientIndication(it)
349             }
350         )
351     }
352 
353     private fun setUpUdfps(parentView: ViewGroup) {
354         val sensorBounds = udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds
355 
356         // If sensorBounds are default rect, then there is no UDFPS
357         if (sensorBounds == Rect()) {
358             return
359         }
360 
361         // Place the UDFPS view in the proper sensor location
362         val fingerprintLayoutParams =
363             FrameLayout.LayoutParams(sensorBounds.width(), sensorBounds.height())
364         fingerprintLayoutParams.setMarginsRelative(
365             sensorBounds.left,
366             sensorBounds.top,
367             sensorBounds.right,
368             sensorBounds.bottom
369         )
370         val finger =
371             LayoutInflater.from(context)
372                 .inflate(
373                     R.layout.udfps_keyguard_preview,
374                     parentView,
375                     false,
376                 ) as View
377         parentView.addView(finger, fingerprintLayoutParams)
378     }
379 
380     private fun setUpClock(parentView: ViewGroup) {
381         largeClockHostView = createLargeClockHostView()
382         largeClockHostView.isInvisible = true
383         parentView.addView(largeClockHostView)
384 
385         smallClockHostView = createSmallClockHostView(parentView.resources)
386         smallClockHostView.isInvisible = true
387         parentView.addView(smallClockHostView)
388 
389         // TODO (b/283465254): Move the listeners to KeyguardClockRepository
390         val clockChangeListener =
391             object : ClockRegistry.ClockChangeListener {
392                 override fun onCurrentClockChanged() {
393                     onClockChanged()
394                 }
395             }
396         clockRegistry.registerClockChangeListener(clockChangeListener)
397         disposables.add(
398             DisposableHandle { clockRegistry.unregisterClockChangeListener(clockChangeListener) }
399         )
400 
401         clockController.registerListeners(parentView)
402         disposables.add(DisposableHandle { clockController.unregisterListeners() })
403 
404         val receiver =
405             object : BroadcastReceiver() {
406                 override fun onReceive(context: Context?, intent: Intent?) {
407                     clockController.clock?.run {
408                         smallClock.events.onTimeTick()
409                         largeClock.events.onTimeTick()
410                     }
411                 }
412             }
413         broadcastDispatcher.registerReceiver(
414             receiver,
415             IntentFilter().apply {
416                 addAction(Intent.ACTION_TIME_TICK)
417                 addAction(Intent.ACTION_TIME_CHANGED)
418             },
419         )
420         disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
421 
422         val layoutChangeListener =
423             View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
424                 if (clockController.clock !is DefaultClockController) {
425                     clockController.clock
426                         ?.largeClock
427                         ?.events
428                         ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
429                     clockController.clock
430                         ?.smallClock
431                         ?.events
432                         ?.onTargetRegionChanged(KeyguardClockSwitch.getSmallClockRegion(parentView))
433                 }
434             }
435         parentView.addOnLayoutChangeListener(layoutChangeListener)
436         disposables.add(
437             DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
438         )
439 
440         onClockChanged()
441     }
442 
443     private fun createLargeClockHostView(): FrameLayout {
444         val hostView = FrameLayout(context)
445         hostView.layoutParams =
446             FrameLayout.LayoutParams(
447                 FrameLayout.LayoutParams.MATCH_PARENT,
448                 FrameLayout.LayoutParams.MATCH_PARENT,
449             )
450         return hostView
451     }
452 
453     private fun createSmallClockHostView(resources: Resources): FrameLayout {
454         val hostView = FrameLayout(context)
455         val layoutParams =
456             FrameLayout.LayoutParams(
457                 FrameLayout.LayoutParams.WRAP_CONTENT,
458                 resources.getDimensionPixelSize(R.dimen.small_clock_height)
459             )
460         layoutParams.topMargin =
461             KeyguardPreviewSmartspaceViewModel.getStatusBarHeight(resources) +
462                 resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
463         hostView.layoutParams = layoutParams
464 
465         hostView.setPaddingRelative(
466             resources.getDimensionPixelSize(R.dimen.clock_padding_start),
467             0,
468             0,
469             0
470         )
471         hostView.clipChildren = false
472         return hostView
473     }
474 
475     private fun onClockChanged() {
476         val clock = clockRegistry.createCurrentClock()
477         clockController.clock = clock
478 
479         if (clockRegistry.seedColor == null) {
480             // Seed color null means users do override any color on the clock. The default color
481             // will need to use wallpaper's extracted color and consider if the wallpaper's color
482             // is dark or a light.
483             // TODO(b/277832214) we can potentially simplify this code by checking for
484             // wallpaperColors being null in the if clause above and removing the many ?.
485             val wallpaperColorScheme =
486                 wallpaperColors?.let { ColorScheme(it, /* darkTheme= */ false) }
487             val lightClockColor = wallpaperColorScheme?.accent1?.s100
488             val darkClockColor = wallpaperColorScheme?.accent2?.s600
489             /** Note that when [wallpaperColors] is null, isWallpaperDark is true. */
490             val isWallpaperDark: Boolean =
491                 (wallpaperColors?.colorHints?.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
492             clock.events.onSeedColorChanged(
493                 if (isWallpaperDark) lightClockColor else darkClockColor
494             )
495         }
496 
497         updateLargeClock(clock)
498         updateSmallClock(clock)
499     }
500 
501     private fun updateLargeClock(clock: ClockController) {
502         clock.largeClock.events.onTargetRegionChanged(
503             KeyguardClockSwitch.getLargeClockRegion(largeClockHostView)
504         )
505         if (shouldHighlightSelectedAffordance) {
506             clock.largeClock.view.alpha = DIM_ALPHA
507         }
508         largeClockHostView.removeAllViews()
509         largeClockHostView.addView(clock.largeClock.view)
510     }
511 
512     private fun updateSmallClock(clock: ClockController) {
513         clock.smallClock.events.onTargetRegionChanged(
514             KeyguardClockSwitch.getSmallClockRegion(smallClockHostView)
515         )
516         if (shouldHighlightSelectedAffordance) {
517             clock.smallClock.view.alpha = DIM_ALPHA
518         }
519         smallClockHostView.removeAllViews()
520         smallClockHostView.addView(clock.smallClock.view)
521     }
522 
523     companion object {
524         private const val KEY_HOST_TOKEN = "host_token"
525         private const val KEY_VIEW_WIDTH = "width"
526         private const val KEY_VIEW_HEIGHT = "height"
527         private const val KEY_DISPLAY_ID = "display_id"
528         private const val KEY_COLORS = "wallpaper_colors"
529 
530         private const val DIM_ALPHA = 0.3f
531     }
532 }
533