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.os.Bundle 21 import android.os.Handler 22 import android.os.IBinder 23 import android.os.Message 24 import android.os.Messenger 25 import android.util.ArrayMap 26 import android.util.Log 27 import androidx.annotation.VisibleForTesting 28 import com.android.app.tracing.coroutines.launchTraced as launch 29 import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking 30 import com.android.systemui.dagger.SysUISingleton 31 import com.android.systemui.dagger.qualifiers.Application 32 import com.android.systemui.dagger.qualifiers.Background 33 import com.android.systemui.dagger.qualifiers.Main 34 import com.android.systemui.keyguard.shared.model.ClockSizeSetting 35 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START 36 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.CLOCK_SIZE_DYNAMIC 37 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.CLOCK_SIZE_SMALL 38 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_CLOCK_SIZE 39 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE 40 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID 41 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_QUICK_AFFORDANCE_ID 42 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_SLOT_ID 43 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_DEFAULT_PREVIEW 44 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE 45 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_PREVIEW_CLOCK_SIZE 46 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED 47 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED 48 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES 49 import com.android.systemui.util.kotlin.logD 50 import javax.inject.Inject 51 import kotlinx.coroutines.CoroutineDispatcher 52 import kotlinx.coroutines.CoroutineScope 53 54 @SysUISingleton 55 class KeyguardRemotePreviewManager 56 @Inject 57 constructor( 58 private val previewRendererFactory: KeyguardPreviewRendererFactory, 59 @Application private val applicationScope: CoroutineScope, 60 @Main private val mainDispatcher: CoroutineDispatcher, 61 @Background private val backgroundHandler: Handler, 62 ) { 63 private val activePreviews: ArrayMap<Pair<IBinder?, Int>, PreviewLifecycleObserver> = 64 ArrayMap<Pair<IBinder?, Int>, PreviewLifecycleObserver>() 65 66 fun preview(request: Bundle?): Bundle? { 67 if (request == null) { 68 return null 69 } 70 71 var observer: PreviewLifecycleObserver? = null 72 return try { 73 val renderer = 74 runBlocking("$TAG#previewRendererFactory.create", mainDispatcher) { 75 previewRendererFactory.create(request) 76 } 77 78 observer = 79 PreviewLifecycleObserver( 80 applicationScope, 81 mainDispatcher, 82 renderer, 83 ::destroyObserver, 84 ) 85 86 logD(TAG) { "Created observer $observer" } 87 88 // Destroy any previous renderer associated with this token. 89 activePreviews[renderer.id]?.let { destroyObserver(it) } 90 activePreviews[renderer.id] = observer 91 renderer.render() 92 renderer.hostToken?.linkToDeath(observer, 0) 93 val result = Bundle() 94 result.putParcelable(KEY_PREVIEW_SURFACE_PACKAGE, renderer.surfacePackage) 95 val messenger = Messenger(Handler(backgroundHandler.looper, observer)) 96 // NOTE: The process on the other side can retain messenger indefinitely. 97 // (e.g. GC might not trigger and cleanup the reference) 98 val msg = Message.obtain() 99 msg.replyTo = messenger 100 result.putParcelable(KEY_PREVIEW_CALLBACK, msg) 101 result 102 } catch (e: Exception) { 103 Log.e(TAG, "Unable to generate preview", e) 104 observer?.let { destroyObserver(it) } 105 null 106 } 107 } 108 109 private fun destroyObserver(observer: PreviewLifecycleObserver) { 110 observer.onDestroy()?.let { identifier -> 111 if (activePreviews[identifier] === observer) { 112 activePreviews.remove(identifier) 113 } 114 } 115 } 116 117 companion object { 118 internal const val TAG = "KeyguardRemotePreviewManager" 119 @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package" 120 @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback" 121 } 122 } 123 124 /** 125 * Handles messages from the other process and handles cleanup. 126 * 127 * NOTE: The other process might hold on to reference of this class indefinitely. It's entirely 128 * possible that GC won't trigger and we'll leak this for all times even if [onDestroy] was called. 129 * This helps make sure no non-Singleton objects are retained beyond destruction to prevent leaks. 130 */ 131 @VisibleForTesting(VisibleForTesting.PRIVATE) 132 class PreviewLifecycleObserver( 133 private val scope: CoroutineScope, 134 private val mainDispatcher: CoroutineDispatcher, 135 renderer: KeyguardPreviewRenderer, 136 onDestroy: (PreviewLifecycleObserver) -> Unit, 137 ) : Handler.Callback, IBinder.DeathRecipient { 138 139 private var isDestroyedOrDestroying = false 140 // These two are null after destruction 141 @VisibleForTesting var renderer: KeyguardPreviewRenderer? 142 @VisibleForTesting var onDestroy: ((PreviewLifecycleObserver) -> Unit)? 143 144 init { 145 this.renderer = renderer 146 this.onDestroy = onDestroy 147 } 148 handleMessagenull149 override fun handleMessage(message: Message): Boolean { 150 if (isDestroyedOrDestroying) { 151 return true 152 } 153 154 if (renderer == null || onDestroy == null) { 155 Log.wtf(TAG, "Renderer/onDestroy should not be null.") 156 return true 157 } 158 159 when (message.what) { 160 MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES -> { 161 checkNotNull(renderer) 162 .onStartCustomizingQuickAffordances( 163 initiallySelectedSlotId = 164 message.data.getString(KEY_INITIALLY_SELECTED_SLOT_ID) 165 ?: SLOT_ID_BOTTOM_START 166 ) 167 } 168 MESSAGE_ID_SLOT_SELECTED -> { 169 message.data.getString(KEY_SLOT_ID)?.let { slotId -> 170 checkNotNull(renderer).onSlotSelected(slotId = slotId) 171 } 172 } 173 MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED -> { 174 val slotId = message.data.getString(KEY_SLOT_ID) 175 val quickAffordanceId = message.data.getString(KEY_QUICK_AFFORDANCE_ID) 176 if (slotId != null && quickAffordanceId != null) { 177 checkNotNull(renderer) 178 .onPreviewQuickAffordanceSelected( 179 slotId = slotId, 180 quickAffordanceId = quickAffordanceId, 181 ) 182 } 183 } 184 MESSAGE_ID_DEFAULT_PREVIEW -> { 185 checkNotNull(renderer).onDefaultPreview() 186 } 187 MESSAGE_ID_HIDE_SMART_SPACE -> { 188 checkNotNull(renderer).hideSmartspace(message.data.getBoolean(KEY_HIDE_SMART_SPACE)) 189 } 190 MESSAGE_ID_PREVIEW_CLOCK_SIZE -> { 191 message.data 192 .getString(KEY_CLOCK_SIZE) 193 ?.let { 194 when (it) { 195 CLOCK_SIZE_DYNAMIC -> ClockSizeSetting.DYNAMIC 196 CLOCK_SIZE_SMALL -> ClockSizeSetting.SMALL 197 else -> null 198 } 199 } 200 ?.let { checkNotNull(renderer).onClockSizeSelected(it) } 201 } 202 else -> checkNotNull(onDestroy).invoke(this) 203 } 204 205 return true 206 } 207 binderDiednull208 override fun binderDied() { 209 onDestroy?.invoke(this) 210 } 211 onDestroynull212 fun onDestroy(): Pair<IBinder?, Int>? { 213 if (isDestroyedOrDestroying) { 214 return null 215 } 216 217 logD(TAG) { "Destroying $this" } 218 219 isDestroyedOrDestroying = true 220 return renderer?.let { rendererToDestroy -> 221 this.renderer = null 222 this.onDestroy = null 223 val hostToken = rendererToDestroy.hostToken 224 hostToken?.unlinkToDeath(this, 0) 225 scope.launch(context = mainDispatcher) { rendererToDestroy.destroy() } 226 rendererToDestroy.id 227 } 228 } 229 230 companion object { 231 private const val TAG = "KeyguardRemotePreviewManager" 232 } 233 } 234