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 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.Notification; 27 import android.content.Context; 28 import android.content.ContextWrapper; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.PackageManager; 31 import android.os.AsyncTask; 32 import android.os.CancellationSignal; 33 import android.os.UserHandle; 34 import android.service.notification.StatusBarNotification; 35 import android.util.Log; 36 import android.view.View; 37 import android.widget.RemoteViews; 38 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.Background; 43 import com.android.systemui.media.MediaFeatureFlag; 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.wrapper.NotificationViewWrapper; 50 import com.android.systemui.statusbar.phone.StatusBar; 51 import com.android.systemui.statusbar.policy.InflatedSmartReplyState; 52 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder; 53 import com.android.systemui.statusbar.policy.SmartReplyStateInflater; 54 import com.android.systemui.util.Assert; 55 56 import java.util.HashMap; 57 import java.util.concurrent.Executor; 58 59 import javax.inject.Inject; 60 61 /** 62 * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by 63 * asynchronously building the content's {@link RemoteViews} and applying it to the row. 64 */ 65 @SysUISingleton 66 @VisibleForTesting(visibility = PACKAGE) 67 public class NotificationContentInflater implements NotificationRowContentBinder { 68 69 public static final String TAG = "NotifContentInflater"; 70 71 private boolean mInflateSynchronously = false; 72 private final boolean mIsMediaInQS; 73 private final NotificationRemoteInputManager mRemoteInputManager; 74 private final NotifRemoteViewCache mRemoteViewCache; 75 private final ConversationNotificationProcessor mConversationProcessor; 76 private final Executor mBgExecutor; 77 private final SmartReplyStateInflater mSmartReplyStateInflater; 78 79 @Inject NotificationContentInflater( NotifRemoteViewCache remoteViewCache, NotificationRemoteInputManager remoteInputManager, ConversationNotificationProcessor conversationProcessor, MediaFeatureFlag mediaFeatureFlag, @Background Executor bgExecutor, SmartReplyStateInflater smartRepliesInflater)80 NotificationContentInflater( 81 NotifRemoteViewCache remoteViewCache, 82 NotificationRemoteInputManager remoteInputManager, 83 ConversationNotificationProcessor conversationProcessor, 84 MediaFeatureFlag mediaFeatureFlag, 85 @Background Executor bgExecutor, 86 SmartReplyStateInflater smartRepliesInflater) { 87 mRemoteViewCache = remoteViewCache; 88 mRemoteInputManager = remoteInputManager; 89 mConversationProcessor = conversationProcessor; 90 mIsMediaInQS = mediaFeatureFlag.getEnabled(); 91 mBgExecutor = bgExecutor; 92 mSmartReplyStateInflater = smartRepliesInflater; 93 } 94 95 @Override bindContent( NotificationEntry entry, ExpandableNotificationRow row, @InflationFlag int contentToBind, BindParams bindParams, boolean forceInflate, @Nullable InflationCallback callback)96 public void bindContent( 97 NotificationEntry entry, 98 ExpandableNotificationRow row, 99 @InflationFlag int contentToBind, 100 BindParams bindParams, 101 boolean forceInflate, 102 @Nullable InflationCallback callback) { 103 if (row.isRemoved()) { 104 // We don't want to reinflate anything for removed notifications. Otherwise views might 105 // be readded to the stack, leading to leaks. This may happen with low-priority groups 106 // where the removal of already removed children can lead to a reinflation. 107 return; 108 } 109 110 StatusBarNotification sbn = entry.getSbn(); 111 112 // To check if the notification has inline image and preload inline image if necessary. 113 row.getImageResolver().preloadImages(sbn.getNotification()); 114 115 if (forceInflate) { 116 mRemoteViewCache.clearCache(entry); 117 } 118 119 // Cancel any pending frees on any view we're trying to bind since we should be bound after. 120 cancelContentViewFrees(row, contentToBind); 121 122 AsyncInflationTask task = new AsyncInflationTask( 123 mBgExecutor, 124 mInflateSynchronously, 125 contentToBind, 126 mRemoteViewCache, 127 entry, 128 mConversationProcessor, 129 row, 130 bindParams.isLowPriority, 131 bindParams.usesIncreasedHeight, 132 bindParams.usesIncreasedHeadsUpHeight, 133 callback, 134 mRemoteInputManager.getRemoteViewsOnClickHandler(), 135 mIsMediaInQS, 136 mSmartReplyStateInflater); 137 if (mInflateSynchronously) { 138 task.onPostExecute(task.doInBackground()); 139 } else { 140 task.executeOnExecutor(mBgExecutor); 141 } 142 } 143 144 @VisibleForTesting inflateNotificationViews( NotificationEntry entry, ExpandableNotificationRow row, BindParams bindParams, boolean inflateSynchronously, @InflationFlag int reInflateFlags, Notification.Builder builder, Context packageContext, SmartReplyStateInflater smartRepliesInflater)145 InflationProgress inflateNotificationViews( 146 NotificationEntry entry, 147 ExpandableNotificationRow row, 148 BindParams bindParams, 149 boolean inflateSynchronously, 150 @InflationFlag int reInflateFlags, 151 Notification.Builder builder, 152 Context packageContext, 153 SmartReplyStateInflater smartRepliesInflater) { 154 InflationProgress result = createRemoteViews(reInflateFlags, 155 builder, 156 bindParams.isLowPriority, 157 bindParams.usesIncreasedHeight, 158 bindParams.usesIncreasedHeadsUpHeight, 159 packageContext); 160 161 result = inflateSmartReplyViews(result, reInflateFlags, entry, 162 row.getContext(), packageContext, 163 row.getExistingSmartReplyState(), 164 smartRepliesInflater); 165 166 apply( 167 mBgExecutor, 168 inflateSynchronously, 169 result, 170 reInflateFlags, 171 mRemoteViewCache, 172 entry, 173 row, 174 mRemoteInputManager.getRemoteViewsOnClickHandler(), 175 null); 176 return result; 177 } 178 179 @Override cancelBind( @onNull NotificationEntry entry, @NonNull ExpandableNotificationRow row)180 public void cancelBind( 181 @NonNull NotificationEntry entry, 182 @NonNull ExpandableNotificationRow row) { 183 entry.abortTask(); 184 } 185 186 @Override unbindContent( @onNull NotificationEntry entry, @NonNull ExpandableNotificationRow row, @InflationFlag int contentToUnbind)187 public void unbindContent( 188 @NonNull NotificationEntry entry, 189 @NonNull ExpandableNotificationRow row, 190 @InflationFlag int contentToUnbind) { 191 int curFlag = 1; 192 while (contentToUnbind != 0) { 193 if ((contentToUnbind & curFlag) != 0) { 194 freeNotificationView(entry, row, curFlag); 195 } 196 contentToUnbind &= ~curFlag; 197 curFlag = curFlag << 1; 198 } 199 } 200 201 /** 202 * Frees the content view associated with the inflation flag as soon as the view is not showing. 203 * 204 * @param inflateFlag the flag corresponding to the content view which should be freed 205 */ freeNotificationView( NotificationEntry entry, ExpandableNotificationRow row, @InflationFlag int inflateFlag)206 private void freeNotificationView( 207 NotificationEntry entry, 208 ExpandableNotificationRow row, 209 @InflationFlag int inflateFlag) { 210 switch (inflateFlag) { 211 case FLAG_CONTENT_VIEW_CONTRACTED: 212 row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> { 213 row.getPrivateLayout().setContractedChild(null); 214 mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED); 215 }); 216 break; 217 case FLAG_CONTENT_VIEW_EXPANDED: 218 row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_EXPANDED, () -> { 219 row.getPrivateLayout().setExpandedChild(null); 220 mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED); 221 }); 222 break; 223 case FLAG_CONTENT_VIEW_HEADS_UP: 224 row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, () -> { 225 row.getPrivateLayout().setHeadsUpChild(null); 226 mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); 227 row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null); 228 }); 229 break; 230 case FLAG_CONTENT_VIEW_PUBLIC: 231 row.getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> { 232 row.getPublicLayout().setContractedChild(null); 233 mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC); 234 }); 235 break; 236 default: 237 break; 238 } 239 } 240 241 /** 242 * Cancel any pending content view frees from {@link #freeNotificationView} for the provided 243 * content views. 244 * 245 * @param row top level notification row containing the content views 246 * @param contentViews content views to cancel pending frees on 247 */ cancelContentViewFrees( ExpandableNotificationRow row, @InflationFlag int contentViews)248 private void cancelContentViewFrees( 249 ExpandableNotificationRow row, 250 @InflationFlag int contentViews) { 251 if ((contentViews & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { 252 row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED); 253 } 254 if ((contentViews & FLAG_CONTENT_VIEW_EXPANDED) != 0) { 255 row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_EXPANDED); 256 } 257 if ((contentViews & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { 258 row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_HEADSUP); 259 } 260 if ((contentViews & FLAG_CONTENT_VIEW_PUBLIC) != 0) { 261 row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED); 262 } 263 } 264 inflateSmartReplyViews( InflationProgress result, @InflationFlag int reInflateFlags, NotificationEntry entry, Context context, Context packageContext, InflatedSmartReplyState previousSmartReplyState, SmartReplyStateInflater inflater)265 private static InflationProgress inflateSmartReplyViews( 266 InflationProgress result, 267 @InflationFlag int reInflateFlags, 268 NotificationEntry entry, 269 Context context, 270 Context packageContext, 271 InflatedSmartReplyState previousSmartReplyState, 272 SmartReplyStateInflater inflater) { 273 boolean inflateContracted = (reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0 274 && result.newContentView != null; 275 boolean inflateExpanded = (reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 276 && result.newExpandedView != null; 277 boolean inflateHeadsUp = (reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 278 && result.newHeadsUpView != null; 279 if (inflateContracted || inflateExpanded || inflateHeadsUp) { 280 result.inflatedSmartReplyState = inflater.inflateSmartReplyState(entry); 281 } 282 if (inflateExpanded) { 283 result.expandedInflatedSmartReplies = inflater.inflateSmartReplyViewHolder( 284 context, packageContext, entry, previousSmartReplyState, 285 result.inflatedSmartReplyState); 286 } 287 if (inflateHeadsUp) { 288 result.headsUpInflatedSmartReplies = inflater.inflateSmartReplyViewHolder( 289 context, packageContext, entry, previousSmartReplyState, 290 result.inflatedSmartReplyState); 291 } 292 return result; 293 } 294 createRemoteViews(@nflationFlag int reInflateFlags, Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, Context packageContext)295 private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags, 296 Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight, 297 boolean usesIncreasedHeadsUpHeight, Context packageContext) { 298 InflationProgress result = new InflationProgress(); 299 300 if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { 301 result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight); 302 } 303 304 if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { 305 result.newExpandedView = createExpandedView(builder, isLowPriority); 306 } 307 308 if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { 309 result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight); 310 } 311 312 if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { 313 result.newPublicView = builder.makePublicContentView(isLowPriority); 314 } 315 316 result.packageContext = packageContext; 317 result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */); 318 result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText( 319 true /* showingPublic */); 320 return result; 321 } 322 apply( Executor bgExecutor, boolean inflateSynchronously, InflationProgress result, @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, NotificationEntry entry, ExpandableNotificationRow row, RemoteViews.InteractionHandler remoteViewClickHandler, @Nullable InflationCallback callback)323 private static CancellationSignal apply( 324 Executor bgExecutor, 325 boolean inflateSynchronously, 326 InflationProgress result, 327 @InflationFlag int reInflateFlags, 328 NotifRemoteViewCache remoteViewCache, 329 NotificationEntry entry, 330 ExpandableNotificationRow row, 331 RemoteViews.InteractionHandler remoteViewClickHandler, 332 @Nullable InflationCallback callback) { 333 NotificationContentView privateLayout = row.getPrivateLayout(); 334 NotificationContentView publicLayout = row.getPublicLayout(); 335 final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>(); 336 337 int flag = FLAG_CONTENT_VIEW_CONTRACTED; 338 if ((reInflateFlags & flag) != 0) { 339 boolean isNewView = 340 !canReapplyRemoteView(result.newContentView, 341 remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)); 342 ApplyCallback applyCallback = new ApplyCallback() { 343 @Override 344 public void setResultView(View v) { 345 result.inflatedContentView = v; 346 } 347 348 @Override 349 public RemoteViews getRemoteView() { 350 return result.newContentView; 351 } 352 }; 353 applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag, 354 remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback, 355 privateLayout, privateLayout.getContractedChild(), 356 privateLayout.getVisibleWrapper( 357 NotificationContentView.VISIBLE_TYPE_CONTRACTED), 358 runningInflations, applyCallback); 359 } 360 361 flag = FLAG_CONTENT_VIEW_EXPANDED; 362 if ((reInflateFlags & flag) != 0) { 363 if (result.newExpandedView != null) { 364 boolean isNewView = 365 !canReapplyRemoteView(result.newExpandedView, 366 remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)); 367 ApplyCallback applyCallback = new ApplyCallback() { 368 @Override 369 public void setResultView(View v) { 370 result.inflatedExpandedView = v; 371 } 372 373 @Override 374 public RemoteViews getRemoteView() { 375 return result.newExpandedView; 376 } 377 }; 378 applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag, 379 remoteViewCache, entry, row, isNewView, remoteViewClickHandler, 380 callback, privateLayout, privateLayout.getExpandedChild(), 381 privateLayout.getVisibleWrapper( 382 NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations, 383 applyCallback); 384 } 385 } 386 387 flag = FLAG_CONTENT_VIEW_HEADS_UP; 388 if ((reInflateFlags & flag) != 0) { 389 if (result.newHeadsUpView != null) { 390 boolean isNewView = 391 !canReapplyRemoteView(result.newHeadsUpView, 392 remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)); 393 ApplyCallback applyCallback = new ApplyCallback() { 394 @Override 395 public void setResultView(View v) { 396 result.inflatedHeadsUpView = v; 397 } 398 399 @Override 400 public RemoteViews getRemoteView() { 401 return result.newHeadsUpView; 402 } 403 }; 404 applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag, 405 remoteViewCache, entry, row, isNewView, remoteViewClickHandler, 406 callback, privateLayout, privateLayout.getHeadsUpChild(), 407 privateLayout.getVisibleWrapper( 408 VISIBLE_TYPE_HEADSUP), runningInflations, 409 applyCallback); 410 } 411 } 412 413 flag = FLAG_CONTENT_VIEW_PUBLIC; 414 if ((reInflateFlags & flag) != 0) { 415 boolean isNewView = 416 !canReapplyRemoteView(result.newPublicView, 417 remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)); 418 ApplyCallback applyCallback = new ApplyCallback() { 419 @Override 420 public void setResultView(View v) { 421 result.inflatedPublicView = v; 422 } 423 424 @Override 425 public RemoteViews getRemoteView() { 426 return result.newPublicView; 427 } 428 }; 429 applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag, 430 remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback, 431 publicLayout, publicLayout.getContractedChild(), 432 publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED), 433 runningInflations, applyCallback); 434 } 435 436 // Let's try to finish, maybe nobody is even inflating anything 437 finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry, 438 row); 439 CancellationSignal cancellationSignal = new CancellationSignal(); 440 cancellationSignal.setOnCancelListener( 441 () -> runningInflations.values().forEach(CancellationSignal::cancel)); 442 return cancellationSignal; 443 } 444 445 @VisibleForTesting applyRemoteView( Executor bgExecutor, boolean inflateSynchronously, 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, NotificationContentView parentLayout, View existingView, NotificationViewWrapper existingWrapper, final HashMap<Integer, CancellationSignal> runningInflations, ApplyCallback applyCallback)446 static void applyRemoteView( 447 Executor bgExecutor, 448 boolean inflateSynchronously, 449 final InflationProgress result, 450 final @InflationFlag int reInflateFlags, 451 @InflationFlag int inflationId, 452 final NotifRemoteViewCache remoteViewCache, 453 final NotificationEntry entry, 454 final ExpandableNotificationRow row, 455 boolean isNewView, 456 RemoteViews.InteractionHandler remoteViewClickHandler, 457 @Nullable final InflationCallback callback, 458 NotificationContentView parentLayout, 459 View existingView, 460 NotificationViewWrapper existingWrapper, 461 final HashMap<Integer, CancellationSignal> runningInflations, 462 ApplyCallback applyCallback) { 463 RemoteViews newContentView = applyCallback.getRemoteView(); 464 if (inflateSynchronously) { 465 try { 466 if (isNewView) { 467 View v = newContentView.apply( 468 result.packageContext, 469 parentLayout, 470 remoteViewClickHandler); 471 v.setIsRootNamespace(true); 472 applyCallback.setResultView(v); 473 } else { 474 newContentView.reapply( 475 result.packageContext, 476 existingView, 477 remoteViewClickHandler); 478 existingWrapper.onReinflated(); 479 } 480 } catch (Exception e) { 481 handleInflationError(runningInflations, e, row.getEntry(), callback); 482 // Add a running inflation to make sure we don't trigger callbacks. 483 // Safe to do because only happens in tests. 484 runningInflations.put(inflationId, new CancellationSignal()); 485 } 486 return; 487 } 488 RemoteViews.OnViewAppliedListener listener = new RemoteViews.OnViewAppliedListener() { 489 490 @Override 491 public void onViewInflated(View v) { 492 if (v instanceof ImageMessageConsumer) { 493 ((ImageMessageConsumer) v).setImageResolver(row.getImageResolver()); 494 } 495 } 496 497 @Override 498 public void onViewApplied(View v) { 499 if (isNewView) { 500 v.setIsRootNamespace(true); 501 applyCallback.setResultView(v); 502 } else if (existingWrapper != null) { 503 existingWrapper.onReinflated(); 504 } 505 runningInflations.remove(inflationId); 506 finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, 507 callback, entry, row); 508 } 509 510 @Override 511 public void onError(Exception e) { 512 // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this could 513 // actually also be a system issue, so let's try on the UI thread again to be safe. 514 try { 515 View newView = existingView; 516 if (isNewView) { 517 newView = newContentView.apply( 518 result.packageContext, 519 parentLayout, 520 remoteViewClickHandler); 521 } else { 522 newContentView.reapply( 523 result.packageContext, 524 existingView, 525 remoteViewClickHandler); 526 } 527 Log.wtf(TAG, "Async Inflation failed but normal inflation finished normally.", 528 e); 529 onViewApplied(newView); 530 } catch (Exception anotherException) { 531 runningInflations.remove(inflationId); 532 handleInflationError(runningInflations, e, row.getEntry(), 533 callback); 534 } 535 } 536 }; 537 CancellationSignal cancellationSignal; 538 if (isNewView) { 539 cancellationSignal = newContentView.applyAsync( 540 result.packageContext, 541 parentLayout, 542 bgExecutor, 543 listener, 544 remoteViewClickHandler); 545 } else { 546 cancellationSignal = newContentView.reapplyAsync( 547 result.packageContext, 548 existingView, 549 bgExecutor, 550 listener, 551 remoteViewClickHandler); 552 } 553 runningInflations.put(inflationId, cancellationSignal); 554 } 555 handleInflationError( HashMap<Integer, CancellationSignal> runningInflations, Exception e, NotificationEntry notification, @Nullable InflationCallback callback)556 private static void handleInflationError( 557 HashMap<Integer, CancellationSignal> runningInflations, Exception e, 558 NotificationEntry notification, @Nullable InflationCallback callback) { 559 Assert.isMainThread(); 560 runningInflations.values().forEach(CancellationSignal::cancel); 561 if (callback != null) { 562 callback.handleInflationException(notification, e); 563 } 564 } 565 566 /** 567 * Finish the inflation of the views 568 * 569 * @return true if the inflation was finished 570 */ finishIfDone(InflationProgress result, @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, HashMap<Integer, CancellationSignal> runningInflations, @Nullable InflationCallback endListener, NotificationEntry entry, ExpandableNotificationRow row)571 private static boolean finishIfDone(InflationProgress result, 572 @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, 573 HashMap<Integer, CancellationSignal> runningInflations, 574 @Nullable InflationCallback endListener, NotificationEntry entry, 575 ExpandableNotificationRow row) { 576 Assert.isMainThread(); 577 NotificationContentView privateLayout = row.getPrivateLayout(); 578 NotificationContentView publicLayout = row.getPublicLayout(); 579 if (runningInflations.isEmpty()) { 580 boolean setRepliesAndActions = true; 581 if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) { 582 if (result.inflatedContentView != null) { 583 // New view case 584 privateLayout.setContractedChild(result.inflatedContentView); 585 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, 586 result.newContentView); 587 } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) { 588 // Reinflation case. Only update if it's still cached (i.e. view has not been 589 // freed while inflating). 590 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED, 591 result.newContentView); 592 } 593 setRepliesAndActions = true; 594 } 595 596 if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { 597 if (result.inflatedExpandedView != null) { 598 privateLayout.setExpandedChild(result.inflatedExpandedView); 599 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, 600 result.newExpandedView); 601 } else if (result.newExpandedView == null) { 602 privateLayout.setExpandedChild(null); 603 remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED); 604 } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) { 605 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED, 606 result.newExpandedView); 607 } 608 if (result.newExpandedView != null) { 609 privateLayout.setExpandedInflatedSmartReplies( 610 result.expandedInflatedSmartReplies); 611 } else { 612 privateLayout.setExpandedInflatedSmartReplies(null); 613 } 614 row.setExpandable(result.newExpandedView != null); 615 setRepliesAndActions = true; 616 } 617 618 if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { 619 if (result.inflatedHeadsUpView != null) { 620 privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); 621 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, 622 result.newHeadsUpView); 623 } else if (result.newHeadsUpView == null) { 624 privateLayout.setHeadsUpChild(null); 625 remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP); 626 } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) { 627 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP, 628 result.newHeadsUpView); 629 } 630 if (result.newHeadsUpView != null) { 631 privateLayout.setHeadsUpInflatedSmartReplies( 632 result.headsUpInflatedSmartReplies); 633 } else { 634 privateLayout.setHeadsUpInflatedSmartReplies(null); 635 } 636 setRepliesAndActions = true; 637 } 638 if (setRepliesAndActions) { 639 privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState); 640 } 641 642 if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { 643 if (result.inflatedPublicView != null) { 644 publicLayout.setContractedChild(result.inflatedPublicView); 645 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, 646 result.newPublicView); 647 } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) { 648 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC, 649 result.newPublicView); 650 } 651 } 652 653 entry.headsUpStatusBarText = result.headsUpStatusBarText; 654 entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; 655 if (endListener != null) { 656 endListener.onAsyncInflationFinished(entry); 657 } 658 return true; 659 } 660 return false; 661 } 662 createExpandedView(Notification.Builder builder, boolean isLowPriority)663 private static RemoteViews createExpandedView(Notification.Builder builder, 664 boolean isLowPriority) { 665 RemoteViews bigContentView = builder.createBigContentView(); 666 if (bigContentView != null) { 667 return bigContentView; 668 } 669 if (isLowPriority) { 670 RemoteViews contentView = builder.createContentView(); 671 Notification.Builder.makeHeaderExpanded(contentView); 672 return contentView; 673 } 674 return null; 675 } 676 createContentView(Notification.Builder builder, boolean isLowPriority, boolean useLarge)677 private static RemoteViews createContentView(Notification.Builder builder, 678 boolean isLowPriority, boolean useLarge) { 679 if (isLowPriority) { 680 return builder.makeLowPriorityContentView(false /* useRegularSubtext */); 681 } 682 return builder.createContentView(useLarge); 683 } 684 685 /** 686 * @param newView The new view that will be applied 687 * @param oldView The old view that was applied to the existing view before 688 * @return {@code true} if the RemoteViews are the same and the view can be reused to reapply. 689 */ 690 @VisibleForTesting canReapplyRemoteView(final RemoteViews newView, final RemoteViews oldView)691 static boolean canReapplyRemoteView(final RemoteViews newView, 692 final RemoteViews oldView) { 693 return (newView == null && oldView == null) || 694 (newView != null && oldView != null 695 && oldView.getPackage() != null 696 && newView.getPackage() != null 697 && newView.getPackage().equals(oldView.getPackage()) 698 && newView.getLayoutId() == oldView.getLayoutId() 699 && !oldView.hasFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED)); 700 } 701 702 /** 703 * Sets whether to perform inflation on the same thread as the caller. This method should only 704 * be used in tests, not in production. 705 */ 706 @VisibleForTesting setInflateSynchronously(boolean inflateSynchronously)707 public void setInflateSynchronously(boolean inflateSynchronously) { 708 mInflateSynchronously = inflateSynchronously; 709 } 710 711 public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress> 712 implements InflationCallback, InflationTask { 713 714 private final NotificationEntry mEntry; 715 private final Context mContext; 716 private final boolean mInflateSynchronously; 717 private final boolean mIsLowPriority; 718 private final boolean mUsesIncreasedHeight; 719 private final InflationCallback mCallback; 720 private final boolean mUsesIncreasedHeadsUpHeight; 721 private final @InflationFlag int mReInflateFlags; 722 private final NotifRemoteViewCache mRemoteViewCache; 723 private final Executor mBgExecutor; 724 private ExpandableNotificationRow mRow; 725 private Exception mError; 726 private RemoteViews.InteractionHandler mRemoteViewClickHandler; 727 private CancellationSignal mCancellationSignal; 728 private final ConversationNotificationProcessor mConversationProcessor; 729 private final boolean mIsMediaInQS; 730 private final SmartReplyStateInflater mSmartRepliesInflater; 731 AsyncInflationTask( Executor bgExecutor, boolean inflateSynchronously, @InflationFlag int reInflateFlags, NotifRemoteViewCache cache, NotificationEntry entry, ConversationNotificationProcessor conversationProcessor, ExpandableNotificationRow row, boolean isLowPriority, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, InflationCallback callback, RemoteViews.InteractionHandler remoteViewClickHandler, boolean isMediaFlagEnabled, SmartReplyStateInflater smartRepliesInflater)732 private AsyncInflationTask( 733 Executor bgExecutor, 734 boolean inflateSynchronously, 735 @InflationFlag int reInflateFlags, 736 NotifRemoteViewCache cache, 737 NotificationEntry entry, 738 ConversationNotificationProcessor conversationProcessor, 739 ExpandableNotificationRow row, 740 boolean isLowPriority, 741 boolean usesIncreasedHeight, 742 boolean usesIncreasedHeadsUpHeight, 743 InflationCallback callback, 744 RemoteViews.InteractionHandler remoteViewClickHandler, 745 boolean isMediaFlagEnabled, 746 SmartReplyStateInflater smartRepliesInflater) { 747 mEntry = entry; 748 mRow = row; 749 mBgExecutor = bgExecutor; 750 mInflateSynchronously = inflateSynchronously; 751 mReInflateFlags = reInflateFlags; 752 mRemoteViewCache = cache; 753 mSmartRepliesInflater = smartRepliesInflater; 754 mContext = mRow.getContext(); 755 mIsLowPriority = isLowPriority; 756 mUsesIncreasedHeight = usesIncreasedHeight; 757 mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; 758 mRemoteViewClickHandler = remoteViewClickHandler; 759 mCallback = callback; 760 mConversationProcessor = conversationProcessor; 761 mIsMediaInQS = isMediaFlagEnabled; 762 entry.setInflationTask(this); 763 } 764 765 @VisibleForTesting 766 @InflationFlag getReInflateFlags()767 public int getReInflateFlags() { 768 return mReInflateFlags; 769 } 770 updateApplicationInfo(StatusBarNotification sbn)771 void updateApplicationInfo(StatusBarNotification sbn) { 772 String packageName = sbn.getPackageName(); 773 int userId = UserHandle.getUserId(sbn.getUid()); 774 final ApplicationInfo appInfo; 775 try { 776 // This method has an internal cache, so we don't need to add our own caching here. 777 appInfo = mContext.getPackageManager().getApplicationInfoAsUser(packageName, 778 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 779 } catch (PackageManager.NameNotFoundException e) { 780 return; 781 } 782 Notification.addFieldsFromContext(appInfo, sbn.getNotification()); 783 } 784 785 @Override doInBackground(Void... params)786 protected InflationProgress doInBackground(Void... params) { 787 try { 788 final StatusBarNotification sbn = mEntry.getSbn(); 789 // Ensure the ApplicationInfo is updated before a builder is recovered. 790 updateApplicationInfo(sbn); 791 final Notification.Builder recoveredBuilder 792 = Notification.Builder.recoverBuilder(mContext, 793 sbn.getNotification()); 794 795 Context packageContext = sbn.getPackageContext(mContext); 796 if (recoveredBuilder.usesTemplate()) { 797 // For all of our templates, we want it to be RTL 798 packageContext = new RtlEnabledContext(packageContext); 799 } 800 if (mEntry.getRanking().isConversation()) { 801 mConversationProcessor.processNotification(mEntry, recoveredBuilder); 802 } 803 InflationProgress inflationProgress = createRemoteViews(mReInflateFlags, 804 recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight, 805 mUsesIncreasedHeadsUpHeight, packageContext); 806 InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState(); 807 return inflateSmartReplyViews( 808 inflationProgress, 809 mReInflateFlags, 810 mEntry, 811 mContext, 812 packageContext, 813 previousSmartReplyState, 814 mSmartRepliesInflater); 815 } catch (Exception e) { 816 mError = e; 817 return null; 818 } 819 } 820 821 @Override onPostExecute(InflationProgress result)822 protected void onPostExecute(InflationProgress result) { 823 if (mError == null) { 824 mCancellationSignal = apply( 825 mBgExecutor, 826 mInflateSynchronously, 827 result, 828 mReInflateFlags, 829 mRemoteViewCache, 830 mEntry, 831 mRow, 832 mRemoteViewClickHandler, 833 this); 834 } else { 835 handleError(mError); 836 } 837 } 838 handleError(Exception e)839 private void handleError(Exception e) { 840 mEntry.onInflationTaskFinished(); 841 StatusBarNotification sbn = mEntry.getSbn(); 842 final String ident = sbn.getPackageName() + "/0x" 843 + Integer.toHexString(sbn.getId()); 844 Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); 845 if (mCallback != null) { 846 mCallback.handleInflationException(mRow.getEntry(), 847 new InflationException("Couldn't inflate contentViews" + e)); 848 } 849 } 850 851 @Override abort()852 public void abort() { 853 cancel(true /* mayInterruptIfRunning */); 854 if (mCancellationSignal != null) { 855 mCancellationSignal.cancel(); 856 } 857 } 858 859 @Override handleInflationException(NotificationEntry entry, Exception e)860 public void handleInflationException(NotificationEntry entry, Exception e) { 861 handleError(e); 862 } 863 864 @Override onAsyncInflationFinished(NotificationEntry entry)865 public void onAsyncInflationFinished(NotificationEntry entry) { 866 mEntry.onInflationTaskFinished(); 867 mRow.onNotificationUpdated(); 868 if (mCallback != null) { 869 mCallback.onAsyncInflationFinished(mEntry); 870 } 871 872 // Notify the resolver that the inflation task has finished, 873 // try to purge unnecessary cached entries. 874 mRow.getImageResolver().purgeCache(); 875 } 876 877 private static class RtlEnabledContext extends ContextWrapper { RtlEnabledContext(Context packageContext)878 private RtlEnabledContext(Context packageContext) { 879 super(packageContext); 880 } 881 882 @Override getApplicationInfo()883 public ApplicationInfo getApplicationInfo() { 884 ApplicationInfo applicationInfo = super.getApplicationInfo(); 885 applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_RTL; 886 return applicationInfo; 887 } 888 } 889 } 890 891 @VisibleForTesting 892 static class InflationProgress { 893 private RemoteViews newContentView; 894 private RemoteViews newHeadsUpView; 895 private RemoteViews newExpandedView; 896 private RemoteViews newPublicView; 897 898 @VisibleForTesting 899 Context packageContext; 900 901 private View inflatedContentView; 902 private View inflatedHeadsUpView; 903 private View inflatedExpandedView; 904 private View inflatedPublicView; 905 private CharSequence headsUpStatusBarText; 906 private CharSequence headsUpStatusBarTextPublic; 907 908 private InflatedSmartReplyState inflatedSmartReplyState; 909 private InflatedSmartReplyViewHolder expandedInflatedSmartReplies; 910 private InflatedSmartReplyViewHolder headsUpInflatedSmartReplies; 911 } 912 913 @VisibleForTesting 914 abstract static class ApplyCallback { setResultView(View v)915 public abstract void setResultView(View v); getRemoteView()916 public abstract RemoteViews getRemoteView(); 917 } 918 } 919