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; 18 19 import android.annotation.Nullable; 20 import android.app.Notification; 21 import android.content.Context; 22 import android.os.AsyncTask; 23 import android.os.CancellationSignal; 24 import android.service.notification.StatusBarNotification; 25 import android.util.Log; 26 import android.view.View; 27 import android.widget.RemoteViews; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.systemui.R; 31 import com.android.systemui.statusbar.InflationTask; 32 import com.android.systemui.statusbar.ExpandableNotificationRow; 33 import com.android.systemui.statusbar.NotificationContentView; 34 import com.android.systemui.statusbar.NotificationData; 35 import com.android.systemui.statusbar.phone.StatusBar; 36 import com.android.systemui.util.Assert; 37 38 import java.util.HashMap; 39 import java.util.concurrent.Executor; 40 import java.util.concurrent.LinkedBlockingQueue; 41 import java.util.concurrent.ThreadFactory; 42 import java.util.concurrent.ThreadPoolExecutor; 43 import java.util.concurrent.TimeUnit; 44 import java.util.concurrent.atomic.AtomicInteger; 45 46 /** 47 * A utility that inflates the right kind of contentView based on the state 48 */ 49 public class NotificationInflater { 50 51 public static final String TAG = "NotificationInflater"; 52 @VisibleForTesting 53 static final int FLAG_REINFLATE_ALL = ~0; 54 private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0; 55 @VisibleForTesting 56 static final int FLAG_REINFLATE_EXPANDED_VIEW = 1<<1; 57 private static final int FLAG_REINFLATE_HEADS_UP_VIEW = 1<<2; 58 private static final int FLAG_REINFLATE_PUBLIC_VIEW = 1<<3; 59 private static final int FLAG_REINFLATE_AMBIENT_VIEW = 1<<4; 60 private static final InflationExecutor EXECUTOR = new InflationExecutor(); 61 62 private final ExpandableNotificationRow mRow; 63 private boolean mIsLowPriority; 64 private boolean mUsesIncreasedHeight; 65 private boolean mUsesIncreasedHeadsUpHeight; 66 private RemoteViews.OnClickHandler mRemoteViewClickHandler; 67 private boolean mIsChildInGroup; 68 private InflationCallback mCallback; 69 private boolean mRedactAmbient; 70 71 public NotificationInflater(ExpandableNotificationRow row) { 72 mRow = row; 73 } 74 75 public void setIsLowPriority(boolean isLowPriority) { 76 mIsLowPriority = isLowPriority; 77 } 78 79 /** 80 * Set whether the notification is a child in a group 81 * 82 * @return whether the view was re-inflated 83 */ 84 public void setIsChildInGroup(boolean childInGroup) { 85 if (childInGroup != mIsChildInGroup) { 86 mIsChildInGroup = childInGroup; 87 if (mIsLowPriority) { 88 int flags = FLAG_REINFLATE_CONTENT_VIEW | FLAG_REINFLATE_EXPANDED_VIEW; 89 inflateNotificationViews(flags); 90 } 91 } ; 92 } 93 94 public void setUsesIncreasedHeight(boolean usesIncreasedHeight) { 95 mUsesIncreasedHeight = usesIncreasedHeight; 96 } 97 98 public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) { 99 mUsesIncreasedHeadsUpHeight = usesIncreasedHeight; 100 } 101 102 public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { 103 mRemoteViewClickHandler = remoteViewClickHandler; 104 } 105 106 public void setRedactAmbient(boolean redactAmbient) { 107 if (mRedactAmbient != redactAmbient) { 108 mRedactAmbient = redactAmbient; 109 if (mRow.getEntry() == null) { 110 return; 111 } 112 inflateNotificationViews(FLAG_REINFLATE_AMBIENT_VIEW); 113 } 114 } 115 116 /** 117 * Inflate all views of this notification on a background thread. This is asynchronous and will 118 * notify the callback once it's finished. 119 */ 120 public void inflateNotificationViews() { 121 inflateNotificationViews(FLAG_REINFLATE_ALL); 122 } 123 124 /** 125 * Reinflate all views for the specified flags on a background thread. This is asynchronous and 126 * will notify the callback once it's finished. 127 * 128 * @param reInflateFlags flags which views should be reinflated. Use {@link #FLAG_REINFLATE_ALL} 129 * to reinflate all of views. 130 */ 131 @VisibleForTesting 132 void inflateNotificationViews(int reInflateFlags) { 133 if (mRow.isRemoved()) { 134 // We don't want to reinflate anything for removed notifications. Otherwise views might 135 // be readded to the stack, leading to leaks. This may happen with low-priority groups 136 // where the removal of already removed children can lead to a reinflation. 137 return; 138 } 139 StatusBarNotification sbn = mRow.getEntry().notification; 140 AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mRow, 141 mIsLowPriority, 142 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient, 143 mCallback, mRemoteViewClickHandler); 144 if (mCallback != null && mCallback.doInflateSynchronous()) { 145 task.onPostExecute(task.doInBackground()); 146 } else { 147 task.execute(); 148 } 149 } 150 151 @VisibleForTesting 152 InflationProgress inflateNotificationViews(int reInflateFlags, 153 Notification.Builder builder, Context packageContext) { 154 InflationProgress result = createRemoteViews(reInflateFlags, builder, mIsLowPriority, 155 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, 156 mRedactAmbient, packageContext); 157 apply(result, reInflateFlags, mRow, mRedactAmbient, mRemoteViewClickHandler, null); 158 return result; 159 } 160 161 private static InflationProgress createRemoteViews(int reInflateFlags, 162 Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup, 163 boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, 164 Context packageContext) { 165 InflationProgress result = new InflationProgress(); 166 isLowPriority = isLowPriority && !isChildInGroup; 167 if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) { 168 result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight); 169 } 170 171 if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) { 172 result.newExpandedView = createExpandedView(builder, isLowPriority); 173 } 174 175 if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) { 176 result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight); 177 } 178 179 if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) { 180 result.newPublicView = builder.makePublicContentView(); 181 } 182 183 if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) { 184 result.newAmbientView = redactAmbient ? builder.makePublicAmbientNotification() 185 : builder.makeAmbientNotification(); 186 } 187 result.packageContext = packageContext; 188 result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */); 189 result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText( 190 true /* showingPublic */); 191 return result; 192 } 193 194 public static CancellationSignal apply(InflationProgress result, int reInflateFlags, 195 ExpandableNotificationRow row, boolean redactAmbient, 196 RemoteViews.OnClickHandler remoteViewClickHandler, 197 @Nullable InflationCallback callback) { 198 NotificationData.Entry entry = row.getEntry(); 199 NotificationContentView privateLayout = row.getPrivateLayout(); 200 NotificationContentView publicLayout = row.getPublicLayout(); 201 final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>(); 202 203 int flag = FLAG_REINFLATE_CONTENT_VIEW; 204 if ((reInflateFlags & flag) != 0) { 205 boolean isNewView = !canReapplyRemoteView(result.newContentView, entry.cachedContentView); 206 ApplyCallback applyCallback = new ApplyCallback() { 207 @Override 208 public void setResultView(View v) { 209 result.inflatedContentView = v; 210 } 211 212 @Override 213 public RemoteViews getRemoteView() { 214 return result.newContentView; 215 } 216 }; 217 applyRemoteView(result, reInflateFlags, flag, row, redactAmbient, 218 isNewView, remoteViewClickHandler, callback, entry, privateLayout, 219 privateLayout.getContractedChild(), privateLayout.getVisibleWrapper( 220 NotificationContentView.VISIBLE_TYPE_CONTRACTED), 221 runningInflations, applyCallback); 222 } 223 224 flag = FLAG_REINFLATE_EXPANDED_VIEW; 225 if ((reInflateFlags & flag) != 0) { 226 if (result.newExpandedView != null) { 227 boolean isNewView = !canReapplyRemoteView(result.newExpandedView, 228 entry.cachedBigContentView); 229 ApplyCallback applyCallback = new ApplyCallback() { 230 @Override 231 public void setResultView(View v) { 232 result.inflatedExpandedView = v; 233 } 234 235 @Override 236 public RemoteViews getRemoteView() { 237 return result.newExpandedView; 238 } 239 }; 240 applyRemoteView(result, reInflateFlags, flag, row, 241 redactAmbient, isNewView, remoteViewClickHandler, callback, entry, 242 privateLayout, privateLayout.getExpandedChild(), 243 privateLayout.getVisibleWrapper( 244 NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations, 245 applyCallback); 246 } 247 } 248 249 flag = FLAG_REINFLATE_HEADS_UP_VIEW; 250 if ((reInflateFlags & flag) != 0) { 251 if (result.newHeadsUpView != null) { 252 boolean isNewView = !canReapplyRemoteView(result.newHeadsUpView, 253 entry.cachedHeadsUpContentView); 254 ApplyCallback applyCallback = new ApplyCallback() { 255 @Override 256 public void setResultView(View v) { 257 result.inflatedHeadsUpView = v; 258 } 259 260 @Override 261 public RemoteViews getRemoteView() { 262 return result.newHeadsUpView; 263 } 264 }; 265 applyRemoteView(result, reInflateFlags, flag, row, 266 redactAmbient, isNewView, remoteViewClickHandler, callback, entry, 267 privateLayout, privateLayout.getHeadsUpChild(), 268 privateLayout.getVisibleWrapper( 269 NotificationContentView.VISIBLE_TYPE_HEADSUP), runningInflations, 270 applyCallback); 271 } 272 } 273 274 flag = FLAG_REINFLATE_PUBLIC_VIEW; 275 if ((reInflateFlags & flag) != 0) { 276 boolean isNewView = !canReapplyRemoteView(result.newPublicView, 277 entry.cachedPublicContentView); 278 ApplyCallback applyCallback = new ApplyCallback() { 279 @Override 280 public void setResultView(View v) { 281 result.inflatedPublicView = v; 282 } 283 284 @Override 285 public RemoteViews getRemoteView() { 286 return result.newPublicView; 287 } 288 }; 289 applyRemoteView(result, reInflateFlags, flag, row, 290 redactAmbient, isNewView, remoteViewClickHandler, callback, entry, 291 publicLayout, publicLayout.getContractedChild(), 292 publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED), 293 runningInflations, applyCallback); 294 } 295 296 flag = FLAG_REINFLATE_AMBIENT_VIEW; 297 if ((reInflateFlags & flag) != 0) { 298 NotificationContentView newParent = redactAmbient ? publicLayout : privateLayout; 299 boolean isNewView = !canReapplyAmbient(row, redactAmbient) || 300 !canReapplyRemoteView(result.newAmbientView, entry.cachedAmbientContentView); 301 ApplyCallback applyCallback = new ApplyCallback() { 302 @Override 303 public void setResultView(View v) { 304 result.inflatedAmbientView = v; 305 } 306 307 @Override 308 public RemoteViews getRemoteView() { 309 return result.newAmbientView; 310 } 311 }; 312 applyRemoteView(result, reInflateFlags, flag, row, 313 redactAmbient, isNewView, remoteViewClickHandler, callback, entry, 314 newParent, newParent.getAmbientChild(), newParent.getVisibleWrapper( 315 NotificationContentView.VISIBLE_TYPE_AMBIENT), runningInflations, 316 applyCallback); 317 } 318 319 // Let's try to finish, maybe nobody is even inflating anything 320 finishIfDone(result, reInflateFlags, runningInflations, callback, row, 321 redactAmbient); 322 CancellationSignal cancellationSignal = new CancellationSignal(); 323 cancellationSignal.setOnCancelListener( 324 () -> runningInflations.values().forEach(CancellationSignal::cancel)); 325 return cancellationSignal; 326 } 327 328 @VisibleForTesting 329 static void applyRemoteView(final InflationProgress result, 330 final int reInflateFlags, int inflationId, 331 final ExpandableNotificationRow row, 332 final boolean redactAmbient, boolean isNewView, 333 RemoteViews.OnClickHandler remoteViewClickHandler, 334 @Nullable final InflationCallback callback, NotificationData.Entry entry, 335 NotificationContentView parentLayout, View existingView, 336 NotificationViewWrapper existingWrapper, 337 final HashMap<Integer, CancellationSignal> runningInflations, 338 ApplyCallback applyCallback) { 339 RemoteViews newContentView = applyCallback.getRemoteView(); 340 if (callback != null && callback.doInflateSynchronous()) { 341 try { 342 if (isNewView) { 343 View v = newContentView.apply( 344 result.packageContext, 345 parentLayout, 346 remoteViewClickHandler); 347 v.setIsRootNamespace(true); 348 applyCallback.setResultView(v); 349 } else { 350 newContentView.reapply( 351 result.packageContext, 352 existingView, 353 remoteViewClickHandler); 354 existingWrapper.onReinflated(); 355 } 356 } catch (Exception e) { 357 handleInflationError(runningInflations, e, entry.notification, callback); 358 // Add a running inflation to make sure we don't trigger callbacks. 359 // Safe to do because only happens in tests. 360 runningInflations.put(inflationId, new CancellationSignal()); 361 } 362 return; 363 } 364 RemoteViews.OnViewAppliedListener listener 365 = new RemoteViews.OnViewAppliedListener() { 366 367 @Override 368 public void onViewApplied(View v) { 369 if (isNewView) { 370 v.setIsRootNamespace(true); 371 applyCallback.setResultView(v); 372 } else if (existingWrapper != null) { 373 existingWrapper.onReinflated(); 374 } 375 runningInflations.remove(inflationId); 376 finishIfDone(result, reInflateFlags, runningInflations, callback, row, 377 redactAmbient); 378 } 379 380 @Override 381 public void onError(Exception e) { 382 // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this could 383 // actually also be a system issue, so let's try on the UI thread again to be safe. 384 try { 385 View newView = existingView; 386 if (isNewView) { 387 newView = newContentView.apply( 388 result.packageContext, 389 parentLayout, 390 remoteViewClickHandler); 391 } else { 392 newContentView.reapply( 393 result.packageContext, 394 existingView, 395 remoteViewClickHandler); 396 } 397 Log.wtf(TAG, "Async Inflation failed but normal inflation finished normally.", 398 e); 399 onViewApplied(newView); 400 } catch (Exception anotherException) { 401 runningInflations.remove(inflationId); 402 handleInflationError(runningInflations, e, entry.notification, callback); 403 } 404 } 405 }; 406 CancellationSignal cancellationSignal; 407 if (isNewView) { 408 cancellationSignal = newContentView.applyAsync( 409 result.packageContext, 410 parentLayout, 411 EXECUTOR, 412 listener, 413 remoteViewClickHandler); 414 } else { 415 cancellationSignal = newContentView.reapplyAsync( 416 result.packageContext, 417 existingView, 418 EXECUTOR, 419 listener, 420 remoteViewClickHandler); 421 } 422 runningInflations.put(inflationId, cancellationSignal); 423 } 424 425 private static void handleInflationError(HashMap<Integer, CancellationSignal> runningInflations, 426 Exception e, StatusBarNotification notification, @Nullable InflationCallback callback) { 427 Assert.isMainThread(); 428 runningInflations.values().forEach(CancellationSignal::cancel); 429 if (callback != null) { 430 callback.handleInflationException(notification, e); 431 } 432 } 433 434 /** 435 * Finish the inflation of the views 436 * 437 * @return true if the inflation was finished 438 */ 439 private static boolean finishIfDone(InflationProgress result, int reInflateFlags, 440 HashMap<Integer, CancellationSignal> runningInflations, 441 @Nullable InflationCallback endListener, ExpandableNotificationRow row, 442 boolean redactAmbient) { 443 Assert.isMainThread(); 444 NotificationData.Entry entry = row.getEntry(); 445 NotificationContentView privateLayout = row.getPrivateLayout(); 446 NotificationContentView publicLayout = row.getPublicLayout(); 447 if (runningInflations.isEmpty()) { 448 if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) { 449 if (result.inflatedContentView != null) { 450 privateLayout.setContractedChild(result.inflatedContentView); 451 } 452 entry.cachedContentView = result.newContentView; 453 } 454 455 if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) { 456 if (result.inflatedExpandedView != null) { 457 privateLayout.setExpandedChild(result.inflatedExpandedView); 458 } else if (result.newExpandedView == null) { 459 privateLayout.setExpandedChild(null); 460 } 461 entry.cachedBigContentView = result.newExpandedView; 462 row.setExpandable(result.newExpandedView != null); 463 } 464 465 if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) { 466 if (result.inflatedHeadsUpView != null) { 467 privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); 468 } else if (result.newHeadsUpView == null) { 469 privateLayout.setHeadsUpChild(null); 470 } 471 entry.cachedHeadsUpContentView = result.newHeadsUpView; 472 } 473 474 if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) { 475 if (result.inflatedPublicView != null) { 476 publicLayout.setContractedChild(result.inflatedPublicView); 477 } 478 entry.cachedPublicContentView = result.newPublicView; 479 } 480 481 if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) { 482 if (result.inflatedAmbientView != null) { 483 NotificationContentView newParent = redactAmbient 484 ? publicLayout : privateLayout; 485 NotificationContentView otherParent = !redactAmbient 486 ? publicLayout : privateLayout; 487 newParent.setAmbientChild(result.inflatedAmbientView); 488 otherParent.setAmbientChild(null); 489 } 490 entry.cachedAmbientContentView = result.newAmbientView; 491 } 492 entry.headsUpStatusBarText = result.headsUpStatusBarText; 493 entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; 494 if (endListener != null) { 495 endListener.onAsyncInflationFinished(row.getEntry()); 496 } 497 return true; 498 } 499 return false; 500 } 501 502 private static RemoteViews createExpandedView(Notification.Builder builder, 503 boolean isLowPriority) { 504 RemoteViews bigContentView = builder.createBigContentView(); 505 if (bigContentView != null) { 506 return bigContentView; 507 } 508 if (isLowPriority) { 509 RemoteViews contentView = builder.createContentView(); 510 Notification.Builder.makeHeaderExpanded(contentView); 511 return contentView; 512 } 513 return null; 514 } 515 516 private static RemoteViews createContentView(Notification.Builder builder, 517 boolean isLowPriority, boolean useLarge) { 518 if (isLowPriority) { 519 return builder.makeLowPriorityContentView(false /* useRegularSubtext */); 520 } 521 return builder.createContentView(useLarge); 522 } 523 524 /** 525 * @param newView The new view that will be applied 526 * @param oldView The old view that was applied to the existing view before 527 * @return {@code true} if the RemoteViews are the same and the view can be reused to reapply. 528 */ 529 @VisibleForTesting 530 static boolean canReapplyRemoteView(final RemoteViews newView, 531 final RemoteViews oldView) { 532 return (newView == null && oldView == null) || 533 (newView != null && oldView != null 534 && oldView.getPackage() != null 535 && newView.getPackage() != null 536 && newView.getPackage().equals(oldView.getPackage()) 537 && newView.getLayoutId() == oldView.getLayoutId() 538 && !oldView.isReapplyDisallowed()); 539 } 540 541 public void setInflationCallback(InflationCallback callback) { 542 mCallback = callback; 543 } 544 545 public interface InflationCallback { 546 void handleInflationException(StatusBarNotification notification, Exception e); 547 void onAsyncInflationFinished(NotificationData.Entry entry); 548 549 /** 550 * Used to disable async-ness for tests. Should only be used for tests. 551 */ 552 default boolean doInflateSynchronous() { 553 return false; 554 } 555 } 556 557 public void onDensityOrFontScaleChanged() { 558 NotificationData.Entry entry = mRow.getEntry(); 559 entry.cachedAmbientContentView = null; 560 entry.cachedBigContentView = null; 561 entry.cachedContentView = null; 562 entry.cachedHeadsUpContentView = null; 563 entry.cachedPublicContentView = null; 564 inflateNotificationViews(); 565 } 566 567 private static boolean canReapplyAmbient(ExpandableNotificationRow row, boolean redactAmbient) { 568 NotificationContentView ambientView = redactAmbient ? row.getPublicLayout() 569 : row.getPrivateLayout(); ; 570 return ambientView.getAmbientChild() != null; 571 } 572 573 public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress> 574 implements InflationCallback, InflationTask { 575 576 private final StatusBarNotification mSbn; 577 private final Context mContext; 578 private final boolean mIsLowPriority; 579 private final boolean mIsChildInGroup; 580 private final boolean mUsesIncreasedHeight; 581 private final InflationCallback mCallback; 582 private final boolean mUsesIncreasedHeadsUpHeight; 583 private final boolean mRedactAmbient; 584 private int mReInflateFlags; 585 private ExpandableNotificationRow mRow; 586 private Exception mError; 587 private RemoteViews.OnClickHandler mRemoteViewClickHandler; 588 private CancellationSignal mCancellationSignal; 589 590 private AsyncInflationTask(StatusBarNotification notification, 591 int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority, 592 boolean isChildInGroup, boolean usesIncreasedHeight, 593 boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, 594 InflationCallback callback, 595 RemoteViews.OnClickHandler remoteViewClickHandler) { 596 mRow = row; 597 mSbn = notification; 598 mReInflateFlags = reInflateFlags; 599 mContext = mRow.getContext(); 600 mIsLowPriority = isLowPriority; 601 mIsChildInGroup = isChildInGroup; 602 mUsesIncreasedHeight = usesIncreasedHeight; 603 mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; 604 mRedactAmbient = redactAmbient; 605 mRemoteViewClickHandler = remoteViewClickHandler; 606 mCallback = callback; 607 NotificationData.Entry entry = row.getEntry(); 608 entry.setInflationTask(this); 609 } 610 611 @VisibleForTesting 612 public int getReInflateFlags() { 613 return mReInflateFlags; 614 } 615 616 @Override 617 protected InflationProgress doInBackground(Void... params) { 618 try { 619 final Notification.Builder recoveredBuilder 620 = Notification.Builder.recoverBuilder(mContext, 621 mSbn.getNotification()); 622 Context packageContext = mSbn.getPackageContext(mContext); 623 Notification notification = mSbn.getNotification(); 624 if (notification.isMediaNotification()) { 625 MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, 626 packageContext); 627 processor.processNotification(notification, recoveredBuilder); 628 } 629 return createRemoteViews(mReInflateFlags, 630 recoveredBuilder, mIsLowPriority, mIsChildInGroup, 631 mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient, 632 packageContext); 633 } catch (Exception e) { 634 mError = e; 635 return null; 636 } 637 } 638 639 @Override 640 protected void onPostExecute(InflationProgress result) { 641 if (mError == null) { 642 mCancellationSignal = apply(result, mReInflateFlags, mRow, mRedactAmbient, 643 mRemoteViewClickHandler, this); 644 } else { 645 handleError(mError); 646 } 647 } 648 649 private void handleError(Exception e) { 650 mRow.getEntry().onInflationTaskFinished(); 651 StatusBarNotification sbn = mRow.getStatusBarNotification(); 652 final String ident = sbn.getPackageName() + "/0x" 653 + Integer.toHexString(sbn.getId()); 654 Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); 655 mCallback.handleInflationException(sbn, 656 new InflationException("Couldn't inflate contentViews" + e)); 657 } 658 659 @Override 660 public void abort() { 661 cancel(true /* mayInterruptIfRunning */); 662 if (mCancellationSignal != null) { 663 mCancellationSignal.cancel(); 664 } 665 } 666 667 @Override 668 public void supersedeTask(InflationTask task) { 669 if (task instanceof AsyncInflationTask) { 670 // We want to inflate all flags of the previous task as well 671 mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags; 672 } 673 } 674 675 @Override 676 public void handleInflationException(StatusBarNotification notification, Exception e) { 677 handleError(e); 678 } 679 680 @Override 681 public void onAsyncInflationFinished(NotificationData.Entry entry) { 682 mRow.getEntry().onInflationTaskFinished(); 683 mRow.onNotificationUpdated(); 684 mCallback.onAsyncInflationFinished(mRow.getEntry()); 685 } 686 687 @Override 688 public boolean doInflateSynchronous() { 689 return mCallback != null && mCallback.doInflateSynchronous(); 690 } 691 } 692 693 @VisibleForTesting 694 static class InflationProgress { 695 private RemoteViews newContentView; 696 private RemoteViews newHeadsUpView; 697 private RemoteViews newExpandedView; 698 private RemoteViews newAmbientView; 699 private RemoteViews newPublicView; 700 701 @VisibleForTesting 702 Context packageContext; 703 704 private View inflatedContentView; 705 private View inflatedHeadsUpView; 706 private View inflatedExpandedView; 707 private View inflatedAmbientView; 708 private View inflatedPublicView; 709 private CharSequence headsUpStatusBarText; 710 private CharSequence headsUpStatusBarTextPublic; 711 } 712 713 @VisibleForTesting 714 abstract static class ApplyCallback { 715 public abstract void setResultView(View v); 716 public abstract RemoteViews getRemoteView(); 717 } 718 719 /** 720 * A custom executor that allows more tasks to be queued. Default values are copied from 721 * AsyncTask 722 */ 723 private static class InflationExecutor implements Executor { 724 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 725 // We want at least 2 threads and at most 4 threads in the core pool, 726 // preferring to have 1 less than the CPU count to avoid saturating 727 // the CPU with background work 728 private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); 729 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; 730 private static final int KEEP_ALIVE_SECONDS = 30; 731 732 private static final ThreadFactory sThreadFactory = new ThreadFactory() { 733 private final AtomicInteger mCount = new AtomicInteger(1); 734 735 public Thread newThread(Runnable r) { 736 return new Thread(r, "InflaterThread #" + mCount.getAndIncrement()); 737 } 738 }; 739 740 private final ThreadPoolExecutor mExecutor; 741 742 private InflationExecutor() { 743 mExecutor = new ThreadPoolExecutor( 744 CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, 745 new LinkedBlockingQueue<>(), sThreadFactory); 746 mExecutor.allowCoreThreadTimeOut(true); 747 } 748 749 @Override 750 public void execute(Runnable runnable) { 751 mExecutor.execute(runnable); 752 } 753 } 754 } 755