1 /*
<lambda>null2  * 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.graphics.Rect
26 import android.hardware.display.DisplayManager
27 import android.os.Bundle
28 import android.os.Handler
29 import android.os.IBinder
30 import android.provider.Settings
31 import android.util.Log
32 import android.view.ContextThemeWrapper
33 import android.view.Display
34 import android.view.Display.DEFAULT_DISPLAY
35 import android.view.DisplayInfo
36 import android.view.LayoutInflater
37 import android.view.SurfaceControlViewHost
38 import android.view.View
39 import android.view.ViewGroup
40 import android.view.WindowManager
41 import android.widget.FrameLayout
42 import android.widget.TextView
43 import android.window.InputTransferToken
44 import androidx.constraintlayout.widget.ConstraintLayout
45 import androidx.constraintlayout.widget.ConstraintSet
46 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
47 import androidx.constraintlayout.widget.ConstraintSet.START
48 import androidx.constraintlayout.widget.ConstraintSet.TOP
49 import androidx.core.view.isInvisible
50 import com.android.internal.policy.SystemBarUtils
51 import com.android.keyguard.ClockEventController
52 import com.android.keyguard.KeyguardClockSwitch
53 import com.android.systemui.animation.view.LaunchableImageView
54 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
55 import com.android.systemui.broadcast.BroadcastDispatcher
56 import com.android.systemui.common.ui.ConfigurationState
57 import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder
58 import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel
59 import com.android.systemui.dagger.qualifiers.Application
60 import com.android.systemui.dagger.qualifiers.Background
61 import com.android.systemui.dagger.qualifiers.Main
62 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
63 import com.android.systemui.keyguard.MigrateClocksToBlueprint
64 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
65 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
66 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
67 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
68 import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
69 import com.android.systemui.keyguard.ui.view.KeyguardRootView
70 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
71 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
72 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
73 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
74 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
75 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
76 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
77 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
78 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
79 import com.android.systemui.monet.ColorScheme
80 import com.android.systemui.monet.Style
81 import com.android.systemui.plugins.FalsingManager
82 import com.android.systemui.plugins.clocks.ClockController
83 import com.android.systemui.res.R
84 import com.android.systemui.shade.domain.interactor.ShadeInteractor
85 import com.android.systemui.shared.clocks.ClockRegistry
86 import com.android.systemui.shared.clocks.DefaultClockController
87 import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
88 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
89 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
90 import com.android.systemui.statusbar.KeyguardIndicationController
91 import com.android.systemui.statusbar.VibratorHelper
92 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
93 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
94 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
95 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
96 import com.android.systemui.util.kotlin.DisposableHandles
97 import com.android.systemui.util.settings.SecureSettings
98 import dagger.assisted.Assisted
99 import dagger.assisted.AssistedInject
100 import kotlinx.coroutines.CoroutineDispatcher
101 import kotlinx.coroutines.CoroutineScope
102 import kotlinx.coroutines.DisposableHandle
103 import kotlinx.coroutines.ExperimentalCoroutinesApi
104 import kotlinx.coroutines.Job
105 import kotlinx.coroutines.cancel
106 import kotlinx.coroutines.flow.flowOf
107 import kotlinx.coroutines.launch
108 import kotlinx.coroutines.runBlocking
109 import kotlinx.coroutines.withContext
110 import org.json.JSONException
111 import org.json.JSONObject
112 
113 /** Renders the preview of the lock screen. */
114 class KeyguardPreviewRenderer
115 @OptIn(ExperimentalCoroutinesApi::class)
116 @AssistedInject
117 constructor(
118     @Application private val context: Context,
119     @Application applicationScope: CoroutineScope,
120     @Main private val mainDispatcher: CoroutineDispatcher,
121     @Main private val mainHandler: Handler,
122     @Background private val backgroundDispatcher: CoroutineDispatcher,
123     private val clockViewModel: KeyguardPreviewClockViewModel,
124     private val smartspaceViewModel: KeyguardPreviewSmartspaceViewModel,
125     private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
126     private val quickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
127     displayManager: DisplayManager,
128     private val windowManager: WindowManager,
129     private val configuration: ConfigurationState,
130     private val clockController: ClockEventController,
131     private val clockRegistry: ClockRegistry,
132     private val broadcastDispatcher: BroadcastDispatcher,
133     private val lockscreenSmartspaceController: LockscreenSmartspaceController,
134     private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
135     private val falsingManager: FalsingManager,
136     private val vibratorHelper: VibratorHelper,
137     private val indicationController: KeyguardIndicationController,
138     private val keyguardRootViewModel: KeyguardRootViewModel,
139     private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
140     @Assisted bundle: Bundle,
141     private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
142     private val chipbarCoordinator: ChipbarCoordinator,
143     private val screenOffAnimationController: ScreenOffAnimationController,
144     private val shadeInteractor: ShadeInteractor,
145     private val secureSettings: SecureSettings,
146     private val communalTutorialViewModel: CommunalTutorialIndicatorViewModel,
147     private val defaultShortcutsSection: DefaultShortcutsSection,
148     private val keyguardClockInteractor: KeyguardClockInteractor,
149     private val keyguardClockViewModel: KeyguardClockViewModel,
150 ) {
151     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
152     private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
153     private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
154     private val shouldHighlightSelectedAffordance: Boolean =
155         bundle.getBoolean(
156             KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
157             false,
158         )
159 
160     private val displayId = bundle.getInt(KEY_DISPLAY_ID, DEFAULT_DISPLAY)
161     private val display: Display? = displayManager.getDisplay(displayId)
162     /**
163      * Returns a key that should make the KeyguardPreviewRenderer unique and if two of them have the
164      * same key they will be treated as the same KeyguardPreviewRenderer. Primary this is used to
165      * prevent memory leaks by allowing removal of the old KeyguardPreviewRenderer.
166      */
167     val id = Pair(hostToken, displayId)
168 
169     /** [shouldHideClock] here means that we never create and bind the clock views */
170     private val shouldHideClock: Boolean =
171         bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
172     private val wallpaperColors: WallpaperColors? = bundle.getParcelable(KEY_COLORS)
173 
174     private var host: SurfaceControlViewHost
175 
176     val surfacePackage: SurfaceControlViewHost.SurfacePackage
177         get() = checkNotNull(host.surfacePackage)
178 
179     private lateinit var largeClockHostView: FrameLayout
180     private lateinit var smallClockHostView: FrameLayout
181     private var smartSpaceView: View? = null
182 
183     private val disposables = DisposableHandles()
184     private var isDestroyed = false
185 
186     private val shortcutsBindings = mutableSetOf<KeyguardQuickAffordanceViewBinder.Binding>()
187 
188     private val coroutineScope: CoroutineScope
189     private var themeStyle: Style? = null
190 
191     init {
192         coroutineScope = CoroutineScope(applicationScope.coroutineContext + Job())
193         disposables += DisposableHandle { coroutineScope.cancel() }
194 
195         if (KeyguardBottomAreaRefactor.isEnabled) {
196             quickAffordancesCombinedViewModel.enablePreviewMode(
197                 initiallySelectedSlotId =
198                     bundle.getString(
199                         KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
200                     )
201                         ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
202                 shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
203             )
204         } else {
205             bottomAreaViewModel.enablePreviewMode(
206                 initiallySelectedSlotId =
207                     bundle.getString(
208                         KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
209                     ),
210                 shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
211             )
212         }
213         if (MigrateClocksToBlueprint.isEnabled) {
214             clockViewModel.shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance
215         }
216         runBlocking(mainDispatcher) {
217             host =
218                 SurfaceControlViewHost(
219                     context,
220                     displayManager.getDisplay(DEFAULT_DISPLAY),
221                     if (hostToken == null) null else InputTransferToken(hostToken),
222                     "KeyguardPreviewRenderer"
223                 )
224             disposables += DisposableHandle { host.release() }
225         }
226     }
227 
228     fun render() {
229         mainHandler.post {
230             val previewContext =
231                 display?.let {
232                     ContextThemeWrapper(context.createDisplayContext(it), context.getTheme())
233                 }
234                     ?: context
235 
236             val rootView = FrameLayout(previewContext)
237 
238             setupKeyguardRootView(previewContext, rootView)
239 
240             if (!KeyguardBottomAreaRefactor.isEnabled) {
241                 setUpBottomArea(rootView)
242             }
243 
244             var displayInfo: DisplayInfo? = null
245             display?.let {
246                 displayInfo = DisplayInfo()
247                 it.getDisplayInfo(displayInfo)
248             }
249             rootView.measure(
250                 View.MeasureSpec.makeMeasureSpec(
251                     displayInfo?.logicalWidth ?: windowManager.currentWindowMetrics.bounds.width(),
252                     View.MeasureSpec.EXACTLY
253                 ),
254                 View.MeasureSpec.makeMeasureSpec(
255                     displayInfo?.logicalHeight
256                         ?: windowManager.currentWindowMetrics.bounds.height(),
257                     View.MeasureSpec.EXACTLY
258                 ),
259             )
260             rootView.layout(0, 0, rootView.measuredWidth, rootView.measuredHeight)
261 
262             // This aspect scales the view to fit in the surface and centers it
263             val scale: Float =
264                 (width / rootView.measuredWidth.toFloat()).coerceAtMost(
265                     height / rootView.measuredHeight.toFloat()
266                 )
267 
268             rootView.scaleX = scale
269             rootView.scaleY = scale
270             rootView.pivotX = 0f
271             rootView.pivotY = 0f
272             rootView.translationX = (width - scale * rootView.width) / 2
273             rootView.translationY = (height - scale * rootView.height) / 2
274 
275             if (isDestroyed) {
276                 return@post
277             }
278 
279             host.setView(rootView, rootView.measuredWidth, rootView.measuredHeight)
280         }
281     }
282 
283     fun onSlotSelected(slotId: String) {
284         if (KeyguardBottomAreaRefactor.isEnabled) {
285             quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
286         } else {
287             bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
288         }
289     }
290 
291     fun destroy() {
292         isDestroyed = true
293         lockscreenSmartspaceController.disconnect()
294         disposables.dispose()
295         if (KeyguardBottomAreaRefactor.isEnabled) {
296             shortcutsBindings.forEach { it.destroy() }
297         }
298     }
299 
300     /**
301      * Hides or shows smartspace
302      *
303      * @param hide TRUE hides smartspace, FALSE shows smartspace
304      */
305     fun hideSmartspace(hide: Boolean) {
306         mainHandler.post { smartSpaceView?.visibility = if (hide) View.INVISIBLE else View.VISIBLE }
307     }
308 
309     /**
310      * This sets up and shows a non-interactive smart space
311      *
312      * The top padding is as follows: Status bar height + clock top margin + keyguard smart space
313      * top offset
314      *
315      * The start padding is as follows: Clock padding start + Below clock padding start
316      *
317      * The end padding is as follows: Below clock padding end
318      */
319     private fun setUpSmartspace(previewContext: Context, parentView: ViewGroup) {
320         if (
321             !lockscreenSmartspaceController.isEnabled() ||
322                 !lockscreenSmartspaceController.isDateWeatherDecoupled()
323         ) {
324             return
325         }
326 
327         if (smartSpaceView != null) {
328             parentView.removeView(smartSpaceView)
329         }
330 
331         smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
332 
333         val topPadding: Int =
334             smartspaceViewModel.getLargeClockSmartspaceTopPadding(
335                 previewInSplitShade(),
336                 previewContext,
337             )
338         val startPadding: Int = smartspaceViewModel.getSmartspaceStartPadding(previewContext)
339         val endPadding: Int = smartspaceViewModel.getSmartspaceEndPadding(previewContext)
340 
341         smartSpaceView?.let {
342             it.setPaddingRelative(startPadding, topPadding, endPadding, 0)
343             it.isClickable = false
344             it.isInvisible = true
345             parentView.addView(
346                 it,
347                 FrameLayout.LayoutParams(
348                     FrameLayout.LayoutParams.MATCH_PARENT,
349                     FrameLayout.LayoutParams.WRAP_CONTENT,
350                 ),
351             )
352         }
353 
354         smartSpaceView?.alpha = if (shouldHighlightSelectedAffordance) DIM_ALPHA else 1.0f
355     }
356 
357     @Deprecated("Deprecated as part of b/278057014")
358     private fun setUpBottomArea(parentView: ViewGroup) {
359         val bottomAreaView =
360             LayoutInflater.from(context)
361                 .inflate(
362                     R.layout.keyguard_bottom_area,
363                     parentView,
364                     false,
365                 ) as KeyguardBottomAreaView
366         bottomAreaView.init(
367             viewModel = bottomAreaViewModel,
368         )
369         parentView.addView(
370             bottomAreaView,
371             FrameLayout.LayoutParams(
372                 FrameLayout.LayoutParams.MATCH_PARENT,
373                 FrameLayout.LayoutParams.MATCH_PARENT,
374             ),
375         )
376     }
377 
378     @OptIn(ExperimentalCoroutinesApi::class)
379     private fun setupKeyguardRootView(previewContext: Context, rootView: FrameLayout) {
380         val keyguardRootView = KeyguardRootView(previewContext, null)
381         if (!KeyguardBottomAreaRefactor.isEnabled) {
382             disposables +=
383                 KeyguardRootViewBinder.bind(
384                     keyguardRootView,
385                     keyguardRootViewModel,
386                     keyguardBlueprintViewModel,
387                     configuration,
388                     occludingAppDeviceEntryMessageViewModel,
389                     chipbarCoordinator,
390                     screenOffAnimationController,
391                     shadeInteractor,
392                     keyguardClockInteractor,
393                     keyguardClockViewModel,
394                     null, // jank monitor not required for preview mode
395                     null, // device entry haptics not required preview mode
396                     null, // device entry haptics not required for preview mode
397                     null, // falsing manager not required for preview mode
398                     null, // keyguard view mediator is not required for preview mode
399                 )
400         }
401         rootView.addView(
402             keyguardRootView,
403             FrameLayout.LayoutParams(
404                 FrameLayout.LayoutParams.MATCH_PARENT,
405                 FrameLayout.LayoutParams.MATCH_PARENT,
406             ),
407         )
408 
409         setUpUdfps(
410             previewContext,
411             if (MigrateClocksToBlueprint.isEnabled) keyguardRootView else rootView
412         )
413 
414         if (KeyguardBottomAreaRefactor.isEnabled) {
415             setupShortcuts(keyguardRootView)
416         }
417 
418         if (!shouldHideClock) {
419             setUpClock(previewContext, rootView)
420             if (MigrateClocksToBlueprint.isEnabled) {
421                 KeyguardPreviewClockViewBinder.bind(
422                     previewContext,
423                     keyguardRootView,
424                     clockViewModel,
425                     clockRegistry,
426                     ::updateClockAppearance,
427                 )
428             } else {
429                 KeyguardPreviewClockViewBinder.bind(
430                     largeClockHostView,
431                     smallClockHostView,
432                     clockViewModel,
433                 )
434             }
435         }
436 
437         setUpSmartspace(previewContext, rootView)
438 
439         smartSpaceView?.let {
440             KeyguardPreviewSmartspaceViewBinder.bind(
441                 previewContext,
442                 it,
443                 previewInSplitShade(),
444                 smartspaceViewModel
445             )
446         }
447         setupCommunalTutorialIndicator(keyguardRootView)
448     }
449 
450     private fun setupShortcuts(keyguardRootView: ConstraintLayout) {
451         // Add shortcuts
452         val cs = ConstraintSet()
453         cs.clone(keyguardRootView)
454         defaultShortcutsSection.addViews(keyguardRootView)
455         defaultShortcutsSection.applyConstraints(cs)
456         cs.applyTo(keyguardRootView)
457 
458         keyguardRootView.findViewById<LaunchableImageView?>(R.id.start_button)?.let { imageView ->
459             shortcutsBindings.add(
460                 KeyguardQuickAffordanceViewBinder.bind(
461                     view = imageView,
462                     viewModel = quickAffordancesCombinedViewModel.startButton,
463                     alpha = flowOf(1f),
464                     falsingManager = falsingManager,
465                     vibratorHelper = vibratorHelper,
466                 ) { message ->
467                     indicationController.showTransientIndication(message)
468                 }
469             )
470         }
471 
472         keyguardRootView.findViewById<LaunchableImageView?>(R.id.end_button)?.let { imageView ->
473             shortcutsBindings.add(
474                 KeyguardQuickAffordanceViewBinder.bind(
475                     view = imageView,
476                     viewModel = quickAffordancesCombinedViewModel.endButton,
477                     alpha = flowOf(1f),
478                     falsingManager = falsingManager,
479                     vibratorHelper = vibratorHelper,
480                 ) { message ->
481                     indicationController.showTransientIndication(message)
482                 }
483             )
484         }
485     }
486 
487     private fun setUpUdfps(previewContext: Context, parentView: ViewGroup) {
488         val sensorBounds = udfpsOverlayInteractor.udfpsOverlayParams.value.sensorBounds
489 
490         // If sensorBounds are default rect, then there is no UDFPS
491         if (sensorBounds == Rect()) {
492             return
493         }
494 
495         val finger =
496             LayoutInflater.from(previewContext)
497                 .inflate(
498                     R.layout.udfps_keyguard_preview,
499                     parentView,
500                     false,
501                 ) as View
502 
503         // Place the UDFPS view in the proper sensor location
504         if (MigrateClocksToBlueprint.isEnabled) {
505             finger.id = R.id.lock_icon_view
506             parentView.addView(finger)
507             val cs = ConstraintSet()
508             cs.clone(parentView as ConstraintLayout)
509             cs.apply {
510                 constrainWidth(R.id.lock_icon_view, sensorBounds.width())
511                 constrainHeight(R.id.lock_icon_view, sensorBounds.height())
512                 connect(R.id.lock_icon_view, TOP, PARENT_ID, TOP, sensorBounds.top)
513                 connect(R.id.lock_icon_view, START, PARENT_ID, START, sensorBounds.left)
514             }
515             cs.applyTo(parentView)
516         } else {
517             val fingerprintLayoutParams =
518                 FrameLayout.LayoutParams(sensorBounds.width(), sensorBounds.height())
519             fingerprintLayoutParams.setMarginsRelative(
520                 sensorBounds.left,
521                 sensorBounds.top,
522                 sensorBounds.right,
523                 sensorBounds.bottom
524             )
525             parentView.addView(finger, fingerprintLayoutParams)
526         }
527     }
528 
529     private fun setUpClock(previewContext: Context, parentView: ViewGroup) {
530         val resources = parentView.resources
531         if (!MigrateClocksToBlueprint.isEnabled) {
532             largeClockHostView = FrameLayout(previewContext)
533             largeClockHostView.layoutParams =
534                 FrameLayout.LayoutParams(
535                     FrameLayout.LayoutParams.MATCH_PARENT,
536                     FrameLayout.LayoutParams.MATCH_PARENT,
537                 )
538             largeClockHostView.isInvisible = true
539             parentView.addView(largeClockHostView)
540 
541             smallClockHostView = FrameLayout(previewContext)
542             val layoutParams =
543                 FrameLayout.LayoutParams(
544                     FrameLayout.LayoutParams.WRAP_CONTENT,
545                     resources.getDimensionPixelSize(
546                         com.android.systemui.customization.R.dimen.small_clock_height
547                     )
548                 )
549             layoutParams.topMargin =
550                 SystemBarUtils.getStatusBarHeight(previewContext) +
551                     resources.getDimensionPixelSize(
552                         com.android.systemui.customization.R.dimen.small_clock_padding_top
553                     )
554             smallClockHostView.layoutParams = layoutParams
555             smallClockHostView.setPaddingRelative(
556                 /* start = */ resources.getDimensionPixelSize(
557                     com.android.systemui.customization.R.dimen.clock_padding_start
558                 ),
559                 /* top = */ 0,
560                 /* end = */ 0,
561                 /* bottom = */ 0
562             )
563             smallClockHostView.clipChildren = false
564             parentView.addView(smallClockHostView)
565             smallClockHostView.isInvisible = true
566         }
567 
568         // TODO (b/283465254): Move the listeners to KeyguardClockRepository
569         if (!MigrateClocksToBlueprint.isEnabled) {
570             val clockChangeListener =
571                 object : ClockRegistry.ClockChangeListener {
572                     override fun onCurrentClockChanged() {
573                         onClockChanged()
574                     }
575                 }
576             clockRegistry.registerClockChangeListener(clockChangeListener)
577             disposables += DisposableHandle {
578                 clockRegistry.unregisterClockChangeListener(clockChangeListener)
579             }
580 
581             clockController.registerListeners(parentView)
582             disposables += DisposableHandle { clockController.unregisterListeners() }
583         }
584 
585         val receiver =
586             object : BroadcastReceiver() {
587                 override fun onReceive(context: Context?, intent: Intent?) {
588                     clockController.clock?.run {
589                         smallClock.events.onTimeTick()
590                         largeClock.events.onTimeTick()
591                     }
592                 }
593             }
594         broadcastDispatcher.registerReceiver(
595             receiver,
596             IntentFilter().apply {
597                 addAction(Intent.ACTION_TIME_TICK)
598                 addAction(Intent.ACTION_TIME_CHANGED)
599             },
600         )
601         disposables += DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) }
602 
603         if (!MigrateClocksToBlueprint.isEnabled) {
604             val layoutChangeListener =
605                 View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
606                     if (clockController.clock !is DefaultClockController) {
607                         clockController.clock
608                             ?.largeClock
609                             ?.events
610                             ?.onTargetRegionChanged(
611                                 KeyguardClockSwitch.getLargeClockRegion(parentView)
612                             )
613                         clockController.clock
614                             ?.smallClock
615                             ?.events
616                             ?.onTargetRegionChanged(
617                                 KeyguardClockSwitch.getSmallClockRegion(parentView)
618                             )
619                     }
620                 }
621             parentView.addOnLayoutChangeListener(layoutChangeListener)
622             disposables += DisposableHandle {
623                 parentView.removeOnLayoutChangeListener(layoutChangeListener)
624             }
625         }
626 
627         onClockChanged()
628     }
629 
630     private suspend fun updateClockAppearance(clock: ClockController) {
631         if (!MigrateClocksToBlueprint.isEnabled) {
632             clockController.clock = clock
633         }
634         val colors = wallpaperColors
635         if (clockRegistry.seedColor == null && colors != null) {
636             // Seed color null means users do not override any color on the clock. The default
637             // color will need to use wallpaper's extracted color and consider if the
638             // wallpaper's color is dark or light.
639             val style = themeStyle ?: fetchThemeStyleFromSetting().also { themeStyle = it }
640             val wallpaperColorScheme = ColorScheme(colors, false, style)
641             val lightClockColor = wallpaperColorScheme.accent1.s100
642             val darkClockColor = wallpaperColorScheme.accent2.s600
643 
644             // Note that when [wallpaperColors] is null, isWallpaperDark is true.
645             val isWallpaperDark: Boolean =
646                 (colors.colorHints.and(WallpaperColors.HINT_SUPPORTS_DARK_TEXT)) == 0
647             clock.events.onSeedColorChanged(
648                 if (isWallpaperDark) lightClockColor else darkClockColor
649             )
650         }
651         // In clock preview, we should have a seed color for clock
652         // before setting clock to clockEventController to avoid updateColor with seedColor == null
653         if (MigrateClocksToBlueprint.isEnabled) {
654             clockController.clock = clock
655         }
656     }
657     private fun onClockChanged() {
658         if (MigrateClocksToBlueprint.isEnabled) {
659             return
660         }
661         coroutineScope.launch {
662             val clock = clockRegistry.createCurrentClock()
663             clockController.clock = clock
664             updateClockAppearance(clock)
665             updateLargeClock(clock)
666             updateSmallClock(clock)
667         }
668     }
669 
670     private fun setupCommunalTutorialIndicator(keyguardRootView: ConstraintLayout) {
671         keyguardRootView.findViewById<TextView>(R.id.communal_tutorial_indicator)?.let {
672             indicatorView ->
673             CommunalTutorialIndicatorViewBinder.bind(
674                 indicatorView,
675                 communalTutorialViewModel,
676                 isPreviewMode = true,
677             )
678         }
679     }
680 
681     private suspend fun fetchThemeStyleFromSetting(): Style {
682         val overlayPackageJson =
683             withContext(backgroundDispatcher) {
684                 secureSettings.getString(
685                     Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
686                 )
687             }
688         return if (!overlayPackageJson.isNullOrEmpty()) {
689             try {
690                 val jsonObject = JSONObject(overlayPackageJson)
691                 Style.valueOf(jsonObject.getString(OVERLAY_CATEGORY_THEME_STYLE))
692             } catch (e: (JSONException)) {
693                 Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e)
694                 Style.TONAL_SPOT
695             } catch (e: IllegalArgumentException) {
696                 Log.i(TAG, "Failed to parse THEME_CUSTOMIZATION_OVERLAY_PACKAGES.", e)
697                 Style.TONAL_SPOT
698             }
699         } else {
700             Style.TONAL_SPOT
701         }
702     }
703 
704     private fun updateLargeClock(clock: ClockController) {
705         if (MigrateClocksToBlueprint.isEnabled) {
706             return
707         }
708         clock.largeClock.events.onTargetRegionChanged(
709             KeyguardClockSwitch.getLargeClockRegion(largeClockHostView)
710         )
711         if (shouldHighlightSelectedAffordance) {
712             clock.largeClock.view.alpha = DIM_ALPHA
713         }
714         largeClockHostView.removeAllViews()
715         largeClockHostView.addView(clock.largeClock.view)
716     }
717 
718     private fun updateSmallClock(clock: ClockController) {
719         if (MigrateClocksToBlueprint.isEnabled) {
720             return
721         }
722         clock.smallClock.events.onTargetRegionChanged(
723             KeyguardClockSwitch.getSmallClockRegion(smallClockHostView)
724         )
725         if (shouldHighlightSelectedAffordance) {
726             clock.smallClock.view.alpha = DIM_ALPHA
727         }
728         smallClockHostView.removeAllViews()
729         smallClockHostView.addView(clock.smallClock.view)
730     }
731 
732     /*
733      * When multi_crop_preview_ui_flag is on, we can preview portrait in split shadow direction
734      * or vice versa. So we need to decide preview direction by width and height
735      */
736     private fun previewInSplitShade(): Boolean {
737         return width > height
738     }
739 
740     companion object {
741         private const val TAG = "KeyguardPreviewRenderer"
742         private const val OVERLAY_CATEGORY_THEME_STYLE = "android.theme.customization.theme_style"
743         private const val KEY_HOST_TOKEN = "host_token"
744         private const val KEY_VIEW_WIDTH = "width"
745         private const val KEY_VIEW_HEIGHT = "height"
746         private const val KEY_DISPLAY_ID = "display_id"
747         private const val KEY_COLORS = "wallpaper_colors"
748 
749         const val DIM_ALPHA = 0.3f
750     }
751 }
752