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 = !canReapplyRemoteView(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 = !canReapplyRemoteView(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 = !canReapplyRemoteView(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 = !canReapplyRemoteView(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 !canReapplyRemoteView(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 /** 490 * @param newView The new view that will be applied 491 * @param oldView The old view that was applied to the existing view before 492 * @return {@code true} if the RemoteViews are the same and the view can be reused to reapply. 493 */ 494 @VisibleForTesting 495 static boolean canReapplyRemoteView(final RemoteViews newView, 496 final RemoteViews oldView) { 497 return (newView == null && oldView == null) || 498 (newView != null && oldView != null 499 && oldView.getPackage() != null 500 && newView.getPackage() != null 501 && newView.getPackage().equals(oldView.getPackage()) 502 && newView.getLayoutId() == oldView.getLayoutId() 503 && !oldView.isReapplyDisallowed()); 504 } 505 506 public void setInflationCallback(InflationCallback callback) { 507 mCallback = callback; 508 } 509 510 public interface InflationCallback { 511 void handleInflationException(StatusBarNotification notification, Exception e); 512 void onAsyncInflationFinished(NotificationData.Entry entry); 513 } 514 515 public void onDensityOrFontScaleChanged() { 516 NotificationData.Entry entry = mRow.getEntry(); 517 entry.cachedAmbientContentView = null; 518 entry.cachedBigContentView = null; 519 entry.cachedContentView = null; 520 entry.cachedHeadsUpContentView = null; 521 entry.cachedPublicContentView = null; 522 inflateNotificationViews(); 523 } 524 525 private static boolean canReapplyAmbient(ExpandableNotificationRow row, boolean redactAmbient) { 526 NotificationContentView ambientView = redactAmbient ? row.getPublicLayout() 527 : row.getPrivateLayout(); ; 528 return ambientView.getAmbientChild() != null; 529 } 530 531 public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress> 532 implements InflationCallback, InflationTask { 533 534 private final StatusBarNotification mSbn; 535 private final Context mContext; 536 private final boolean mIsLowPriority; 537 private final boolean mIsChildInGroup; 538 private final boolean mUsesIncreasedHeight; 539 private final InflationCallback mCallback; 540 private final boolean mUsesIncreasedHeadsUpHeight; 541 private final boolean mRedactAmbient; 542 private int mReInflateFlags; 543 private ExpandableNotificationRow mRow; 544 private Exception mError; 545 private RemoteViews.OnClickHandler mRemoteViewClickHandler; 546 private CancellationSignal mCancellationSignal; 547 548 private AsyncInflationTask(StatusBarNotification notification, 549 int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority, 550 boolean isChildInGroup, boolean usesIncreasedHeight, 551 boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, 552 InflationCallback callback, 553 RemoteViews.OnClickHandler remoteViewClickHandler) { 554 mRow = row; 555 mSbn = notification; 556 mReInflateFlags = reInflateFlags; 557 mContext = mRow.getContext(); 558 mIsLowPriority = isLowPriority; 559 mIsChildInGroup = isChildInGroup; 560 mUsesIncreasedHeight = usesIncreasedHeight; 561 mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight; 562 mRedactAmbient = redactAmbient; 563 mRemoteViewClickHandler = remoteViewClickHandler; 564 mCallback = callback; 565 NotificationData.Entry entry = row.getEntry(); 566 entry.setInflationTask(this); 567 } 568 569 @VisibleForTesting 570 public int getReInflateFlags() { 571 return mReInflateFlags; 572 } 573 574 @Override 575 protected InflationProgress doInBackground(Void... params) { 576 try { 577 final Notification.Builder recoveredBuilder 578 = Notification.Builder.recoverBuilder(mContext, 579 mSbn.getNotification()); 580 Context packageContext = mSbn.getPackageContext(mContext); 581 Notification notification = mSbn.getNotification(); 582 if (mIsLowPriority) { 583 int backgroundColor = mContext.getColor( 584 R.color.notification_material_background_low_priority_color); 585 recoveredBuilder.setBackgroundColorHint(backgroundColor); 586 } 587 if (notification.isMediaNotification()) { 588 MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext, 589 packageContext); 590 processor.setIsLowPriority(mIsLowPriority); 591 processor.processNotification(notification, recoveredBuilder); 592 } 593 return createRemoteViews(mReInflateFlags, 594 recoveredBuilder, mIsLowPriority, mIsChildInGroup, 595 mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient, 596 packageContext); 597 } catch (Exception e) { 598 mError = e; 599 return null; 600 } 601 } 602 603 @Override 604 protected void onPostExecute(InflationProgress result) { 605 if (mError == null) { 606 mCancellationSignal = apply(result, mReInflateFlags, mRow, mRedactAmbient, 607 mRemoteViewClickHandler, this); 608 } else { 609 handleError(mError); 610 } 611 } 612 613 private void handleError(Exception e) { 614 mRow.getEntry().onInflationTaskFinished(); 615 StatusBarNotification sbn = mRow.getStatusBarNotification(); 616 final String ident = sbn.getPackageName() + "/0x" 617 + Integer.toHexString(sbn.getId()); 618 Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e); 619 mCallback.handleInflationException(sbn, 620 new InflationException("Couldn't inflate contentViews" + e)); 621 } 622 623 @Override 624 public void abort() { 625 cancel(true /* mayInterruptIfRunning */); 626 if (mCancellationSignal != null) { 627 mCancellationSignal.cancel(); 628 } 629 } 630 631 @Override 632 public void supersedeTask(InflationTask task) { 633 if (task instanceof AsyncInflationTask) { 634 // We want to inflate all flags of the previous task as well 635 mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags; 636 } 637 } 638 639 @Override 640 public void handleInflationException(StatusBarNotification notification, Exception e) { 641 handleError(e); 642 } 643 644 @Override 645 public void onAsyncInflationFinished(NotificationData.Entry entry) { 646 mRow.getEntry().onInflationTaskFinished(); 647 mRow.onNotificationUpdated(); 648 mCallback.onAsyncInflationFinished(mRow.getEntry()); 649 } 650 } 651 652 @VisibleForTesting 653 static class InflationProgress { 654 private RemoteViews newContentView; 655 private RemoteViews newHeadsUpView; 656 private RemoteViews newExpandedView; 657 private RemoteViews newAmbientView; 658 private RemoteViews newPublicView; 659 660 @VisibleForTesting 661 Context packageContext; 662 663 private View inflatedContentView; 664 private View inflatedHeadsUpView; 665 private View inflatedExpandedView; 666 private View inflatedAmbientView; 667 private View inflatedPublicView; 668 } 669 670 @VisibleForTesting 671 abstract static class ApplyCallback { 672 public abstract void setResultView(View v); 673 public abstract RemoteViews getRemoteView(); 674 } 675 676 /** 677 * A custom executor that allows more tasks to be queued. Default values are copied from 678 * AsyncTask 679 */ 680 private static class InflationExecutor implements Executor { 681 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 682 // We want at least 2 threads and at most 4 threads in the core pool, 683 // preferring to have 1 less than the CPU count to avoid saturating 684 // the CPU with background work 685 private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); 686 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; 687 private static final int KEEP_ALIVE_SECONDS = 30; 688 689 private static final ThreadFactory sThreadFactory = new ThreadFactory() { 690 private final AtomicInteger mCount = new AtomicInteger(1); 691 692 public Thread newThread(Runnable r) { 693 return new Thread(r, "InflaterThread #" + mCount.getAndIncrement()); 694 } 695 }; 696 697 private final ThreadPoolExecutor mExecutor; 698 699 private InflationExecutor() { 700 mExecutor = new ThreadPoolExecutor( 701 CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, 702 new LinkedBlockingQueue<>(), sThreadFactory); 703 mExecutor.allowCoreThreadTimeOut(true); 704 } 705 706 @Override 707 public void execute(Runnable runnable) { 708 mExecutor.execute(runnable); 709 } 710 } 711 } 712