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