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