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 new AsyncInflationTask(sbn, reInflateFlags, mRow, mIsLowPriority, 141 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient, 142 mCallback, mRemoteViewClickHandler).execute(); 143 } 144 145 @VisibleForTesting 146 InflationProgress inflateNotificationViews(int reInflateFlags, 147 Notification.Builder builder, Context packageContext) { 148 InflationProgress result = createRemoteViews(reInflateFlags, builder, mIsLowPriority, 149 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, 150 mRedactAmbient, packageContext); 151 apply(result, reInflateFlags, mRow, mRedactAmbient, mRemoteViewClickHandler, null); 152 return result; 153 } 154 155 private static InflationProgress createRemoteViews(int reInflateFlags, 156 Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup, 157 boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, 158 Context packageContext) { 159 InflationProgress result = new InflationProgress(); 160 isLowPriority = isLowPriority && !isChildInGroup; 161 if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) { 162 result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight); 163 } 164 165 if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) { 166 result.newExpandedView = createExpandedView(builder, isLowPriority); 167 } 168 169 if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) { 170 result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight); 171 } 172 173 if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) { 174 result.newPublicView = builder.makePublicContentView(); 175 } 176 177 if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) { 178 result.newAmbientView = redactAmbient ? builder.makePublicAmbientNotification() 179 : builder.makeAmbientNotification(); 180 } 181 result.packageContext = packageContext; 182 return result; 183 } 184 185 public static CancellationSignal apply(InflationProgress result, int reInflateFlags, 186 ExpandableNotificationRow row, boolean redactAmbient, 187 RemoteViews.OnClickHandler remoteViewClickHandler, 188 @Nullable InflationCallback callback) { 189 NotificationData.Entry entry = row.getEntry(); 190 NotificationContentView privateLayout = row.getPrivateLayout(); 191 NotificationContentView publicLayout = row.getPublicLayout(); 192 final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>(); 193 194 int flag = FLAG_REINFLATE_CONTENT_VIEW; 195 if ((reInflateFlags & flag) != 0) { 196 boolean isNewView = !compareRemoteViews(result.newContentView, entry.cachedContentView); 197 ApplyCallback applyCallback = new ApplyCallback() { 198 @Override 199 public void setResultView(View v) { 200 result.inflatedContentView = v; 201 } 202 203 @Override 204 public RemoteViews getRemoteView() { 205 return result.newContentView; 206 } 207 }; 208 applyRemoteView(result, reInflateFlags, flag, row, redactAmbient, 209 isNewView, remoteViewClickHandler, callback, entry, privateLayout, 210 privateLayout.getContractedChild(), privateLayout.getVisibleWrapper( 211 NotificationContentView.VISIBLE_TYPE_CONTRACTED), 212 runningInflations, applyCallback); 213 } 214 215 flag = FLAG_REINFLATE_EXPANDED_VIEW; 216 if ((reInflateFlags & flag) != 0) { 217 if (result.newExpandedView != null) { 218 boolean isNewView = !compareRemoteViews(result.newExpandedView, 219 entry.cachedBigContentView); 220 ApplyCallback applyCallback = new ApplyCallback() { 221 @Override 222 public void setResultView(View v) { 223 result.inflatedExpandedView = v; 224 } 225 226 @Override 227 public RemoteViews getRemoteView() { 228 return result.newExpandedView; 229 } 230 }; 231 applyRemoteView(result, reInflateFlags, flag, row, 232 redactAmbient, isNewView, remoteViewClickHandler, callback, entry, 233 privateLayout, privateLayout.getExpandedChild(), 234 privateLayout.getVisibleWrapper( 235 NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations, 236 applyCallback); 237 } 238 } 239 240 flag = FLAG_REINFLATE_HEADS_UP_VIEW; 241 if ((reInflateFlags & flag) != 0) { 242 if (result.newHeadsUpView != null) { 243 boolean isNewView = !compareRemoteViews(result.newHeadsUpView, 244 entry.cachedHeadsUpContentView); 245 ApplyCallback applyCallback = new ApplyCallback() { 246 @Override 247 public void setResultView(View v) { 248 result.inflatedHeadsUpView = v; 249 } 250 251 @Override 252 public RemoteViews getRemoteView() { 253 return result.newHeadsUpView; 254 } 255 }; 256 applyRemoteView(result, reInflateFlags, flag, row, 257 redactAmbient, isNewView, remoteViewClickHandler, callback, entry, 258 privateLayout, privateLayout.getHeadsUpChild(), 259 privateLayout.getVisibleWrapper( 260 NotificationContentView.VISIBLE_TYPE_HEADSUP), runningInflations, 261 applyCallback); 262 } 263 } 264 265 flag = FLAG_REINFLATE_PUBLIC_VIEW; 266 if ((reInflateFlags & flag) != 0) { 267 boolean isNewView = !compareRemoteViews(result.newPublicView, 268 entry.cachedPublicContentView); 269 ApplyCallback applyCallback = new ApplyCallback() { 270 @Override 271 public void setResultView(View v) { 272 result.inflatedPublicView = v; 273 } 274 275 @Override 276 public RemoteViews getRemoteView() { 277 return result.newPublicView; 278 } 279 }; 280 applyRemoteView(result, reInflateFlags, flag, row, 281 redactAmbient, isNewView, remoteViewClickHandler, callback, entry, 282 publicLayout, publicLayout.getContractedChild(), 283 publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED), 284 runningInflations, applyCallback); 285 } 286 287 flag = FLAG_REINFLATE_AMBIENT_VIEW; 288 if ((reInflateFlags & flag) != 0) { 289 NotificationContentView newParent = redactAmbient ? publicLayout : privateLayout; 290 boolean isNewView = !canReapplyAmbient(row, redactAmbient) || 291 !compareRemoteViews(result.newAmbientView, entry.cachedAmbientContentView); 292 ApplyCallback applyCallback = new ApplyCallback() { 293 @Override 294 public void setResultView(View v) { 295 result.inflatedAmbientView = v; 296 } 297 298 @Override 299 public RemoteViews getRemoteView() { 300 return result.newAmbientView; 301 } 302 }; 303 applyRemoteView(result, reInflateFlags, flag, row, 304 redactAmbient, isNewView, remoteViewClickHandler, callback, entry, 305 newParent, newParent.getAmbientChild(), newParent.getVisibleWrapper( 306 NotificationContentView.VISIBLE_TYPE_AMBIENT), runningInflations, 307 applyCallback); 308 } 309 310 // Let's try to finish, maybe nobody is even inflating anything 311 finishIfDone(result, reInflateFlags, runningInflations, callback, row, 312 redactAmbient); 313 CancellationSignal cancellationSignal = new CancellationSignal(); 314 cancellationSignal.setOnCancelListener( 315 () -> runningInflations.values().forEach(CancellationSignal::cancel)); 316 return cancellationSignal; 317 } 318 319 @VisibleForTesting 320 static void applyRemoteView(final InflationProgress result, 321 final int reInflateFlags, int inflationId, 322 final ExpandableNotificationRow row, 323 final boolean redactAmbient, boolean isNewView, 324 RemoteViews.OnClickHandler remoteViewClickHandler, 325 @Nullable final InflationCallback callback, NotificationData.Entry entry, 326 NotificationContentView parentLayout, View existingView, 327 NotificationViewWrapper existingWrapper, 328 final HashMap<Integer, CancellationSignal> runningInflations, 329 ApplyCallback applyCallback) { 330 RemoteViews newContentView = applyCallback.getRemoteView(); 331 RemoteViews.OnViewAppliedListener listener 332 = new RemoteViews.OnViewAppliedListener() { 333 334 @Override 335 public void onViewApplied(View v) { 336 if (isNewView) { 337 v.setIsRootNamespace(true); 338 applyCallback.setResultView(v); 339 } else if (existingWrapper != null) { 340 existingWrapper.onReinflated(); 341 } 342 runningInflations.remove(inflationId); 343 finishIfDone(result, reInflateFlags, runningInflations, callback, row, 344 redactAmbient); 345 } 346 347 @Override 348 public void onError(Exception e) { 349 // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this could 350 // actually also be a system issue, so let's try on the UI thread again to be safe. 351 try { 352 View newView = existingView; 353 if (isNewView) { 354 newView = newContentView.apply( 355 result.packageContext, 356 parentLayout, 357 remoteViewClickHandler); 358 } else { 359 newContentView.reapply( 360 result.packageContext, 361 existingView, 362 remoteViewClickHandler); 363 } 364 Log.wtf(TAG, "Async Inflation failed but normal inflation finished normally.", 365 e); 366 onViewApplied(newView); 367 } catch (Exception anotherException) { 368 runningInflations.remove(inflationId); 369 handleInflationError(runningInflations, e, entry.notification, callback); 370 } 371 } 372 }; 373 CancellationSignal cancellationSignal; 374 if (isNewView) { 375 cancellationSignal = newContentView.applyAsync( 376 result.packageContext, 377 parentLayout, 378 EXECUTOR, 379 listener, 380 remoteViewClickHandler); 381 } else { 382 cancellationSignal = newContentView.reapplyAsync( 383 result.packageContext, 384 existingView, 385 EXECUTOR, 386 listener, 387 remoteViewClickHandler); 388 } 389 runningInflations.put(inflationId, cancellationSignal); 390 } 391 392 private static void handleInflationError(HashMap<Integer, CancellationSignal> runningInflations, 393 Exception e, StatusBarNotification notification, @Nullable InflationCallback callback) { 394 Assert.isMainThread(); 395 runningInflations.values().forEach(CancellationSignal::cancel); 396 if (callback != null) { 397 callback.handleInflationException(notification, e); 398 } 399 } 400 401 /** 402 * Finish the inflation of the views 403 * 404 * @return true if the inflation was finished 405 */ 406 private static boolean finishIfDone(InflationProgress result, int reInflateFlags, 407 HashMap<Integer, CancellationSignal> runningInflations, 408 @Nullable InflationCallback endListener, ExpandableNotificationRow row, 409 boolean redactAmbient) { 410 Assert.isMainThread(); 411 NotificationData.Entry entry = row.getEntry(); 412 NotificationContentView privateLayout = row.getPrivateLayout(); 413 NotificationContentView publicLayout = row.getPublicLayout(); 414 if (runningInflations.isEmpty()) { 415 if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) { 416 if (result.inflatedContentView != null) { 417 privateLayout.setContractedChild(result.inflatedContentView); 418 } 419 entry.cachedContentView = result.newContentView; 420 } 421 422 if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) { 423 if (result.inflatedExpandedView != null) { 424 privateLayout.setExpandedChild(result.inflatedExpandedView); 425 } else if (result.newExpandedView == null) { 426 privateLayout.setExpandedChild(null); 427 } 428 entry.cachedBigContentView = result.newExpandedView; 429 row.setExpandable(result.newExpandedView != null); 430 } 431 432 if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) { 433 if (result.inflatedHeadsUpView != null) { 434 privateLayout.setHeadsUpChild(result.inflatedHeadsUpView); 435 } else if (result.newHeadsUpView == null) { 436 privateLayout.setHeadsUpChild(null); 437 } 438 entry.cachedHeadsUpContentView = result.newHeadsUpView; 439 } 440 441 if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) { 442 if (result.inflatedPublicView != null) { 443 publicLayout.setContractedChild(result.inflatedPublicView); 444 } 445 entry.cachedPublicContentView = result.newPublicView; 446 } 447 448 if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) { 449 if (result.inflatedAmbientView != null) { 450 NotificationContentView newParent = redactAmbient 451 ? publicLayout : privateLayout; 452 NotificationContentView otherParent = !redactAmbient 453 ? publicLayout : privateLayout; 454 newParent.setAmbientChild(result.inflatedAmbientView); 455 otherParent.setAmbientChild(null); 456 } 457 entry.cachedAmbientContentView = result.newAmbientView; 458 } 459 if (endListener != null) { 460 endListener.onAsyncInflationFinished(row.getEntry()); 461 } 462 return true; 463 } 464 return false; 465 } 466 467 private static RemoteViews createExpandedView(Notification.Builder builder, 468 boolean isLowPriority) { 469 RemoteViews bigContentView = builder.createBigContentView(); 470 if (bigContentView != null) { 471 return bigContentView; 472 } 473 if (isLowPriority) { 474 RemoteViews contentView = builder.createContentView(); 475 Notification.Builder.makeHeaderExpanded(contentView); 476 return contentView; 477 } 478 return null; 479 } 480 481 private static RemoteViews createContentView(Notification.Builder builder, 482 boolean isLowPriority, boolean useLarge) { 483 if (isLowPriority) { 484 return builder.makeLowPriorityContentView(false /* useRegularSubtext */); 485 } 486 return builder.createContentView(useLarge); 487 } 488 489 // Returns true if the RemoteViews are the same. 490 private static boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) { 491 return (a == null && b == null) || 492 (a != null && b != null 493 && b.getPackage() != null 494 && a.getPackage() != null 495 && a.getPackage().equals(b.getPackage()) 496 && a.getLayoutId() == b.getLayoutId()); 497 } 498 499 public void setInflationCallback(InflationCallback callback) { 500 mCallback = callback; 501 } 502 503 public interface InflationCallback { 504 void handleInflationException(StatusBarNotification notification, Exception e); 505 void onAsyncInflationFinished(NotificationData.Entry entry); 506 } 507 508 public void onDensityOrFontScaleChanged() { 509 NotificationData.Entry entry = mRow.getEntry(); 510 entry.cachedAmbientContentView = null; 511 entry.cachedBigContentView = null; 512 entry.cachedContentView = null; 513 entry.cachedHeadsUpContentView = null; 514 entry.cachedPublicContentView = null; 515 inflateNotificationViews(); 516 } 517 518 private static boolean canReapplyAmbient(ExpandableNotificationRow row, boolean redactAmbient) { 519 NotificationContentView ambientView = redactAmbient ? row.getPublicLayout() 520 : row.getPrivateLayout(); ; 521 return ambientView.getAmbientChild() != null; 522 } 523 524 public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress> 525 implements InflationCallback, InflationTask { 526 527 private final StatusBarNotification mSbn; 528 private final Context mContext; 529 private final boolean mIsLowPriority; 530 private final boolean mIsChildInGroup; 531 private final boolean mUsesIncreasedHeight; 532 private final InflationCallback mCallback; 533 private final boolean mUsesIncreasedHeadsUpHeight; 534 private final boolean mRedactAmbient; 535 private int mReInflateFlags; 536 private ExpandableNotificationRow mRow; 537 private Exception mError; 538 private RemoteViews.OnClickHandler mRemoteViewClickHandler; 539 private CancellationSignal mCancellationSignal; 540 541 private AsyncInflationTask(StatusBarNotification notification, 542 int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority, 543 boolean isChildInGroup, boolean usesIncreasedHeight, 544 boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, 545 InflationCallback callback, 546 RemoteViews.OnClickHandler remoteViewClickHandler) { 547 mRow = row; 548 mSbn = notification; 549 mReInflateFlags = reInflateFlags; 550 mContext = mRow.getContext(); 551 mIsLowPriority = isLowPriority; 552 mIsChildInGroup = isChildInGroup; 553 mUsesIncreasedHeight = usesIncreasedHeight; 554 mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; 555 mRedactAmbient = redactAmbient; 556 mRemoteViewClickHandler = remoteViewClickHandler; 557 mCallback = callback; 558 NotificationData.Entry entry = row.getEntry(); 559 entry.setInflationTask(this); 560 } 561 562 @VisibleForTesting 563 public int getReInflateFlags() { 564 return mReInflateFlags; 565 } 566 567 @Override 568 protected InflationProgress doInBackground(Void... params) { 569 try { 570 final Notification.Builder recoveredBuilder 571 = Notification.Builder.recoverBuilder(mContext, 572 mSbn.getNotification()); 573 Context packageContext = mSbn.getPackageContext(mContext); 574 Notification notification = mSbn.getNotification(); 575 if (mIsLowPriority) { 576 int backgroundColor = mContext.getColor( 577 R.color.notification_material_background_low_priority_color); 578 recoveredBuilder.setBackgroundColorHint(backgroundColor); 579 } 580 if (notification.isMediaNotification()) { 581 MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, 582 packageContext); 583 processor.setIsLowPriority(mIsLowPriority); 584 processor.processNotification(notification, recoveredBuilder); 585 } 586 return createRemoteViews(mReInflateFlags, 587 recoveredBuilder, mIsLowPriority, mIsChildInGroup, 588 mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient, 589 packageContext); 590 } catch (Exception e) { 591 mError = e; 592 return null; 593 } 594 } 595 596 @Override 597 protected void onPostExecute(InflationProgress result) { 598 if (mError == null) { 599 mCancellationSignal = apply(result, mReInflateFlags, mRow, mRedactAmbient, 600 mRemoteViewClickHandler, this); 601 } else { 602 handleError(mError); 603 } 604 } 605 606 private void handleError(Exception e) { 607 mRow.getEntry().onInflationTaskFinished(); 608 StatusBarNotification sbn = mRow.getStatusBarNotification(); 609 final String ident = sbn.getPackageName() + "/0x" 610 + Integer.toHexString(sbn.getId()); 611 Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); 612 mCallback.handleInflationException(sbn, 613 new InflationException("Couldn't inflate contentViews" + e)); 614 } 615 616 @Override 617 public void abort() { 618 cancel(true /* mayInterruptIfRunning */); 619 if (mCancellationSignal != null) { 620 mCancellationSignal.cancel(); 621 } 622 } 623 624 @Override 625 public void supersedeTask(InflationTask task) { 626 if (task instanceof AsyncInflationTask) { 627 // We want to inflate all flags of the previous task as well 628 mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags; 629 } 630 } 631 632 @Override 633 public void handleInflationException(StatusBarNotification notification, Exception e) { 634 handleError(e); 635 } 636 637 @Override 638 public void onAsyncInflationFinished(NotificationData.Entry entry) { 639 mRow.getEntry().onInflationTaskFinished(); 640 mRow.onNotificationUpdated(); 641 mCallback.onAsyncInflationFinished(mRow.getEntry()); 642 } 643 } 644 645 @VisibleForTesting 646 static class InflationProgress { 647 private RemoteViews newContentView; 648 private RemoteViews newHeadsUpView; 649 private RemoteViews newExpandedView; 650 private RemoteViews newAmbientView; 651 private RemoteViews newPublicView; 652 653 @VisibleForTesting 654 Context packageContext; 655 656 private View inflatedContentView; 657 private View inflatedHeadsUpView; 658 private View inflatedExpandedView; 659 private View inflatedAmbientView; 660 private View inflatedPublicView; 661 } 662 663 @VisibleForTesting 664 abstract static class ApplyCallback { 665 public abstract void setResultView(View v); 666 public abstract RemoteViews getRemoteView(); 667 } 668 669 /** 670 * A custom executor that allows more tasks to be queued. Default values are copied from 671 * AsyncTask 672 */ 673 private static class InflationExecutor implements Executor { 674 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 675 // We want at least 2 threads and at most 4 threads in the core pool, 676 // preferring to have 1 less than the CPU count to avoid saturating 677 // the CPU with background work 678 private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); 679 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; 680 private static final int KEEP_ALIVE_SECONDS = 30; 681 682 private static final ThreadFactory sThreadFactory = new ThreadFactory() { 683 private final AtomicInteger mCount = new AtomicInteger(1); 684 685 public Thread newThread(Runnable r) { 686 return new Thread(r, "InflaterThread #" + mCount.getAndIncrement()); 687 } 688 }; 689 690 private final ThreadPoolExecutor mExecutor; 691 692 private InflationExecutor() { 693 mExecutor = new ThreadPoolExecutor( 694 CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, 695 new LinkedBlockingQueue<>(), sThreadFactory); 696 mExecutor.allowCoreThreadTimeOut(true); 697 } 698 699 @Override 700 public void execute(Runnable runnable) { 701 mExecutor.execute(runnable); 702 } 703 } 704 } 705