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