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