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 NotificationInflater(ExpandableNotificationRow row)71 public NotificationInflater(ExpandableNotificationRow row) { 72 mRow = row; 73 } 74 setIsLowPriority(boolean isLowPriority)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 */ setIsChildInGroup(boolean childInGroup)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 setUsesIncreasedHeight(boolean usesIncreasedHeight)94 public void setUsesIncreasedHeight(boolean usesIncreasedHeight) { 95 mUsesIncreasedHeight = usesIncreasedHeight; 96 } 97 setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight)98 public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) { 99 mUsesIncreasedHeadsUpHeight = usesIncreasedHeight; 100 } 101 setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler)102 public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) { 103 mRemoteViewClickHandler = remoteViewClickHandler; 104 } 105 setRedactAmbient(boolean redactAmbient)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 */ inflateNotificationViews()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 inflateNotificationViews(int reInflateFlags)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 inflateNotificationViews(int reInflateFlags, Notification.Builder builder, Context packageContext)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 createRemoteViews(int reInflateFlags, Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, Context packageContext)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 apply(InflationProgress result, int reInflateFlags, ExpandableNotificationRow row, boolean redactAmbient, RemoteViews.OnClickHandler remoteViewClickHandler, @Nullable InflationCallback callback)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 applyRemoteView(final InflationProgress result, final int reInflateFlags, int inflationId, final ExpandableNotificationRow row, final boolean redactAmbient, boolean isNewView, RemoteViews.OnClickHandler remoteViewClickHandler, @Nullable final InflationCallback callback, NotificationData.Entry entry, NotificationContentView parentLayout, View existingView, NotificationViewWrapper existingWrapper, final HashMap<Integer, CancellationSignal> runningInflations, ApplyCallback applyCallback)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 handleInflationError(HashMap<Integer, CancellationSignal> runningInflations, Exception e, StatusBarNotification notification, @Nullable InflationCallback callback)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 */ finishIfDone(InflationProgress result, int reInflateFlags, HashMap<Integer, CancellationSignal> runningInflations, @Nullable InflationCallback endListener, ExpandableNotificationRow row, boolean redactAmbient)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 createExpandedView(Notification.Builder builder, boolean isLowPriority)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 createContentView(Notification.Builder builder, boolean isLowPriority, boolean useLarge)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. compareRemoteViews(final RemoteViews a, final RemoteViews b)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 setInflationCallback(InflationCallback callback)499 public void setInflationCallback(InflationCallback callback) { 500 mCallback = callback; 501 } 502 503 public interface InflationCallback { handleInflationException(StatusBarNotification notification, Exception e)504 void handleInflationException(StatusBarNotification notification, Exception e); onAsyncInflationFinished(NotificationData.Entry entry)505 void onAsyncInflationFinished(NotificationData.Entry entry); 506 } 507 onDensityOrFontScaleChanged()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 canReapplyAmbient(ExpandableNotificationRow row, boolean redactAmbient)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 AsyncInflationTask(StatusBarNotification notification, int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority, boolean isChildInGroup, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler)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 getReInflateFlags()563 public int getReInflateFlags() { 564 return mReInflateFlags; 565 } 566 567 @Override doInBackground(Void... params)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 onPostExecute(InflationProgress result)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 handleError(Exception e)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 abort()617 public void abort() { 618 cancel(true /* mayInterruptIfRunning */); 619 if (mCancellationSignal != null) { 620 mCancellationSignal.cancel(); 621 } 622 } 623 624 @Override supersedeTask(InflationTask task)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 handleInflationException(StatusBarNotification notification, Exception e)633 public void handleInflationException(StatusBarNotification notification, Exception e) { 634 handleError(e); 635 } 636 637 @Override onAsyncInflationFinished(NotificationData.Entry entry)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 { setResultView(View v)665 public abstract void setResultView(View v); getRemoteView()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 InflationExecutor()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 execute(Runnable runnable)700 public void execute(Runnable runnable) { 701 mExecutor.execute(runnable); 702 } 703 } 704 } 705