• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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