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