Home | History | Annotate | Download | only in notification
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License
     15  */
     16 
     17 package com.android.systemui.statusbar.notification;
     18 
     19 import android.annotation.Nullable;
     20 import android.app.Notification;
     21 import android.content.Context;
     22 import android.os.AsyncTask;
     23 import android.os.CancellationSignal;
     24 import android.service.notification.StatusBarNotification;
     25 import android.util.Log;
     26 import android.view.View;
     27 import android.widget.RemoteViews;
     28 
     29 import com.android.internal.annotations.VisibleForTesting;
     30 import com.android.systemui.R;
     31 import com.android.systemui.statusbar.InflationTask;
     32 import com.android.systemui.statusbar.ExpandableNotificationRow;
     33 import com.android.systemui.statusbar.NotificationContentView;
     34 import com.android.systemui.statusbar.NotificationData;
     35 import com.android.systemui.statusbar.phone.StatusBar;
     36 import com.android.systemui.util.Assert;
     37 
     38 import java.util.HashMap;
     39 import java.util.concurrent.Executor;
     40 import java.util.concurrent.LinkedBlockingQueue;
     41 import java.util.concurrent.ThreadFactory;
     42 import java.util.concurrent.ThreadPoolExecutor;
     43 import java.util.concurrent.TimeUnit;
     44 import java.util.concurrent.atomic.AtomicInteger;
     45 
     46 /**
     47  * A utility that inflates the right kind of contentView based on the state
     48  */
     49 public class NotificationInflater {
     50 
     51     public static final String TAG = "NotificationInflater";
     52     @VisibleForTesting
     53     static final int FLAG_REINFLATE_ALL = ~0;
     54     private static final int FLAG_REINFLATE_CONTENT_VIEW = 1<<0;
     55     @VisibleForTesting
     56     static final int FLAG_REINFLATE_EXPANDED_VIEW = 1<<1;
     57     private static final int FLAG_REINFLATE_HEADS_UP_VIEW = 1<<2;
     58     private static final int FLAG_REINFLATE_PUBLIC_VIEW = 1<<3;
     59     private static final int FLAG_REINFLATE_AMBIENT_VIEW = 1<<4;
     60     private static final InflationExecutor EXECUTOR = new InflationExecutor();
     61 
     62     private final ExpandableNotificationRow mRow;
     63     private boolean mIsLowPriority;
     64     private boolean mUsesIncreasedHeight;
     65     private boolean mUsesIncreasedHeadsUpHeight;
     66     private RemoteViews.OnClickHandler mRemoteViewClickHandler;
     67     private boolean mIsChildInGroup;
     68     private InflationCallback mCallback;
     69     private boolean mRedactAmbient;
     70 
     71     public NotificationInflater(ExpandableNotificationRow row) {
     72         mRow = row;
     73     }
     74 
     75     public void setIsLowPriority(boolean isLowPriority) {
     76         mIsLowPriority = isLowPriority;
     77     }
     78 
     79     /**
     80      * Set whether the notification is a child in a group
     81      *
     82      * @return whether the view was re-inflated
     83      */
     84     public void setIsChildInGroup(boolean childInGroup) {
     85         if (childInGroup != mIsChildInGroup) {
     86             mIsChildInGroup = childInGroup;
     87             if (mIsLowPriority) {
     88                 int flags = FLAG_REINFLATE_CONTENT_VIEW | FLAG_REINFLATE_EXPANDED_VIEW;
     89                 inflateNotificationViews(flags);
     90             }
     91         } ;
     92     }
     93 
     94     public void setUsesIncreasedHeight(boolean usesIncreasedHeight) {
     95         mUsesIncreasedHeight = usesIncreasedHeight;
     96     }
     97 
     98     public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
     99         mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
    100     }
    101 
    102     public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
    103         mRemoteViewClickHandler = remoteViewClickHandler;
    104     }
    105 
    106     public void setRedactAmbient(boolean redactAmbient) {
    107         if (mRedactAmbient != redactAmbient) {
    108             mRedactAmbient = redactAmbient;
    109             if (mRow.getEntry() == null) {
    110                 return;
    111             }
    112             inflateNotificationViews(FLAG_REINFLATE_AMBIENT_VIEW);
    113         }
    114     }
    115 
    116     /**
    117      * Inflate all views of this notification on a background thread. This is asynchronous and will
    118      * notify the callback once it's finished.
    119      */
    120     public void inflateNotificationViews() {
    121         inflateNotificationViews(FLAG_REINFLATE_ALL);
    122     }
    123 
    124     /**
    125      * Reinflate all views for the specified flags on a background thread. This is asynchronous and
    126      * will notify the callback once it's finished.
    127      *
    128      * @param reInflateFlags flags which views should be reinflated. Use {@link #FLAG_REINFLATE_ALL}
    129      *                       to reinflate all of views.
    130      */
    131     @VisibleForTesting
    132     void inflateNotificationViews(int reInflateFlags) {
    133         if (mRow.isRemoved()) {
    134             // We don't want to reinflate anything for removed notifications. Otherwise views might
    135             // be readded to the stack, leading to leaks. This may happen with low-priority groups
    136             // where the removal of already removed children can lead to a reinflation.
    137             return;
    138         }
    139         StatusBarNotification sbn = mRow.getEntry().notification;
    140         new AsyncInflationTask(sbn, reInflateFlags, mRow, mIsLowPriority,
    141                 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
    142                 mCallback, mRemoteViewClickHandler).execute();
    143     }
    144 
    145     @VisibleForTesting
    146     InflationProgress inflateNotificationViews(int reInflateFlags,
    147             Notification.Builder builder, Context packageContext) {
    148         InflationProgress result = createRemoteViews(reInflateFlags, builder, mIsLowPriority,
    149                 mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
    150                 mRedactAmbient, packageContext);
    151         apply(result, reInflateFlags, mRow, mRedactAmbient, mRemoteViewClickHandler, null);
    152         return result;
    153     }
    154 
    155     private static InflationProgress createRemoteViews(int reInflateFlags,
    156             Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup,
    157             boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
    158             Context packageContext) {
    159         InflationProgress result = new InflationProgress();
    160         isLowPriority = isLowPriority && !isChildInGroup;
    161         if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
    162             result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
    163         }
    164 
    165         if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
    166             result.newExpandedView = createExpandedView(builder, isLowPriority);
    167         }
    168 
    169         if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
    170             result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
    171         }
    172 
    173         if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
    174             result.newPublicView = builder.makePublicContentView();
    175         }
    176 
    177         if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
    178             result.newAmbientView = redactAmbient ? builder.makePublicAmbientNotification()
    179                     : builder.makeAmbientNotification();
    180         }
    181         result.packageContext = packageContext;
    182         return result;
    183     }
    184 
    185     public static CancellationSignal apply(InflationProgress result, int reInflateFlags,
    186             ExpandableNotificationRow row, boolean redactAmbient,
    187             RemoteViews.OnClickHandler remoteViewClickHandler,
    188             @Nullable InflationCallback callback) {
    189         NotificationData.Entry entry = row.getEntry();
    190         NotificationContentView privateLayout = row.getPrivateLayout();
    191         NotificationContentView publicLayout = row.getPublicLayout();
    192         final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
    193 
    194         int flag = FLAG_REINFLATE_CONTENT_VIEW;
    195         if ((reInflateFlags & flag) != 0) {
    196             boolean isNewView = !compareRemoteViews(result.newContentView, entry.cachedContentView);
    197             ApplyCallback applyCallback = new ApplyCallback() {
    198                 @Override
    199                 public void setResultView(View v) {
    200                     result.inflatedContentView = v;
    201                 }
    202 
    203                 @Override
    204                 public RemoteViews getRemoteView() {
    205                     return result.newContentView;
    206                 }
    207             };
    208             applyRemoteView(result, reInflateFlags, flag, row, redactAmbient,
    209                     isNewView, remoteViewClickHandler, callback, entry, privateLayout,
    210                     privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
    211                             NotificationContentView.VISIBLE_TYPE_CONTRACTED),
    212                     runningInflations, applyCallback);
    213         }
    214 
    215         flag = FLAG_REINFLATE_EXPANDED_VIEW;
    216         if ((reInflateFlags & flag) != 0) {
    217             if (result.newExpandedView != null) {
    218                 boolean isNewView = !compareRemoteViews(result.newExpandedView,
    219                         entry.cachedBigContentView);
    220                 ApplyCallback applyCallback = new ApplyCallback() {
    221                     @Override
    222                     public void setResultView(View v) {
    223                         result.inflatedExpandedView = v;
    224                     }
    225 
    226                     @Override
    227                     public RemoteViews getRemoteView() {
    228                         return result.newExpandedView;
    229                     }
    230                 };
    231                 applyRemoteView(result, reInflateFlags, flag, row,
    232                         redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
    233                         privateLayout, privateLayout.getExpandedChild(),
    234                         privateLayout.getVisibleWrapper(
    235                                 NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
    236                         applyCallback);
    237             }
    238         }
    239 
    240         flag = FLAG_REINFLATE_HEADS_UP_VIEW;
    241         if ((reInflateFlags & flag) != 0) {
    242             if (result.newHeadsUpView != null) {
    243                 boolean isNewView = !compareRemoteViews(result.newHeadsUpView,
    244                         entry.cachedHeadsUpContentView);
    245                 ApplyCallback applyCallback = new ApplyCallback() {
    246                     @Override
    247                     public void setResultView(View v) {
    248                         result.inflatedHeadsUpView = v;
    249                     }
    250 
    251                     @Override
    252                     public RemoteViews getRemoteView() {
    253                         return result.newHeadsUpView;
    254                     }
    255                 };
    256                 applyRemoteView(result, reInflateFlags, flag, row,
    257                         redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
    258                         privateLayout, privateLayout.getHeadsUpChild(),
    259                         privateLayout.getVisibleWrapper(
    260                                 NotificationContentView.VISIBLE_TYPE_HEADSUP), runningInflations,
    261                         applyCallback);
    262             }
    263         }
    264 
    265         flag = FLAG_REINFLATE_PUBLIC_VIEW;
    266         if ((reInflateFlags & flag) != 0) {
    267             boolean isNewView = !compareRemoteViews(result.newPublicView,
    268                     entry.cachedPublicContentView);
    269             ApplyCallback applyCallback = new ApplyCallback() {
    270                 @Override
    271                 public void setResultView(View v) {
    272                     result.inflatedPublicView = v;
    273                 }
    274 
    275                 @Override
    276                 public RemoteViews getRemoteView() {
    277                     return result.newPublicView;
    278                 }
    279             };
    280             applyRemoteView(result, reInflateFlags, flag, row,
    281                     redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
    282                     publicLayout, publicLayout.getContractedChild(),
    283                     publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
    284                     runningInflations, applyCallback);
    285         }
    286 
    287         flag = FLAG_REINFLATE_AMBIENT_VIEW;
    288         if ((reInflateFlags & flag) != 0) {
    289             NotificationContentView newParent = redactAmbient ? publicLayout : privateLayout;
    290             boolean isNewView = !canReapplyAmbient(row, redactAmbient) ||
    291                     !compareRemoteViews(result.newAmbientView, entry.cachedAmbientContentView);
    292             ApplyCallback applyCallback = new ApplyCallback() {
    293                 @Override
    294                 public void setResultView(View v) {
    295                     result.inflatedAmbientView = v;
    296                 }
    297 
    298                 @Override
    299                 public RemoteViews getRemoteView() {
    300                     return result.newAmbientView;
    301                 }
    302             };
    303             applyRemoteView(result, reInflateFlags, flag, row,
    304                     redactAmbient, isNewView, remoteViewClickHandler, callback, entry,
    305                     newParent, newParent.getAmbientChild(), newParent.getVisibleWrapper(
    306                             NotificationContentView.VISIBLE_TYPE_AMBIENT), runningInflations,
    307                     applyCallback);
    308         }
    309 
    310         // Let's try to finish, maybe nobody is even inflating anything
    311         finishIfDone(result, reInflateFlags, runningInflations, callback, row,
    312                 redactAmbient);
    313         CancellationSignal cancellationSignal = new CancellationSignal();
    314         cancellationSignal.setOnCancelListener(
    315                 () -> runningInflations.values().forEach(CancellationSignal::cancel));
    316         return cancellationSignal;
    317     }
    318 
    319     @VisibleForTesting
    320     static void applyRemoteView(final InflationProgress result,
    321             final int reInflateFlags, int inflationId,
    322             final ExpandableNotificationRow row,
    323             final boolean redactAmbient, boolean isNewView,
    324             RemoteViews.OnClickHandler remoteViewClickHandler,
    325             @Nullable final InflationCallback callback, NotificationData.Entry entry,
    326             NotificationContentView parentLayout, View existingView,
    327             NotificationViewWrapper existingWrapper,
    328             final HashMap<Integer, CancellationSignal> runningInflations,
    329             ApplyCallback applyCallback) {
    330         RemoteViews newContentView = applyCallback.getRemoteView();
    331         RemoteViews.OnViewAppliedListener listener
    332                 = new RemoteViews.OnViewAppliedListener() {
    333 
    334             @Override
    335             public void onViewApplied(View v) {
    336                 if (isNewView) {
    337                     v.setIsRootNamespace(true);
    338                     applyCallback.setResultView(v);
    339                 } else if (existingWrapper != null) {
    340                     existingWrapper.onReinflated();
    341                 }
    342                 runningInflations.remove(inflationId);
    343                 finishIfDone(result, reInflateFlags, runningInflations, callback, row,
    344                         redactAmbient);
    345             }
    346 
    347             @Override
    348             public void onError(Exception e) {
    349                 // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this could
    350                 // actually also be a system issue, so let's try on the UI thread again to be safe.
    351                 try {
    352                     View newView = existingView;
    353                     if (isNewView) {
    354                         newView = newContentView.apply(
    355                                 result.packageContext,
    356                                 parentLayout,
    357                                 remoteViewClickHandler);
    358                     } else {
    359                         newContentView.reapply(
    360                                 result.packageContext,
    361                                 existingView,
    362                                 remoteViewClickHandler);
    363                     }
    364                     Log.wtf(TAG, "Async Inflation failed but normal inflation finished normally.",
    365                             e);
    366                     onViewApplied(newView);
    367                 } catch (Exception anotherException) {
    368                     runningInflations.remove(inflationId);
    369                     handleInflationError(runningInflations, e, entry.notification, callback);
    370                 }
    371             }
    372         };
    373         CancellationSignal cancellationSignal;
    374         if (isNewView) {
    375             cancellationSignal = newContentView.applyAsync(
    376                     result.packageContext,
    377                     parentLayout,
    378                     EXECUTOR,
    379                     listener,
    380                     remoteViewClickHandler);
    381         } else {
    382             cancellationSignal = newContentView.reapplyAsync(
    383                     result.packageContext,
    384                     existingView,
    385                     EXECUTOR,
    386                     listener,
    387                     remoteViewClickHandler);
    388         }
    389         runningInflations.put(inflationId, cancellationSignal);
    390     }
    391 
    392     private static void handleInflationError(HashMap<Integer, CancellationSignal> runningInflations,
    393             Exception e, StatusBarNotification notification, @Nullable InflationCallback callback) {
    394         Assert.isMainThread();
    395         runningInflations.values().forEach(CancellationSignal::cancel);
    396         if (callback != null) {
    397             callback.handleInflationException(notification, e);
    398         }
    399     }
    400 
    401     /**
    402      * Finish the inflation of the views
    403      *
    404      * @return true if the inflation was finished
    405      */
    406     private static boolean finishIfDone(InflationProgress result, int reInflateFlags,
    407             HashMap<Integer, CancellationSignal> runningInflations,
    408             @Nullable InflationCallback endListener, ExpandableNotificationRow row,
    409             boolean redactAmbient) {
    410         Assert.isMainThread();
    411         NotificationData.Entry entry = row.getEntry();
    412         NotificationContentView privateLayout = row.getPrivateLayout();
    413         NotificationContentView publicLayout = row.getPublicLayout();
    414         if (runningInflations.isEmpty()) {
    415             if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
    416                 if (result.inflatedContentView != null) {
    417                     privateLayout.setContractedChild(result.inflatedContentView);
    418                 }
    419                 entry.cachedContentView = result.newContentView;
    420             }
    421 
    422             if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
    423                 if (result.inflatedExpandedView != null) {
    424                     privateLayout.setExpandedChild(result.inflatedExpandedView);
    425                 } else if (result.newExpandedView == null) {
    426                     privateLayout.setExpandedChild(null);
    427                 }
    428                 entry.cachedBigContentView = result.newExpandedView;
    429                 row.setExpandable(result.newExpandedView != null);
    430             }
    431 
    432             if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
    433                 if (result.inflatedHeadsUpView != null) {
    434                     privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
    435                 } else if (result.newHeadsUpView == null) {
    436                     privateLayout.setHeadsUpChild(null);
    437                 }
    438                 entry.cachedHeadsUpContentView = result.newHeadsUpView;
    439             }
    440 
    441             if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
    442                 if (result.inflatedPublicView != null) {
    443                     publicLayout.setContractedChild(result.inflatedPublicView);
    444                 }
    445                 entry.cachedPublicContentView = result.newPublicView;
    446             }
    447 
    448             if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
    449                 if (result.inflatedAmbientView != null) {
    450                     NotificationContentView newParent = redactAmbient
    451                             ? publicLayout : privateLayout;
    452                     NotificationContentView otherParent = !redactAmbient
    453                             ? publicLayout : privateLayout;
    454                     newParent.setAmbientChild(result.inflatedAmbientView);
    455                     otherParent.setAmbientChild(null);
    456                 }
    457                 entry.cachedAmbientContentView = result.newAmbientView;
    458             }
    459             if (endListener != null) {
    460                 endListener.onAsyncInflationFinished(row.getEntry());
    461             }
    462             return true;
    463         }
    464         return false;
    465     }
    466 
    467     private static RemoteViews createExpandedView(Notification.Builder builder,
    468             boolean isLowPriority) {
    469         RemoteViews bigContentView = builder.createBigContentView();
    470         if (bigContentView != null) {
    471             return bigContentView;
    472         }
    473         if (isLowPriority) {
    474             RemoteViews contentView = builder.createContentView();
    475             Notification.Builder.makeHeaderExpanded(contentView);
    476             return contentView;
    477         }
    478         return null;
    479     }
    480 
    481     private static RemoteViews createContentView(Notification.Builder builder,
    482             boolean isLowPriority, boolean useLarge) {
    483         if (isLowPriority) {
    484             return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
    485         }
    486         return builder.createContentView(useLarge);
    487     }
    488 
    489     // Returns true if the RemoteViews are the same.
    490     private static boolean compareRemoteViews(final RemoteViews a, final RemoteViews b) {
    491         return (a == null && b == null) ||
    492                 (a != null && b != null
    493                         && b.getPackage() != null
    494                         && a.getPackage() != null
    495                         && a.getPackage().equals(b.getPackage())
    496                         && a.getLayoutId() == b.getLayoutId());
    497     }
    498 
    499     public void setInflationCallback(InflationCallback callback) {
    500         mCallback = callback;
    501     }
    502 
    503     public interface InflationCallback {
    504         void handleInflationException(StatusBarNotification notification, Exception e);
    505         void onAsyncInflationFinished(NotificationData.Entry entry);
    506     }
    507 
    508     public void onDensityOrFontScaleChanged() {
    509         NotificationData.Entry entry = mRow.getEntry();
    510         entry.cachedAmbientContentView = null;
    511         entry.cachedBigContentView = null;
    512         entry.cachedContentView = null;
    513         entry.cachedHeadsUpContentView = null;
    514         entry.cachedPublicContentView = null;
    515         inflateNotificationViews();
    516     }
    517 
    518     private static boolean canReapplyAmbient(ExpandableNotificationRow row, boolean redactAmbient) {
    519         NotificationContentView ambientView = redactAmbient ? row.getPublicLayout()
    520                 : row.getPrivateLayout();            ;
    521         return ambientView.getAmbientChild() != null;
    522     }
    523 
    524     public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
    525             implements InflationCallback, InflationTask {
    526 
    527         private final StatusBarNotification mSbn;
    528         private final Context mContext;
    529         private final boolean mIsLowPriority;
    530         private final boolean mIsChildInGroup;
    531         private final boolean mUsesIncreasedHeight;
    532         private final InflationCallback mCallback;
    533         private final boolean mUsesIncreasedHeadsUpHeight;
    534         private final boolean mRedactAmbient;
    535         private int mReInflateFlags;
    536         private ExpandableNotificationRow mRow;
    537         private Exception mError;
    538         private RemoteViews.OnClickHandler mRemoteViewClickHandler;
    539         private CancellationSignal mCancellationSignal;
    540 
    541         private AsyncInflationTask(StatusBarNotification notification,
    542                 int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority,
    543                 boolean isChildInGroup, boolean usesIncreasedHeight,
    544                 boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
    545                 InflationCallback callback,
    546                 RemoteViews.OnClickHandler remoteViewClickHandler) {
    547             mRow = row;
    548             mSbn = notification;
    549             mReInflateFlags = reInflateFlags;
    550             mContext = mRow.getContext();
    551             mIsLowPriority = isLowPriority;
    552             mIsChildInGroup = isChildInGroup;
    553             mUsesIncreasedHeight = usesIncreasedHeight;
    554             mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
    555             mRedactAmbient = redactAmbient;
    556             mRemoteViewClickHandler = remoteViewClickHandler;
    557             mCallback = callback;
    558             NotificationData.Entry entry = row.getEntry();
    559             entry.setInflationTask(this);
    560         }
    561 
    562         @VisibleForTesting
    563         public int getReInflateFlags() {
    564             return mReInflateFlags;
    565         }
    566 
    567         @Override
    568         protected InflationProgress doInBackground(Void... params) {
    569             try {
    570                 final Notification.Builder recoveredBuilder
    571                         = Notification.Builder.recoverBuilder(mContext,
    572                         mSbn.getNotification());
    573                 Context packageContext = mSbn.getPackageContext(mContext);
    574                 Notification notification = mSbn.getNotification();
    575                 if (mIsLowPriority) {
    576                     int backgroundColor = mContext.getColor(
    577                             R.color.notification_material_background_low_priority_color);
    578                     recoveredBuilder.setBackgroundColorHint(backgroundColor);
    579                 }
    580                 if (notification.isMediaNotification()) {
    581                     MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
    582                             packageContext);
    583                     processor.setIsLowPriority(mIsLowPriority);
    584                     processor.processNotification(notification, recoveredBuilder);
    585                 }
    586                 return createRemoteViews(mReInflateFlags,
    587                         recoveredBuilder, mIsLowPriority, mIsChildInGroup,
    588                         mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
    589                         packageContext);
    590             } catch (Exception e) {
    591                 mError = e;
    592                 return null;
    593             }
    594         }
    595 
    596         @Override
    597         protected void onPostExecute(InflationProgress result) {
    598             if (mError == null) {
    599                 mCancellationSignal = apply(result, mReInflateFlags, mRow, mRedactAmbient,
    600                         mRemoteViewClickHandler, this);
    601             } else {
    602                 handleError(mError);
    603             }
    604         }
    605 
    606         private void handleError(Exception e) {
    607             mRow.getEntry().onInflationTaskFinished();
    608             StatusBarNotification sbn = mRow.getStatusBarNotification();
    609             final String ident = sbn.getPackageName() + "/0x"
    610                     + Integer.toHexString(sbn.getId());
    611             Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
    612             mCallback.handleInflationException(sbn,
    613                     new InflationException("Couldn't inflate contentViews" + e));
    614         }
    615 
    616         @Override
    617         public void abort() {
    618             cancel(true /* mayInterruptIfRunning */);
    619             if (mCancellationSignal != null) {
    620                 mCancellationSignal.cancel();
    621             }
    622         }
    623 
    624         @Override
    625         public void supersedeTask(InflationTask task) {
    626             if (task instanceof AsyncInflationTask) {
    627                 // We want to inflate all flags of the previous task as well
    628                 mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags;
    629             }
    630         }
    631 
    632         @Override
    633         public void handleInflationException(StatusBarNotification notification, Exception e) {
    634             handleError(e);
    635         }
    636 
    637         @Override
    638         public void onAsyncInflationFinished(NotificationData.Entry entry) {
    639             mRow.getEntry().onInflationTaskFinished();
    640             mRow.onNotificationUpdated();
    641             mCallback.onAsyncInflationFinished(mRow.getEntry());
    642         }
    643     }
    644 
    645     @VisibleForTesting
    646     static class InflationProgress {
    647         private RemoteViews newContentView;
    648         private RemoteViews newHeadsUpView;
    649         private RemoteViews newExpandedView;
    650         private RemoteViews newAmbientView;
    651         private RemoteViews newPublicView;
    652 
    653         @VisibleForTesting
    654         Context packageContext;
    655 
    656         private View inflatedContentView;
    657         private View inflatedHeadsUpView;
    658         private View inflatedExpandedView;
    659         private View inflatedAmbientView;
    660         private View inflatedPublicView;
    661     }
    662 
    663     @VisibleForTesting
    664     abstract static class ApplyCallback {
    665         public abstract void setResultView(View v);
    666         public abstract RemoteViews getRemoteView();
    667     }
    668 
    669     /**
    670      * A custom executor that allows more tasks to be queued. Default values are copied from
    671      * AsyncTask
    672       */
    673     private static class InflationExecutor implements Executor {
    674         private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    675         // We want at least 2 threads and at most 4 threads in the core pool,
    676         // preferring to have 1 less than the CPU count to avoid saturating
    677         // the CPU with background work
    678         private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    679         private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    680         private static final int KEEP_ALIVE_SECONDS = 30;
    681 
    682         private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    683             private final AtomicInteger mCount = new AtomicInteger(1);
    684 
    685             public Thread newThread(Runnable r) {
    686                 return new Thread(r, "InflaterThread #" + mCount.getAndIncrement());
    687             }
    688         };
    689 
    690         private final ThreadPoolExecutor mExecutor;
    691 
    692         private InflationExecutor() {
    693             mExecutor = new ThreadPoolExecutor(
    694                     CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
    695                     new LinkedBlockingQueue<>(), sThreadFactory);
    696             mExecutor.allowCoreThreadTimeOut(true);
    697         }
    698 
    699         @Override
    700         public void execute(Runnable runnable) {
    701             mExecutor.execute(runnable);
    702         }
    703     }
    704 }
    705