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