• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2017 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 package com.android.systemui.statusbar.notification.row
17 
18 import android.annotation.SuppressLint
19 import android.app.Notification
20 import android.app.Notification.EXTRA_SUMMARIZED_CONTENT
21 import android.app.Notification.MessagingStyle
22 import android.content.Context
23 import android.content.ContextWrapper
24 import android.content.pm.ApplicationInfo
25 import android.content.pm.PackageManager
26 import android.content.res.Resources
27 import android.os.AsyncTask
28 import android.os.Build
29 import android.os.CancellationSignal
30 import android.os.Trace
31 import android.os.UserHandle
32 import android.service.notification.StatusBarNotification
33 import android.util.Log
34 import android.view.NotificationHeaderView
35 import android.view.View
36 import android.view.ViewGroup
37 import android.widget.RemoteViews
38 import android.widget.RemoteViews.InteractionHandler
39 import android.widget.RemoteViews.OnViewAppliedListener
40 import com.android.app.tracing.TraceUtils
41 import com.android.internal.annotations.VisibleForTesting
42 import com.android.internal.widget.ImageMessageConsumer
43 import com.android.systemui.dagger.SysUISingleton
44 import com.android.systemui.dagger.qualifiers.NotifInflation
45 import com.android.systemui.res.R
46 import com.android.systemui.statusbar.InflationTask
47 import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_OTP
48 import com.android.systemui.statusbar.NotificationRemoteInputManager
49 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
50 import com.android.systemui.statusbar.notification.InflationException
51 import com.android.systemui.statusbar.notification.NmSummarizationUiFlag
52 import com.android.systemui.statusbar.notification.collection.NotificationEntry
53 import com.android.systemui.statusbar.notification.logKey
54 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
55 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
56 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
57 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
58 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
59 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
60 import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_SINGLELINE
61 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
62 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
63 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
64 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
65 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC
66 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE
67 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
68 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_GROUP_SUMMARY_HEADER
69 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER
70 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback
71 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
72 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation
73 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
74 import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel
75 import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction
76 import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
77 import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
78 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
79 import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
80 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
81 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
82 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
83 import com.android.systemui.statusbar.policy.InflatedSmartReplyState
84 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder
85 import com.android.systemui.statusbar.policy.SmartReplyStateInflater
86 import com.android.systemui.util.Assert
87 import java.util.concurrent.Executor
88 import java.util.function.Consumer
89 import javax.inject.Inject
90 
91 /**
92  * [NotificationRowContentBinderImpl] binds content to a [ExpandableNotificationRow] by
93  * asynchronously building the content's [RemoteViews] and applying it to the row.
94  */
95 @SysUISingleton
96 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
97 class NotificationRowContentBinderImpl
98 @Inject
99 constructor(
100     private val remoteViewCache: NotifRemoteViewCache,
101     private val remoteInputManager: NotificationRemoteInputManager,
102     private val conversationProcessor: ConversationNotificationProcessor,
103     @NotifInflation private val inflationExecutor: Executor,
104     private val smartReplyStateInflater: SmartReplyStateInflater,
105     private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
106     private val headsUpStyleProvider: HeadsUpStyleProvider,
107     private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
108     private val logger: NotificationRowContentBinderLogger,
109 ) : NotificationRowContentBinder {
110 
111     init {
112         /* check if */ NotificationRowContentBinderRefactor.isUnexpectedlyInLegacyMode()
113     }
114 
115     private var inflateSynchronously = false
116 
117     override fun bindContent(
118         entry: NotificationEntry,
119         row: ExpandableNotificationRow,
120         @InflationFlag contentToBind: Int,
121         bindParams: BindParams,
122         forceInflate: Boolean,
123         callback: InflationCallback?,
124     ) {
125         if (row.isRemoved) {
126             // We don't want to reinflate anything for removed notifications. Otherwise views might
127             // be readded to the stack, leading to leaks. This may happen with low-priority groups
128             // where the removal of already removed children can lead to a reinflation.
129             logger.logNotBindingRowWasRemoved(row.loggingKey)
130             return
131         }
132         logger.logBinding(row.loggingKey, contentToBind)
133         val sbn: StatusBarNotification = entry.sbn
134 
135         // To check if the notification has inline image and preload inline image if necessary.
136         row.imageResolver.preloadImages(sbn.notification)
137         if (forceInflate) {
138             remoteViewCache.clearCache(entry)
139         }
140 
141         // Cancel any pending frees on any view we're trying to bind since we should be bound after.
142         cancelContentViewFrees(row, contentToBind)
143         val task =
144             AsyncInflationTask(
145                 inflationExecutor,
146                 inflateSynchronously,
147                 /* reInflateFlags = */ contentToBind,
148                 remoteViewCache,
149                 entry,
150                 conversationProcessor,
151                 row,
152                 bindParams,
153                 callback,
154                 remoteInputManager.remoteViewsOnClickHandler,
155                 /* isMediaFlagEnabled = */ smartReplyStateInflater,
156                 notifLayoutInflaterFactoryProvider,
157                 headsUpStyleProvider,
158                 promotedNotificationContentExtractor,
159                 logger,
160             )
161         if (inflateSynchronously) {
162             task.onPostExecute(task.doInBackground())
163         } else {
164             task.executeOnExecutor(inflationExecutor)
165         }
166     }
167 
168     @VisibleForTesting
169     fun inflateNotificationViews(
170         entry: NotificationEntry,
171         row: ExpandableNotificationRow,
172         bindParams: BindParams,
173         inflateSynchronously: Boolean,
174         @InflationFlag reInflateFlags: Int,
175         builder: Notification.Builder,
176         packageContext: Context,
177         smartRepliesInflater: SmartReplyStateInflater,
178         promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
179     ): InflationProgress {
180         val systemUIContext = row.context
181         val result =
182             beginInflationAsync(
183                 reInflateFlags = reInflateFlags,
184                 entry = entry,
185                 builder = builder,
186                 bindParams,
187                 systemUiContext = systemUIContext,
188                 packageContext = packageContext,
189                 row = row,
190                 notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
191                 headsUpStyleProvider = headsUpStyleProvider,
192                 conversationProcessor = conversationProcessor,
193                 promotedNotificationContentExtractor = promotedNotificationContentExtractor,
194                 logger = logger,
195             )
196         inflateSmartReplyViews(
197             result,
198             reInflateFlags,
199             entry,
200             systemUIContext,
201             packageContext,
202             row.existingSmartReplyState,
203             smartRepliesInflater,
204             logger,
205         )
206         if (AsyncHybridViewInflation.isEnabled) {
207             result.inflatedSingleLineView =
208                 result.contentModel.singleLineViewModel?.let { viewModel ->
209                     SingleLineViewInflater.inflatePrivateSingleLineView(
210                         viewModel.isConversation(),
211                         reInflateFlags,
212                         entry,
213                         systemUIContext,
214                         logger,
215                     )
216                 }
217         }
218         if (LockscreenOtpRedaction.isEnabled) {
219             result.inflatedPublicSingleLineView =
220                 result.contentModel.publicSingleLineViewModel?.let { viewModel ->
221                     SingleLineViewInflater.inflatePublicSingleLineView(
222                         viewModel.isConversation(),
223                         reInflateFlags,
224                         entry,
225                         systemUIContext,
226                         logger,
227                     )
228                 }
229         }
230         apply(
231             inflationExecutor,
232             inflateSynchronously,
233             bindParams.isMinimized,
234             result,
235             reInflateFlags,
236             remoteViewCache,
237             entry,
238             row,
239             remoteInputManager.remoteViewsOnClickHandler,
240             /* callback= */ null,
241             logger,
242         )
243         return result
244     }
245 
246     override fun cancelBind(entry: NotificationEntry, row: ExpandableNotificationRow): Boolean {
247         val abortedTask: Boolean = entry.abortTask()
248         if (abortedTask) {
249             logger.logCancelBindAbortedTask(row.loggingKey)
250         }
251         return abortedTask
252     }
253 
254     @SuppressLint("WrongConstant")
255     override fun unbindContent(
256         entry: NotificationEntry,
257         row: ExpandableNotificationRow,
258         @InflationFlag contentToUnbind: Int,
259     ) {
260         logger.logUnbinding(row.loggingKey, contentToUnbind)
261         var curFlag = 1
262         var contentLeftToUnbind = contentToUnbind
263         while (contentLeftToUnbind != 0) {
264             if (contentLeftToUnbind and curFlag != 0) {
265                 freeNotificationView(entry, row, curFlag)
266             }
267             contentLeftToUnbind = contentLeftToUnbind and curFlag.inv()
268             curFlag = curFlag shl 1
269         }
270     }
271 
272     /**
273      * Frees the content view associated with the inflation flag as soon as the view is not showing.
274      *
275      * @param inflateFlag the flag corresponding to the content view which should be freed
276      */
277     private fun freeNotificationView(
278         entry: NotificationEntry,
279         row: ExpandableNotificationRow,
280         @InflationFlag inflateFlag: Int,
281     ) {
282         when (inflateFlag) {
283             FLAG_CONTENT_VIEW_CONTRACTED ->
284                 row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_CONTRACTED) {
285                     row.privateLayout.setContractedChild(null)
286                     remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
287                 }
288             FLAG_CONTENT_VIEW_EXPANDED ->
289                 row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_EXPANDED) {
290                     row.privateLayout.setExpandedChild(null)
291                     remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
292                 }
293             FLAG_CONTENT_VIEW_HEADS_UP ->
294                 row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_HEADSUP) {
295                     row.privateLayout.setHeadsUpChild(null)
296                     remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
297                     row.privateLayout.setHeadsUpInflatedSmartReplies(null)
298                 }
299             FLAG_CONTENT_VIEW_PUBLIC ->
300                 row.publicLayout.performWhenContentInactive(VISIBLE_TYPE_CONTRACTED) {
301                     row.publicLayout.setContractedChild(null)
302                     remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)
303                 }
304             FLAG_CONTENT_VIEW_SINGLE_LINE -> {
305                 if (AsyncHybridViewInflation.isEnabled) {
306                     row.privateLayout.performWhenContentInactive(VISIBLE_TYPE_SINGLELINE) {
307                         row.privateLayout.setSingleLineView(null)
308                     }
309                 }
310             }
311             FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE -> {
312                 if (LockscreenOtpRedaction.isEnabled) {
313                     row.publicLayout.performWhenContentInactive(VISIBLE_TYPE_SINGLELINE) {
314                         row.publicLayout.setSingleLineView(null)
315                     }
316                 }
317             }
318             else -> {}
319         }
320     }
321 
322     /**
323      * Cancel any pending content view frees from [.freeNotificationView] for the provided content
324      * views.
325      *
326      * @param row top level notification row containing the content views
327      * @param contentViews content views to cancel pending frees on
328      */
329     private fun cancelContentViewFrees(
330         row: ExpandableNotificationRow,
331         @InflationFlag contentViews: Int,
332     ) {
333         if (contentViews and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
334             row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED)
335         }
336         if (contentViews and FLAG_CONTENT_VIEW_EXPANDED != 0) {
337             row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_EXPANDED)
338         }
339         if (contentViews and FLAG_CONTENT_VIEW_HEADS_UP != 0) {
340             row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_HEADSUP)
341         }
342         if (contentViews and FLAG_CONTENT_VIEW_PUBLIC != 0) {
343             row.publicLayout.removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED)
344         }
345         if (
346             AsyncHybridViewInflation.isEnabled &&
347                 contentViews and FLAG_CONTENT_VIEW_SINGLE_LINE != 0
348         ) {
349             row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE)
350         }
351         if (
352             LockscreenOtpRedaction.isEnabled &&
353                 contentViews and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE != 0
354         ) {
355             row.publicLayout.removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE)
356         }
357     }
358 
359     /**
360      * Sets whether to perform inflation on the same thread as the caller. This method should only
361      * be used in tests, not in production.
362      */
363     @VisibleForTesting
364     override fun setInflateSynchronously(inflateSynchronously: Boolean) {
365         this.inflateSynchronously = inflateSynchronously
366     }
367 
368     class AsyncInflationTask(
369         private val inflationExecutor: Executor,
370         private val inflateSynchronously: Boolean,
371         @get:InflationFlag @get:VisibleForTesting @InflationFlag val reInflateFlags: Int,
372         private val remoteViewCache: NotifRemoteViewCache,
373         private val entry: NotificationEntry,
374         private val conversationProcessor: ConversationNotificationProcessor,
375         private val row: ExpandableNotificationRow,
376         private val bindParams: BindParams,
377         private val callback: InflationCallback?,
378         private val remoteViewClickHandler: InteractionHandler?,
379         private val smartRepliesInflater: SmartReplyStateInflater,
380         private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
381         private val headsUpStyleProvider: HeadsUpStyleProvider,
382         private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
383         private val logger: NotificationRowContentBinderLogger,
384     ) : AsyncTask<Void, Void, Result<InflationProgress>>(), InflationCallback, InflationTask {
385         private val context: Context
386             get() = row.context
387 
388         private var cancellationSignal: CancellationSignal? = null
389 
390         init {
391             entry.setInflationTask(this)
392         }
393 
394         private fun updateApplicationInfo(sbn: StatusBarNotification) {
395             val packageName: String = sbn.packageName
396             val userId: Int = UserHandle.getUserId(sbn.uid)
397             val appInfo: ApplicationInfo
398             try {
399                 // This method has an internal cache, so we don't need to add our own caching here.
400                 appInfo =
401                     context.packageManager.getApplicationInfoAsUser(
402                         packageName,
403                         PackageManager.MATCH_UNINSTALLED_PACKAGES,
404                         userId,
405                     )
406             } catch (e: PackageManager.NameNotFoundException) {
407                 return
408             }
409             Notification.addFieldsFromContext(appInfo, sbn.notification)
410         }
411 
412         override fun onPreExecute() {
413             Trace.beginAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this))
414         }
415 
416         public override fun doInBackground(vararg params: Void): Result<InflationProgress> {
417             return TraceUtils.trace(
418                 "NotificationContentInflater.AsyncInflationTask#doInBackground"
419             ) {
420                 try {
421                     return@trace Result.success(doInBackgroundInternal())
422                 } catch (e: Exception) {
423                     logger.logAsyncTaskException(entry.logKey, "inflating", e)
424                     return@trace Result.failure(e)
425                 }
426             }
427         }
428 
429         private fun doInBackgroundInternal(): InflationProgress {
430             val sbn: StatusBarNotification = entry.sbn
431             // Ensure the ApplicationInfo is updated before a builder is recovered.
432             updateApplicationInfo(sbn)
433             val recoveredBuilder = Notification.Builder.recoverBuilder(context, sbn.notification)
434             var packageContext: Context = sbn.getPackageContext(context)
435             if (recoveredBuilder.usesTemplate()) {
436                 // For all of our templates, we want it to be RTL
437                 packageContext = RtlEnabledContext(packageContext)
438             }
439             val inflationProgress =
440                 beginInflationAsync(
441                     reInflateFlags = reInflateFlags,
442                     entry = entry,
443                     builder = recoveredBuilder,
444                     bindParams = bindParams,
445                     systemUiContext = context,
446                     packageContext = packageContext,
447                     row = row,
448                     notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
449                     headsUpStyleProvider = headsUpStyleProvider,
450                     conversationProcessor = conversationProcessor,
451                     promotedNotificationContentExtractor = promotedNotificationContentExtractor,
452                     logger = logger,
453                 )
454             logger.logAsyncTaskProgress(
455                 row.loggingKey,
456                 "getting existing smart reply state (on wrong thread!)",
457             )
458             val previousSmartReplyState: InflatedSmartReplyState? = row.existingSmartReplyState
459             logger.logAsyncTaskProgress(entry.logKey, "inflating smart reply views")
460             inflateSmartReplyViews(
461                 /* result = */ inflationProgress,
462                 reInflateFlags,
463                 entry,
464                 context,
465                 packageContext,
466                 previousSmartReplyState,
467                 smartRepliesInflater,
468                 logger,
469             )
470             if (AsyncHybridViewInflation.isEnabled) {
471                 logger.logAsyncTaskProgress(entry.logKey, "inflating single line view")
472                 inflationProgress.inflatedSingleLineView =
473                     inflationProgress.contentModel.singleLineViewModel?.let {
474                         SingleLineViewInflater.inflatePrivateSingleLineView(
475                             it.isConversation(),
476                             reInflateFlags,
477                             entry,
478                             context,
479                             logger,
480                         )
481                     }
482             }
483 
484             if (LockscreenOtpRedaction.isEnabled) {
485                 logger.logAsyncTaskProgress(entry.logKey, "inflating public single line view")
486                 inflationProgress.inflatedPublicSingleLineView =
487                     inflationProgress.contentModel.publicSingleLineViewModel?.let { viewModel ->
488                         SingleLineViewInflater.inflatePublicSingleLineView(
489                             viewModel.isConversation(),
490                             reInflateFlags,
491                             entry,
492                             context,
493                             logger,
494                         )
495                     }
496             }
497 
498             logger.logAsyncTaskProgress(entry.logKey, "loading RON images")
499             inflationProgress.rowImageInflater.loadImagesSynchronously(packageContext)
500 
501             logger.logAsyncTaskProgress(
502                 entry.logKey,
503                 "getting row image resolver (on wrong thread!)",
504             )
505             val imageResolver = row.imageResolver
506             // wait for image resolver to finish preloading
507             logger.logAsyncTaskProgress(entry.logKey, "waiting for preloaded images")
508             imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS)
509             return inflationProgress
510         }
511 
512         public override fun onPostExecute(result: Result<InflationProgress>) {
513             Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this))
514             result
515                 .onSuccess { progress ->
516                     // Logged in detail in apply.
517                     cancellationSignal =
518                         apply(
519                             inflationExecutor,
520                             inflateSynchronously,
521                             bindParams.isMinimized,
522                             progress,
523                             reInflateFlags,
524                             remoteViewCache,
525                             entry,
526                             row,
527                             remoteViewClickHandler,
528                             this /* callback */,
529                             logger,
530                         )
531                 }
532                 .onFailure { error -> handleError(error as Exception) }
533         }
534 
535         override fun onCancelled(result: Result<InflationProgress>) {
536             Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this))
537         }
538 
539         private fun handleError(e: Exception) {
540             entry.onInflationTaskFinished()
541             val sbn: StatusBarNotification = entry.sbn
542             val ident: String = (sbn.packageName + "/0x" + Integer.toHexString(sbn.id))
543             Log.e(TAG, "couldn't inflate view for notification $ident", e)
544             callback?.handleInflationException(
545                 if (NotificationBundleUi.isEnabled) entry else row.entryLegacy,
546                 InflationException("Couldn't inflate contentViews$e"),
547             )
548 
549             // Cancel any image loading tasks, not useful any more
550             row.imageResolver.cancelRunningTasks()
551         }
552 
553         override fun abort() {
554             logger.logAsyncTaskProgress(entry.logKey, "cancelling inflate")
555             cancel(/* mayInterruptIfRunning= */ true)
556             if (cancellationSignal != null) {
557                 logger.logAsyncTaskProgress(entry.logKey, "cancelling apply")
558                 cancellationSignal!!.cancel()
559             }
560             logger.logAsyncTaskProgress(entry.logKey, "aborted")
561         }
562 
563         override fun handleInflationException(e: Exception) {
564             handleError(e)
565         }
566 
567         override fun onAsyncInflationFinished() {
568             this.entry.onInflationTaskFinished()
569             row.onNotificationUpdated()
570             callback?.onAsyncInflationFinished(this.entry)
571 
572             // Notify the resolver that the inflation task has finished,
573             // try to purge unnecessary cached entries.
574             row.imageResolver.purgeCache()
575 
576             // Cancel any image loading tasks that have not completed at this point
577             row.imageResolver.cancelRunningTasks()
578         }
579 
580         class RtlEnabledContext(packageContext: Context) : ContextWrapper(packageContext) {
581             override fun getApplicationInfo(): ApplicationInfo {
582                 val applicationInfo = ApplicationInfo(super.getApplicationInfo())
583                 applicationInfo.flags = applicationInfo.flags or ApplicationInfo.FLAG_SUPPORTS_RTL
584                 return applicationInfo
585             }
586         }
587 
588         companion object {
589             private const val IMG_PRELOAD_TIMEOUT_MS = 1000L
590         }
591     }
592 
593     @VisibleForTesting
594     class InflationProgress(
595         @VisibleForTesting val packageContext: Context,
596         val rowImageInflater: RowImageInflater,
597         val remoteViews: NewRemoteViews,
598         val contentModel: NotificationContentModel,
599         val promotedContent: PromotedNotificationContentModels?,
600     ) {
601 
602         var inflatedContentView: View? = null
603         var inflatedHeadsUpView: View? = null
604         var inflatedExpandedView: View? = null
605         var inflatedPublicView: View? = null
606         var inflatedGroupHeaderView: NotificationHeaderView? = null
607         var inflatedMinimizedGroupHeaderView: NotificationHeaderView? = null
608         var inflatedSmartReplyState: InflatedSmartReplyState? = null
609         var expandedInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
610         var headsUpInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
611 
612         // Inflated SingleLineView that lacks the UI State
613         var inflatedSingleLineView: HybridNotificationView? = null
614         // Inflated public SingleLineView that lacks the UI State
615         var inflatedPublicSingleLineView: HybridNotificationView? = null
616     }
617 
618     @VisibleForTesting
619     abstract class ApplyCallback {
620         abstract fun setResultView(v: View)
621 
622         abstract val remoteView: RemoteViews
623     }
624 
625     companion object {
626         const val TAG = "NotifContentInflater"
627 
628         private fun inflateSmartReplyViews(
629             result: InflationProgress,
630             @InflationFlag reInflateFlags: Int,
631             entry: NotificationEntry,
632             context: Context,
633             packageContext: Context,
634             previousSmartReplyState: InflatedSmartReplyState?,
635             inflater: SmartReplyStateInflater,
636             logger: NotificationRowContentBinderLogger,
637         ) {
638             val inflateContracted =
639                 (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0 &&
640                     result.remoteViews.contracted != null)
641             val inflateExpanded =
642                 (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0 &&
643                     result.remoteViews.expanded != null)
644             val inflateHeadsUp =
645                 (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0 &&
646                     result.remoteViews.headsUp != null)
647             if (inflateContracted || inflateExpanded || inflateHeadsUp) {
648                 logger.logAsyncTaskProgress(entry.logKey, "inflating contracted smart reply state")
649                 result.inflatedSmartReplyState = inflater.inflateSmartReplyState(entry)
650             }
651             if (inflateExpanded) {
652                 logger.logAsyncTaskProgress(entry.logKey, "inflating expanded smart reply state")
653                 result.expandedInflatedSmartReplies =
654                     inflater.inflateSmartReplyViewHolder(
655                         context,
656                         packageContext,
657                         entry,
658                         previousSmartReplyState,
659                         result.inflatedSmartReplyState!!,
660                     )
661             }
662             if (inflateHeadsUp) {
663                 logger.logAsyncTaskProgress(entry.logKey, "inflating heads up smart reply state")
664                 result.headsUpInflatedSmartReplies =
665                     inflater.inflateSmartReplyViewHolder(
666                         context,
667                         packageContext,
668                         entry,
669                         previousSmartReplyState,
670                         result.inflatedSmartReplyState!!,
671                     )
672             }
673         }
674 
675         private fun beginInflationAsync(
676             @InflationFlag reInflateFlags: Int,
677             entry: NotificationEntry,
678             builder: Notification.Builder,
679             bindParams: BindParams,
680             systemUiContext: Context,
681             packageContext: Context,
682             row: ExpandableNotificationRow,
683             notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
684             headsUpStyleProvider: HeadsUpStyleProvider,
685             conversationProcessor: ConversationNotificationProcessor,
686             promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
687             logger: NotificationRowContentBinderLogger,
688         ): InflationProgress {
689             val rowImageInflater =
690                 RowImageInflater.newInstance(
691                     previousIndex = row.mImageModelIndex,
692                     // inflating the contracted view is the legacy invalidation trigger
693                     reinflating = reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0,
694                 )
695 
696             val promotedContent =
697                 if (PromotedNotificationContentModel.featureFlagEnabled()) {
698                     logger.logAsyncTaskProgress(
699                         entry.logKey,
700                         "extracting promoted notification content",
701                     )
702                     val imageModelProvider = rowImageInflater.useForContentModel()
703                     promotedNotificationContentExtractor
704                         .extractContent(
705                             entry,
706                             builder,
707                             bindParams.redactionType,
708                             imageModelProvider,
709                         )
710                         .also {
711                             logger.logAsyncTaskProgress(
712                                 entry.logKey,
713                                 "extracted promoted notification content: ${it?.toRedactedString()}",
714                             )
715                         }
716                 } else {
717                     null
718                 }
719 
720             // process conversations and extract the messaging style
721             val messagingStyle =
722                 if (NmSummarizationUiFlag.isEnabled || entry.ranking.isConversation) {
723                     conversationProcessor.processNotification(entry, builder, logger)
724                 } else null
725 
726             val remoteViews =
727                 createRemoteViews(
728                     reInflateFlags = reInflateFlags,
729                     builder = builder,
730                     bindParams = bindParams,
731                     entry = entry,
732                     systemUiContext = systemUiContext,
733                     packageContext = packageContext,
734                     row = row,
735                     notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
736                     headsUpStyleProvider = headsUpStyleProvider,
737                     logger = logger,
738                 )
739 
740             val singleLineViewModel =
741                 if (
742                     AsyncHybridViewInflation.isEnabled &&
743                         reInflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE != 0
744                 ) {
745                     logger.logAsyncTaskProgress(entry.logKey, "inflating single line view model")
746                     SingleLineViewInflater.inflateSingleLineViewModel(
747                         notification = entry.sbn.notification,
748                         messagingStyle = messagingStyle,
749                         builder = builder,
750                         systemUiContext = systemUiContext,
751                         redactText = false,
752                         summarization =
753                             entry.sbn.notification.extras.getCharSequence(EXTRA_SUMMARIZED_CONTENT),
754                     )
755                 } else null
756 
757             val publicSingleLineViewModel =
758                 if (
759                     LockscreenOtpRedaction.isEnabled &&
760                         reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE != 0
761                 ) {
762                     logger.logAsyncTaskProgress(
763                         entry.logKey,
764                         "inflating public single line view model",
765                     )
766                     if (bindParams.redactionType == REDACTION_TYPE_OTP) {
767                         SingleLineViewInflater.inflateSingleLineViewModel(
768                             notification = entry.sbn.notification,
769                             messagingStyle = messagingStyle,
770                             builder = builder,
771                             systemUiContext = systemUiContext,
772                             redactText = true,
773                             summarization = null,
774                         )
775                     } else {
776                         SingleLineViewInflater.inflatePublicSingleLineViewModel(
777                             systemUiContext,
778                             entry.ranking.isConversation,
779                         )
780                     }
781                 } else null
782 
783             val headsUpStatusBarModel =
784                 HeadsUpStatusBarModel(
785                     privateText = builder.getHeadsUpStatusBarText(/* publicMode= */ false),
786                     publicText = builder.getHeadsUpStatusBarText(/* publicMode= */ true),
787                 )
788 
789             val contentModel =
790                 NotificationContentModel(
791                     headsUpStatusBarModel = headsUpStatusBarModel,
792                     singleLineViewModel = singleLineViewModel,
793                     publicSingleLineViewModel = publicSingleLineViewModel,
794                 )
795 
796             return InflationProgress(
797                 packageContext = packageContext,
798                 rowImageInflater = rowImageInflater,
799                 remoteViews = remoteViews,
800                 contentModel = contentModel,
801                 promotedContent = promotedContent,
802             )
803         }
804 
805         private fun createSensitiveContentMessageNotification(
806             original: Notification,
807             originalStyle: Notification.Style?,
808             sysUiContext: Context,
809             packageContext: Context,
810         ): Notification.Builder {
811             val redacted = Notification.Builder(packageContext, original.channelId)
812             redacted.setContentTitle(original.extras.getCharSequence(Notification.EXTRA_TITLE))
813             val redactedMessage =
814                 sysUiContext.getString(R.string.redacted_otp_notification_single_line_text)
815 
816             if (originalStyle is MessagingStyle) {
817                 val newStyle = MessagingStyle(originalStyle.user)
818                 newStyle.conversationTitle = originalStyle.conversationTitle
819                 newStyle.isGroupConversation = false
820                 newStyle.conversationType = originalStyle.conversationType
821                 newStyle.shortcutIcon = originalStyle.shortcutIcon
822                 newStyle.setBuilder(redacted)
823                 val latestMessage = MessagingStyle.findLatestIncomingMessage(originalStyle.messages)
824                 if (latestMessage != null) {
825                     val newMessage =
826                         MessagingStyle.Message(
827                             redactedMessage,
828                             latestMessage.timestamp,
829                             latestMessage.senderPerson,
830                         )
831                     newStyle.addMessage(newMessage)
832                 }
833                 redacted.style = newStyle
834             } else {
835                 redacted.setContentText(redactedMessage)
836             }
837             redacted.setLargeIcon(original.getLargeIcon())
838             redacted.setSmallIcon(original.smallIcon)
839             redacted.setWhen(original.getWhen())
840             return redacted
841         }
842 
843         private fun createRemoteViews(
844             @InflationFlag reInflateFlags: Int,
845             builder: Notification.Builder,
846             bindParams: BindParams,
847             entry: NotificationEntry,
848             systemUiContext: Context,
849             packageContext: Context,
850             row: ExpandableNotificationRow,
851             notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
852             headsUpStyleProvider: HeadsUpStyleProvider,
853             logger: NotificationRowContentBinderLogger,
854         ): NewRemoteViews {
855             return TraceUtils.trace("NotificationContentInflater.createRemoteViews") {
856                 val contracted =
857                     if (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
858                         logger.logAsyncTaskProgress(
859                             row.loggingKey,
860                             "creating contracted remote view",
861                         )
862                         createContentView(builder, bindParams.isMinimized)
863                     } else null
864                 val expanded =
865                     if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
866                         logger.logAsyncTaskProgress(row.loggingKey, "creating expanded remote view")
867                         createExpandedView(builder, bindParams.isMinimized)
868                     } else null
869                 val headsUp =
870                     if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) {
871                         logger.logAsyncTaskProgress(row.loggingKey, "creating heads up remote view")
872                         val isHeadsUpCompact = headsUpStyleProvider.shouldApplyCompactStyle()
873                         if (isHeadsUpCompact) {
874                             builder.createCompactHeadsUpContentView()
875                         } else {
876                             @Suppress("DEPRECATION") builder.createHeadsUpContentView()
877                         }
878                     } else null
879                 val public =
880                     if (reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC != 0) {
881                         logger.logAsyncTaskProgress(row.loggingKey, "creating public remote view")
882                         if (
883                             LockscreenOtpRedaction.isEnabled &&
884                                 bindParams.redactionType == REDACTION_TYPE_OTP
885                         ) {
886                             createSensitiveContentMessageNotification(
887                                     entry.sbn.notification,
888                                     builder.style,
889                                     systemUiContext,
890                                     packageContext,
891                                 )
892                                 .createContentView()
893                         } else {
894                             builder.makePublicContentView(bindParams.isMinimized)
895                         }
896                     } else null
897                 val normalGroupHeader =
898                     if (
899                         AsyncGroupHeaderViewInflation.isEnabled &&
900                             reInflateFlags and FLAG_GROUP_SUMMARY_HEADER != 0
901                     ) {
902                         logger.logAsyncTaskProgress(
903                             row.loggingKey,
904                             "creating group summary remote view",
905                         )
906                         builder.makeNotificationGroupHeader()
907                     } else null
908                 val minimizedGroupHeader =
909                     if (
910                         AsyncGroupHeaderViewInflation.isEnabled &&
911                             reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0
912                     ) {
913                         logger.logAsyncTaskProgress(
914                             row.loggingKey,
915                             "creating low-priority group summary remote view",
916                         )
917                         builder.makeLowPriorityContentView(true /* useRegularSubtext */)
918                     } else null
919                 NewRemoteViews(
920                         contracted = contracted,
921                         headsUp = headsUp,
922                         expanded = expanded,
923                         public = public,
924                         normalGroupHeader = normalGroupHeader,
925                         minimizedGroupHeader = minimizedGroupHeader,
926                     )
927                     .withLayoutInflaterFactory(row, notifLayoutInflaterFactoryProvider)
928             }
929         }
930 
931         private fun NewRemoteViews.withLayoutInflaterFactory(
932             row: ExpandableNotificationRow,
933             provider: NotifLayoutInflaterFactory.Provider,
934         ): NewRemoteViews {
935             contracted?.let {
936                 it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED)
937             }
938             expanded?.let {
939                 it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_EXPANDED)
940             }
941             headsUp?.let {
942                 it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_HEADS_UP)
943             }
944             public?.let {
945                 it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_PUBLIC)
946             }
947             if (android.app.Flags.notificationsRedesignAppIcons()) {
948                 normalGroupHeader?.let {
949                     it.layoutInflaterFactory = provider.provide(row, FLAG_GROUP_SUMMARY_HEADER)
950                 }
951                 minimizedGroupHeader?.let {
952                     it.layoutInflaterFactory =
953                         provider.provide(row, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)
954                 }
955             }
956             return this
957         }
958 
959         private fun apply(
960             inflationExecutor: Executor,
961             inflateSynchronously: Boolean,
962             isMinimized: Boolean,
963             result: InflationProgress,
964             @InflationFlag reInflateFlags: Int,
965             remoteViewCache: NotifRemoteViewCache,
966             entry: NotificationEntry,
967             row: ExpandableNotificationRow,
968             remoteViewClickHandler: InteractionHandler?,
969             callback: InflationCallback?,
970             logger: NotificationRowContentBinderLogger,
971         ): CancellationSignal {
972             Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
973             val privateLayout = row.privateLayout
974             val publicLayout = row.publicLayout
975             val runningInflations = HashMap<Int, CancellationSignal>()
976             var flag = FLAG_CONTENT_VIEW_CONTRACTED
977             if (reInflateFlags and flag != 0 && result.remoteViews.contracted != null) {
978                 val isNewView =
979                     !canReapplyRemoteView(
980                         newView = result.remoteViews.contracted,
981                         oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED),
982                     )
983                 val applyCallback: ApplyCallback =
984                     object : ApplyCallback() {
985                         override fun setResultView(v: View) {
986                             logger.logAsyncTaskProgress(entry.logKey, "contracted view applied")
987                             result.inflatedContentView = v
988                         }
989 
990                         override val remoteView: RemoteViews
991                             get() = result.remoteViews.contracted
992                     }
993                 logger.logAsyncTaskProgress(entry.logKey, "applying contracted view")
994                 applyRemoteView(
995                     inflationExecutor = inflationExecutor,
996                     inflateSynchronously = inflateSynchronously,
997                     isMinimized = isMinimized,
998                     result = result,
999                     reInflateFlags = reInflateFlags,
1000                     inflationId = flag,
1001                     remoteViewCache = remoteViewCache,
1002                     entry = entry,
1003                     row = row,
1004                     isNewView = isNewView,
1005                     remoteViewClickHandler = remoteViewClickHandler,
1006                     callback = callback,
1007                     parentLayout = privateLayout,
1008                     existingView = privateLayout.contractedChild,
1009                     existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED),
1010                     runningInflations = runningInflations,
1011                     applyCallback = applyCallback,
1012                     logger = logger,
1013                 )
1014             }
1015             flag = FLAG_CONTENT_VIEW_EXPANDED
1016             if (reInflateFlags and flag != 0 && result.remoteViews.expanded != null) {
1017                 val isNewView =
1018                     !canReapplyRemoteView(
1019                         newView = result.remoteViews.expanded,
1020                         oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED),
1021                     )
1022                 val applyCallback: ApplyCallback =
1023                     object : ApplyCallback() {
1024                         override fun setResultView(v: View) {
1025                             logger.logAsyncTaskProgress(entry.logKey, "expanded view applied")
1026                             result.inflatedExpandedView = v
1027                         }
1028 
1029                         override val remoteView: RemoteViews
1030                             get() = result.remoteViews.expanded
1031                     }
1032                 logger.logAsyncTaskProgress(entry.logKey, "applying expanded view")
1033                 applyRemoteView(
1034                     inflationExecutor = inflationExecutor,
1035                     inflateSynchronously = inflateSynchronously,
1036                     isMinimized = isMinimized,
1037                     result = result,
1038                     reInflateFlags = reInflateFlags,
1039                     inflationId = flag,
1040                     remoteViewCache = remoteViewCache,
1041                     entry = entry,
1042                     row = row,
1043                     isNewView = isNewView,
1044                     remoteViewClickHandler = remoteViewClickHandler,
1045                     callback = callback,
1046                     parentLayout = privateLayout,
1047                     existingView = privateLayout.expandedChild,
1048                     existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED),
1049                     runningInflations = runningInflations,
1050                     applyCallback = applyCallback,
1051                     logger = logger,
1052                 )
1053             }
1054             flag = FLAG_CONTENT_VIEW_HEADS_UP
1055             if (reInflateFlags and flag != 0 && result.remoteViews.headsUp != null) {
1056                 val isNewView =
1057                     !canReapplyRemoteView(
1058                         newView = result.remoteViews.headsUp,
1059                         oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP),
1060                     )
1061                 val applyCallback: ApplyCallback =
1062                     object : ApplyCallback() {
1063                         override fun setResultView(v: View) {
1064                             logger.logAsyncTaskProgress(entry.logKey, "heads up view applied")
1065                             result.inflatedHeadsUpView = v
1066                         }
1067 
1068                         override val remoteView: RemoteViews
1069                             get() = result.remoteViews.headsUp
1070                     }
1071                 logger.logAsyncTaskProgress(entry.logKey, "applying heads up view")
1072                 applyRemoteView(
1073                     inflationExecutor = inflationExecutor,
1074                     inflateSynchronously = inflateSynchronously,
1075                     isMinimized = isMinimized,
1076                     result = result,
1077                     reInflateFlags = reInflateFlags,
1078                     inflationId = flag,
1079                     remoteViewCache = remoteViewCache,
1080                     entry = entry,
1081                     row = row,
1082                     isNewView = isNewView,
1083                     remoteViewClickHandler = remoteViewClickHandler,
1084                     callback = callback,
1085                     parentLayout = privateLayout,
1086                     existingView = privateLayout.headsUpChild,
1087                     existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP),
1088                     runningInflations = runningInflations,
1089                     applyCallback = applyCallback,
1090                     logger = logger,
1091                 )
1092             }
1093             flag = FLAG_CONTENT_VIEW_PUBLIC
1094             if (reInflateFlags and flag != 0) {
1095                 val isNewView =
1096                     !canReapplyRemoteView(
1097                         newView = result.remoteViews.public,
1098                         oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC),
1099                     )
1100                 val applyCallback: ApplyCallback =
1101                     object : ApplyCallback() {
1102                         override fun setResultView(v: View) {
1103                             logger.logAsyncTaskProgress(entry.logKey, "public view applied")
1104                             result.inflatedPublicView = v
1105                         }
1106 
1107                         override val remoteView: RemoteViews
1108                             get() = result.remoteViews.public!!
1109                     }
1110                 logger.logAsyncTaskProgress(entry.logKey, "applying public view")
1111                 applyRemoteView(
1112                     inflationExecutor = inflationExecutor,
1113                     inflateSynchronously = inflateSynchronously,
1114                     isMinimized = isMinimized,
1115                     result = result,
1116                     reInflateFlags = reInflateFlags,
1117                     inflationId = flag,
1118                     remoteViewCache = remoteViewCache,
1119                     entry = entry,
1120                     row = row,
1121                     isNewView = isNewView,
1122                     remoteViewClickHandler = remoteViewClickHandler,
1123                     callback = callback,
1124                     parentLayout = publicLayout,
1125                     existingView = publicLayout.contractedChild,
1126                     existingWrapper = publicLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED),
1127                     runningInflations = runningInflations,
1128                     applyCallback = applyCallback,
1129                     logger = logger,
1130                 )
1131             }
1132             if (AsyncGroupHeaderViewInflation.isEnabled) {
1133                 val childrenContainer: NotificationChildrenContainer =
1134                     row.getChildrenContainerNonNull()
1135                 if (reInflateFlags and FLAG_GROUP_SUMMARY_HEADER != 0) {
1136                     val isNewView =
1137                         !canReapplyRemoteView(
1138                             newView = result.remoteViews.normalGroupHeader,
1139                             oldView =
1140                                 remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER),
1141                         )
1142                     val applyCallback: ApplyCallback =
1143                         object : ApplyCallback() {
1144                             override fun setResultView(v: View) {
1145                                 logger.logAsyncTaskProgress(
1146                                     entry.logKey,
1147                                     "group header view applied",
1148                                 )
1149                                 result.inflatedGroupHeaderView = v as NotificationHeaderView?
1150                             }
1151 
1152                             override val remoteView: RemoteViews
1153                                 get() = result.remoteViews.normalGroupHeader!!
1154                         }
1155                     logger.logAsyncTaskProgress(entry.logKey, "applying group header view")
1156                     applyRemoteView(
1157                         inflationExecutor = inflationExecutor,
1158                         inflateSynchronously = inflateSynchronously,
1159                         isMinimized = isMinimized,
1160                         result = result,
1161                         reInflateFlags = reInflateFlags,
1162                         inflationId = FLAG_GROUP_SUMMARY_HEADER,
1163                         remoteViewCache = remoteViewCache,
1164                         entry = entry,
1165                         row = row,
1166                         isNewView = isNewView,
1167                         remoteViewClickHandler = remoteViewClickHandler,
1168                         callback = callback,
1169                         parentLayout = childrenContainer,
1170                         existingView = childrenContainer.groupHeader,
1171                         existingWrapper = childrenContainer.notificationHeaderWrapper,
1172                         runningInflations = runningInflations,
1173                         applyCallback = applyCallback,
1174                         logger = logger,
1175                     )
1176                 }
1177                 if (reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0) {
1178                     val isNewView =
1179                         !canReapplyRemoteView(
1180                             newView = result.remoteViews.minimizedGroupHeader,
1181                             oldView =
1182                                 remoteViewCache.getCachedView(
1183                                     entry,
1184                                     FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
1185                                 ),
1186                         )
1187                     val applyCallback: ApplyCallback =
1188                         object : ApplyCallback() {
1189                             override fun setResultView(v: View) {
1190                                 logger.logAsyncTaskProgress(
1191                                     entry.logKey,
1192                                     "low-priority group header view applied",
1193                                 )
1194                                 result.inflatedMinimizedGroupHeaderView =
1195                                     v as NotificationHeaderView?
1196                             }
1197 
1198                             override val remoteView: RemoteViews
1199                                 get() = result.remoteViews.minimizedGroupHeader!!
1200                         }
1201                     logger.logAsyncTaskProgress(
1202                         entry.logKey,
1203                         "applying low priority group header view",
1204                     )
1205                     applyRemoteView(
1206                         inflationExecutor = inflationExecutor,
1207                         inflateSynchronously = inflateSynchronously,
1208                         isMinimized = isMinimized,
1209                         result = result,
1210                         reInflateFlags = reInflateFlags,
1211                         inflationId = FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
1212                         remoteViewCache = remoteViewCache,
1213                         entry = entry,
1214                         row = row,
1215                         isNewView = isNewView,
1216                         remoteViewClickHandler = remoteViewClickHandler,
1217                         callback = callback,
1218                         parentLayout = childrenContainer,
1219                         existingView = childrenContainer.minimizedNotificationHeader,
1220                         existingWrapper = childrenContainer.minimizedGroupHeaderWrapper,
1221                         runningInflations = runningInflations,
1222                         applyCallback = applyCallback,
1223                         logger = logger,
1224                     )
1225                 }
1226             }
1227 
1228             // Let's try to finish, maybe nobody is even inflating anything
1229             finishIfDone(
1230                 result,
1231                 isMinimized,
1232                 reInflateFlags,
1233                 remoteViewCache,
1234                 runningInflations,
1235                 callback,
1236                 entry,
1237                 row,
1238                 logger,
1239             )
1240             val cancellationSignal = CancellationSignal()
1241             cancellationSignal.setOnCancelListener {
1242                 logger.logAsyncTaskProgress(entry.logKey, "apply cancelled")
1243                 Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
1244                 runningInflations.values.forEach(
1245                     Consumer { obj: CancellationSignal -> obj.cancel() }
1246                 )
1247             }
1248             return cancellationSignal
1249         }
1250 
1251         @VisibleForTesting
1252         fun applyRemoteView(
1253             inflationExecutor: Executor?,
1254             inflateSynchronously: Boolean,
1255             isMinimized: Boolean,
1256             result: InflationProgress,
1257             @InflationFlag reInflateFlags: Int,
1258             @InflationFlag inflationId: Int,
1259             remoteViewCache: NotifRemoteViewCache,
1260             entry: NotificationEntry,
1261             row: ExpandableNotificationRow,
1262             isNewView: Boolean,
1263             remoteViewClickHandler: InteractionHandler?,
1264             callback: InflationCallback?,
1265             parentLayout: ViewGroup?,
1266             existingView: View?,
1267             existingWrapper: NotificationViewWrapper?,
1268             runningInflations: HashMap<Int, CancellationSignal>,
1269             applyCallback: ApplyCallback,
1270             logger: NotificationRowContentBinderLogger,
1271         ) {
1272             val newContentView: RemoteViews = applyCallback.remoteView
1273             if (inflateSynchronously) {
1274                 try {
1275                     if (isNewView) {
1276                         val v: View =
1277                             newContentView.apply(
1278                                 result.packageContext,
1279                                 parentLayout,
1280                                 remoteViewClickHandler,
1281                             )
1282                         validateView(v, entry, row.resources)
1283                         applyCallback.setResultView(v)
1284                     } else {
1285                         requireNotNull(existingView)
1286                         requireNotNull(existingWrapper)
1287                         newContentView.reapply(
1288                             result.packageContext,
1289                             existingView,
1290                             remoteViewClickHandler,
1291                         )
1292                         validateView(existingView, entry, row.resources)
1293                         existingWrapper.onReinflated()
1294                     }
1295                 } catch (e: Exception) {
1296                     handleInflationError(
1297                         runningInflations,
1298                         e,
1299                         row,
1300                         entry,
1301                         callback,
1302                         logger,
1303                         "applying view synchronously",
1304                     )
1305                     // Add a running inflation to make sure we don't trigger callbacks.
1306                     // Safe to do because only happens in tests.
1307                     runningInflations[inflationId] = CancellationSignal()
1308                 }
1309                 return
1310             }
1311             val listener: OnViewAppliedListener =
1312                 object : OnViewAppliedListener {
1313                     override fun onViewInflated(v: View) {
1314                         if (v is ImageMessageConsumer) {
1315                             (v as ImageMessageConsumer).setImageResolver(row.imageResolver)
1316                         }
1317                     }
1318 
1319                     override fun onViewApplied(v: View) {
1320                         val invalidReason = isValidView(v, entry, row.resources)
1321                         if (invalidReason != null) {
1322                             handleInflationError(
1323                                 runningInflations,
1324                                 InflationException(invalidReason),
1325                                 row,
1326                                 entry,
1327                                 callback,
1328                                 logger,
1329                                 "applied invalid view",
1330                             )
1331                             runningInflations.remove(inflationId)
1332                             return
1333                         }
1334                         if (isNewView) {
1335                             applyCallback.setResultView(v)
1336                         } else {
1337                             existingWrapper?.onReinflated()
1338                         }
1339                         runningInflations.remove(inflationId)
1340                         finishIfDone(
1341                             result,
1342                             isMinimized,
1343                             reInflateFlags,
1344                             remoteViewCache,
1345                             runningInflations,
1346                             callback,
1347                             entry,
1348                             row,
1349                             logger,
1350                         )
1351                     }
1352 
1353                     override fun onError(e: Exception) {
1354                         // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this
1355                         // could
1356                         // actually also be a system issue, so let's try on the UI thread again to
1357                         // be safe.
1358                         try {
1359                             val newView =
1360                                 if (isNewView) {
1361                                     newContentView.apply(
1362                                         result.packageContext,
1363                                         parentLayout,
1364                                         remoteViewClickHandler,
1365                                     )
1366                                 } else {
1367                                     newContentView.reapply(
1368                                         result.packageContext,
1369                                         existingView,
1370                                         remoteViewClickHandler,
1371                                     )
1372                                     existingView!!
1373                                 }
1374                             Log.wtf(
1375                                 TAG,
1376                                 "Async Inflation failed but normal inflation finished normally.",
1377                                 e,
1378                             )
1379                             onViewApplied(newView)
1380                         } catch (anotherException: Exception) {
1381                             runningInflations.remove(inflationId)
1382                             handleInflationError(
1383                                 runningInflations,
1384                                 e,
1385                                 row,
1386                                 entry,
1387                                 callback,
1388                                 logger,
1389                                 "applying view",
1390                             )
1391                         }
1392                     }
1393                 }
1394             val cancellationSignal: CancellationSignal =
1395                 if (isNewView) {
1396                     newContentView.applyAsync(
1397                         result.packageContext,
1398                         parentLayout,
1399                         inflationExecutor,
1400                         listener,
1401                         remoteViewClickHandler,
1402                     )
1403                 } else {
1404                     newContentView.reapplyAsync(
1405                         result.packageContext,
1406                         existingView,
1407                         inflationExecutor,
1408                         listener,
1409                         remoteViewClickHandler,
1410                     )
1411                 }
1412             runningInflations[inflationId] = cancellationSignal
1413         }
1414 
1415         /**
1416          * Checks if the given View is a valid notification View.
1417          *
1418          * @return null == valid, non-null == invalid, String represents reason for rejection.
1419          */
1420         @VisibleForTesting
1421         fun isValidView(view: View, entry: NotificationEntry, resources: Resources): String? {
1422             if (!satisfiesMinHeightRequirement(view, entry, resources)) {
1423                 return "inflated notification does not meet minimum height requirement"
1424             }
1425 
1426             if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) {
1427                 if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) {
1428                     return "inflated notification does not meet maximum memory size requirement"
1429                 }
1430             }
1431 
1432             return null
1433         }
1434 
1435         private fun satisfiesMinHeightRequirement(
1436             view: View,
1437             entry: NotificationEntry,
1438             resources: Resources,
1439         ): Boolean {
1440             return if (!requiresHeightCheck(entry)) {
1441                 true
1442             } else
1443                 TraceUtils.trace("NotificationContentInflater#satisfiesMinHeightRequirement") {
1444                     val heightSpec =
1445                         View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
1446                     val referenceWidth =
1447                         resources.getDimensionPixelSize(
1448                             R.dimen.notification_validation_reference_width
1449                         )
1450                     val widthSpec =
1451                         View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY)
1452                     view.measure(widthSpec, heightSpec)
1453                     val minHeight =
1454                         resources.getDimensionPixelSize(
1455                             R.dimen.notification_validation_minimum_allowed_height
1456                         )
1457                     view.measuredHeight >= minHeight
1458                 }
1459         }
1460 
1461         /**
1462          * Notifications with undecorated custom views need to satisfy a minimum height to avoid
1463          * visual issues.
1464          */
1465         private fun requiresHeightCheck(entry: NotificationEntry): Boolean {
1466             // Undecorated custom views are disallowed from S onwards
1467             if (entry.targetSdk >= Build.VERSION_CODES.S) {
1468                 return false
1469             }
1470             // No need to check if the app isn't using any custom views
1471             val notification: Notification = entry.sbn.notification
1472             @Suppress("DEPRECATION")
1473             return !(notification.contentView == null &&
1474                 notification.bigContentView == null &&
1475                 notification.headsUpContentView == null)
1476         }
1477 
1478         @Throws(InflationException::class)
1479         private fun validateView(view: View, entry: NotificationEntry, resources: Resources) {
1480             val invalidReason = isValidView(view, entry, resources)
1481             if (invalidReason != null) {
1482                 throw InflationException(invalidReason)
1483             }
1484         }
1485 
1486         private fun handleInflationError(
1487             runningInflations: HashMap<Int, CancellationSignal>,
1488             e: Exception,
1489             notification: ExpandableNotificationRow?,
1490             entry: NotificationEntry,
1491             callback: InflationCallback?,
1492             logger: NotificationRowContentBinderLogger,
1493             logContext: String,
1494         ) {
1495             Assert.isMainThread()
1496             logger.logAsyncTaskException(notification?.loggingKey, logContext, e)
1497             runningInflations.values.forEach(Consumer { obj: CancellationSignal -> obj.cancel() })
1498             callback?.handleInflationException(entry, e)
1499         }
1500 
1501         /**
1502          * Finish the inflation of the views
1503          *
1504          * @return true if the inflation was finished
1505          */
1506         private fun finishIfDone(
1507             result: InflationProgress,
1508             isMinimized: Boolean,
1509             @InflationFlag reInflateFlags: Int,
1510             remoteViewCache: NotifRemoteViewCache,
1511             runningInflations: HashMap<Int, CancellationSignal>,
1512             endListener: InflationCallback?,
1513             entry: NotificationEntry,
1514             row: ExpandableNotificationRow,
1515             logger: NotificationRowContentBinderLogger,
1516         ): Boolean {
1517             Assert.isMainThread()
1518             if (runningInflations.isNotEmpty()) {
1519                 return false
1520             }
1521             logger.logAsyncTaskProgress(row.loggingKey, "finishing")
1522 
1523             // Put the new image index on the row
1524             row.mImageModelIndex = result.rowImageInflater.getNewImageIndex()
1525 
1526             entry.setContentModel(result.contentModel)
1527             if (PromotedNotificationContentModel.featureFlagEnabled()) {
1528                 entry.promotedNotificationContentModels = result.promotedContent
1529             }
1530 
1531             result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
1532 
1533             setContentViewsFromRemoteViews(
1534                 reInflateFlags,
1535                 entry,
1536                 remoteViewCache,
1537                 result,
1538                 row,
1539                 isMinimized,
1540             )
1541 
1542             if (
1543                 AsyncHybridViewInflation.isEnabled &&
1544                     reInflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE != 0
1545             ) {
1546                 val singleLineView = result.inflatedSingleLineView
1547                 val viewModel = result.contentModel.singleLineViewModel
1548                 if (singleLineView != null && viewModel != null) {
1549                     SingleLineViewBinder.bind(viewModel, singleLineView)
1550                     row.privateLayout.setSingleLineView(result.inflatedSingleLineView)
1551                 }
1552             }
1553 
1554             if (
1555                 LockscreenOtpRedaction.isEnabled &&
1556                     reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE != 0
1557             ) {
1558                 val singleLineView = result.inflatedPublicSingleLineView
1559                 val viewModel = result.contentModel.publicSingleLineViewModel
1560                 if (singleLineView != null && viewModel != null) {
1561                     SingleLineViewBinder.bind(viewModel, singleLineView)
1562                     row.publicLayout.setSingleLineView(result.inflatedPublicSingleLineView)
1563                 }
1564             }
1565 
1566             Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
1567             endListener?.onAsyncInflationFinished(entry)
1568             return true
1569         }
1570 
1571         private fun setContentViewsFromRemoteViews(
1572             @InflationFlag reInflateFlags: Int,
1573             entry: NotificationEntry,
1574             remoteViewCache: NotifRemoteViewCache,
1575             result: InflationProgress,
1576             row: ExpandableNotificationRow,
1577             isMinimized: Boolean,
1578         ) {
1579             val privateLayout = row.privateLayout
1580             val publicLayout = row.publicLayout
1581             val remoteViewsUpdater = RemoteViewsUpdater(reInflateFlags, entry, remoteViewCache)
1582             remoteViewsUpdater.setContentView(
1583                 FLAG_CONTENT_VIEW_CONTRACTED,
1584                 result.remoteViews.contracted,
1585                 result.inflatedContentView,
1586                 privateLayout::setContractedChild,
1587             )
1588             remoteViewsUpdater.setContentView(
1589                 FLAG_CONTENT_VIEW_EXPANDED,
1590                 result.remoteViews.expanded,
1591                 result.inflatedExpandedView,
1592                 privateLayout::setExpandedChild,
1593             )
1594             remoteViewsUpdater.setSmartReplies(
1595                 FLAG_CONTENT_VIEW_EXPANDED,
1596                 result.remoteViews.expanded,
1597                 result.expandedInflatedSmartReplies,
1598                 privateLayout::setExpandedInflatedSmartReplies,
1599             )
1600             if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
1601                 row.setExpandable(result.remoteViews.expanded != null)
1602             }
1603             remoteViewsUpdater.setContentView(
1604                 FLAG_CONTENT_VIEW_HEADS_UP,
1605                 result.remoteViews.headsUp,
1606                 result.inflatedHeadsUpView,
1607                 privateLayout::setHeadsUpChild,
1608             )
1609             remoteViewsUpdater.setSmartReplies(
1610                 FLAG_CONTENT_VIEW_HEADS_UP,
1611                 result.remoteViews.headsUp,
1612                 result.headsUpInflatedSmartReplies,
1613                 privateLayout::setHeadsUpInflatedSmartReplies,
1614             )
1615             remoteViewsUpdater.setContentView(
1616                 FLAG_CONTENT_VIEW_PUBLIC,
1617                 result.remoteViews.public,
1618                 result.inflatedPublicView,
1619                 publicLayout::setContractedChild,
1620             )
1621             if (AsyncGroupHeaderViewInflation.isEnabled) {
1622                 remoteViewsUpdater.setContentView(
1623                     FLAG_GROUP_SUMMARY_HEADER,
1624                     result.remoteViews.normalGroupHeader,
1625                     result.inflatedGroupHeaderView,
1626                 ) { views ->
1627                     row.setIsMinimized(isMinimized)
1628                     row.setGroupHeader(views)
1629                 }
1630                 remoteViewsUpdater.setContentView(
1631                     FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
1632                     result.remoteViews.minimizedGroupHeader,
1633                     result.inflatedMinimizedGroupHeaderView,
1634                 ) { views ->
1635                     row.setIsMinimized(isMinimized)
1636                     row.setMinimizedGroupHeader(views)
1637                 }
1638             }
1639         }
1640 
1641         private class RemoteViewsUpdater(
1642             @InflationFlag private val reInflateFlags: Int,
1643             private val entry: NotificationEntry,
1644             private val remoteViewCache: NotifRemoteViewCache,
1645         ) {
1646             fun <V : View> setContentView(
1647                 @InflationFlag flagState: Int,
1648                 remoteViews: RemoteViews?,
1649                 view: V?,
1650                 setView: (V?) -> Unit,
1651             ) {
1652                 val clearViewFlags = FLAG_CONTENT_VIEW_HEADS_UP or FLAG_CONTENT_VIEW_EXPANDED
1653                 val shouldClearView = flagState and clearViewFlags != 0
1654                 if (reInflateFlags and flagState != 0) {
1655                     if (view != null) {
1656                         setView(view)
1657                         remoteViewCache.putCachedView(entry, flagState, remoteViews)
1658                     } else if (shouldClearView && remoteViews == null) {
1659                         setView(null)
1660                         remoteViewCache.removeCachedView(entry, flagState)
1661                     } else if (remoteViewCache.hasCachedView(entry, flagState)) {
1662                         // Re-inflation case. Only update if it's still cached (i.e. view has not
1663                         // been freed while inflating).
1664                         remoteViewCache.putCachedView(entry, flagState, remoteViews)
1665                     }
1666                 }
1667             }
1668 
1669             fun setSmartReplies(
1670                 @InflationFlag flagState: Int,
1671                 remoteViews: RemoteViews?,
1672                 smartReplies: InflatedSmartReplyViewHolder?,
1673                 setSmartReplies: (InflatedSmartReplyViewHolder?) -> Unit,
1674             ) {
1675                 if (reInflateFlags and flagState != 0) {
1676                     if (remoteViews != null) {
1677                         setSmartReplies(smartReplies)
1678                     } else {
1679                         setSmartReplies(null)
1680                     }
1681                 }
1682             }
1683         }
1684 
1685         private fun createExpandedView(
1686             builder: Notification.Builder,
1687             isMinimized: Boolean,
1688         ): RemoteViews? {
1689             @Suppress("DEPRECATION")
1690             val bigContentView: RemoteViews? = builder.createBigContentView()
1691             if (bigContentView != null) {
1692                 return bigContentView
1693             }
1694             if (isMinimized) {
1695                 @Suppress("DEPRECATION") val contentView: RemoteViews = builder.createContentView()
1696                 Notification.Builder.makeHeaderExpanded(contentView)
1697                 return contentView
1698             }
1699             return null
1700         }
1701 
1702         private fun createContentView(
1703             builder: Notification.Builder,
1704             isMinimized: Boolean,
1705         ): RemoteViews {
1706             return if (isMinimized) {
1707                 builder.makeLowPriorityContentView(false /* useRegularSubtext */)
1708             } else {
1709                 @Suppress("DEPRECATION") builder.createContentView()
1710             }
1711         }
1712 
1713         /**
1714          * @param newView The new view that will be applied
1715          * @param oldView The old view that was applied to the existing view before
1716          * @return `true` if the RemoteViews are the same and the view can be reused to reapply.
1717          */
1718         @VisibleForTesting
1719         fun canReapplyRemoteView(newView: RemoteViews?, oldView: RemoteViews?): Boolean {
1720             return newView == null && oldView == null ||
1721                 newView != null &&
1722                     oldView != null &&
1723                     oldView.getPackage() != null &&
1724                     newView.getPackage() != null &&
1725                     newView.getPackage() == oldView.getPackage() &&
1726                     newView.layoutId == oldView.layoutId &&
1727                     !oldView.hasFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED)
1728         }
1729 
1730         private const val ASYNC_TASK_TRACE_METHOD =
1731             "NotificationRowContentBinderImpl.AsyncInflationTask"
1732         private const val APPLY_TRACE_METHOD = "NotificationRowContentBinderImpl#apply"
1733     }
1734 }
1735