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