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