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