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