• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.row;
18 
19 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
20 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_OTP;
21 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
22 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
23 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
24 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_SINGLELINE;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.Notification;
29 import android.app.Notification.MessagingStyle;
30 import android.content.Context;
31 import android.content.ContextWrapper;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.content.res.Resources;
35 import android.os.AsyncTask;
36 import android.os.Build;
37 import android.os.CancellationSignal;
38 import android.os.Trace;
39 import android.os.UserHandle;
40 import android.service.notification.StatusBarNotification;
41 import android.util.Log;
42 import android.view.NotificationHeaderView;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.widget.RemoteViews;
46 
47 import com.android.app.tracing.TraceUtils;
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.internal.widget.ImageMessageConsumer;
50 import com.android.systemui.dagger.SysUISingleton;
51 import com.android.systemui.dagger.qualifiers.NotifInflation;
52 import com.android.systemui.media.controls.util.MediaFeatureFlag;
53 import com.android.systemui.res.R;
54 import com.android.systemui.statusbar.InflationTask;
55 import com.android.systemui.statusbar.NotificationRemoteInputManager;
56 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
57 import com.android.systemui.statusbar.notification.InflationException;
58 import com.android.systemui.statusbar.notification.NmSummarizationUiFlag;
59 import com.android.systemui.statusbar.notification.NotificationUtils;
60 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
61 import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
62 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
63 import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels;
64 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
65 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
66 import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider;
67 import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
68 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
69 import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder;
70 import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel;
71 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
72 import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
73 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
74 import com.android.systemui.statusbar.phone.CentralSurfaces;
75 import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
76 import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
77 import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
78 import com.android.systemui.util.Assert;
79 
80 import java.util.HashMap;
81 import java.util.concurrent.Executor;
82 
83 import javax.inject.Inject;
84 
85 /**
86  * {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
87  * asynchronously building the content's {@link RemoteViews} and applying it to the row.
88  */
89 @SysUISingleton
90 @VisibleForTesting(visibility = PACKAGE)
91 public class NotificationContentInflater implements NotificationRowContentBinder {
92 
93     public static final String TAG = "NotifContentInflater";
94 
95     private boolean mInflateSynchronously = false;
96     private final boolean mIsMediaInQS;
97     private final NotificationRemoteInputManager mRemoteInputManager;
98     private final NotifRemoteViewCache mRemoteViewCache;
99     private final ConversationNotificationProcessor mConversationProcessor;
100     private final Executor mInflationExecutor;
101     private final SmartReplyStateInflater mSmartReplyStateInflater;
102     private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
103     private final HeadsUpStyleProvider mHeadsUpStyleProvider;
104     private final PromotedNotificationContentExtractor mPromotedNotificationContentExtractor;
105 
106     private final NotificationRowContentBinderLogger mLogger;
107 
108     @Inject
NotificationContentInflater( NotifRemoteViewCache remoteViewCache, NotificationRemoteInputManager remoteInputManager, ConversationNotificationProcessor conversationProcessor, MediaFeatureFlag mediaFeatureFlag, @NotifInflation Executor inflationExecutor, SmartReplyStateInflater smartRepliesInflater, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider, HeadsUpStyleProvider headsUpStyleProvider, PromotedNotificationContentExtractor promotedNotificationContentExtractor, NotificationRowContentBinderLogger logger)109     NotificationContentInflater(
110             NotifRemoteViewCache remoteViewCache,
111             NotificationRemoteInputManager remoteInputManager,
112             ConversationNotificationProcessor conversationProcessor,
113             MediaFeatureFlag mediaFeatureFlag,
114             @NotifInflation Executor inflationExecutor,
115             SmartReplyStateInflater smartRepliesInflater,
116             NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
117             HeadsUpStyleProvider headsUpStyleProvider,
118             PromotedNotificationContentExtractor promotedNotificationContentExtractor,
119             NotificationRowContentBinderLogger logger) {
120         NotificationRowContentBinderRefactor.assertInLegacyMode();
121         mRemoteViewCache = remoteViewCache;
122         mRemoteInputManager = remoteInputManager;
123         mConversationProcessor = conversationProcessor;
124         mIsMediaInQS = mediaFeatureFlag.getEnabled();
125         mInflationExecutor = inflationExecutor;
126         mSmartReplyStateInflater = smartRepliesInflater;
127         mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider;
128         mHeadsUpStyleProvider = headsUpStyleProvider;
129         mPromotedNotificationContentExtractor = promotedNotificationContentExtractor;
130         mLogger = logger;
131     }
132 
133     @Override
bindContent( NotificationEntry entry, ExpandableNotificationRow row, @InflationFlag int contentToBind, BindParams bindParams, boolean forceInflate, @Nullable InflationCallback callback)134     public void bindContent(
135             NotificationEntry entry,
136             ExpandableNotificationRow row,
137             @InflationFlag int contentToBind,
138             BindParams bindParams,
139             boolean forceInflate,
140             @Nullable InflationCallback callback) {
141         if (row.isRemoved()) {
142             // We don't want to reinflate anything for removed notifications. Otherwise views might
143             // be readded to the stack, leading to leaks. This may happen with low-priority groups
144             // where the removal of already removed children can lead to a reinflation.
145             mLogger.logNotBindingRowWasRemoved(row.getLoggingKey());
146             return;
147         }
148 
149         mLogger.logBinding(row.getLoggingKey(), contentToBind);
150 
151         StatusBarNotification sbn = entry.getSbn();
152 
153         // To check if the notification has inline image and preload inline image if necessary.
154         row.getImageResolver().preloadImages(sbn.getNotification());
155 
156         if (forceInflate) {
157             mRemoteViewCache.clearCache(entry);
158         }
159 
160         // Cancel any pending frees on any view we're trying to bind since we should be bound after.
161         cancelContentViewFrees(row, contentToBind);
162 
163         AsyncInflationTask task = new AsyncInflationTask(
164                 mInflationExecutor,
165                 mInflateSynchronously,
166                 /* reInflateFlags = */ contentToBind,
167                 mRemoteViewCache,
168                 entry,
169                 mConversationProcessor,
170                 row,
171                 bindParams,
172                 callback,
173                 mRemoteInputManager.getRemoteViewsOnClickHandler(),
174                 /* isMediaFlagEnabled = */ mIsMediaInQS,
175                 mSmartReplyStateInflater,
176                 mNotifLayoutInflaterFactoryProvider,
177                 mHeadsUpStyleProvider,
178                 mPromotedNotificationContentExtractor,
179                 mLogger);
180         if (mInflateSynchronously) {
181             task.onPostExecute(task.doInBackground());
182         } else {
183             task.executeOnExecutor(mInflationExecutor);
184         }
185     }
186 
187     @VisibleForTesting
inflateNotificationViews( NotificationEntry entry, ExpandableNotificationRow row, BindParams bindParams, boolean inflateSynchronously, @InflationFlag int reInflateFlags, Notification.Builder builder, Context systemUiContext, Context packageContext, SmartReplyStateInflater smartRepliesInflater)188     InflationProgress inflateNotificationViews(
189             NotificationEntry entry,
190             ExpandableNotificationRow row,
191             BindParams bindParams,
192             boolean inflateSynchronously,
193             @InflationFlag int reInflateFlags,
194             Notification.Builder builder,
195             Context systemUiContext,
196             Context packageContext,
197             SmartReplyStateInflater smartRepliesInflater) {
198         InflationProgress result = createRemoteViews(reInflateFlags,
199                 builder,
200                 bindParams,
201                 systemUiContext,
202                 packageContext,
203                 row,
204                 mNotifLayoutInflaterFactoryProvider,
205                 mHeadsUpStyleProvider,
206                 mLogger);
207         result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(),
208                 packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger);
209         boolean isConversation = entry.getRanking().isConversation();
210         Notification.MessagingStyle messagingStyle = null;
211         if (NmSummarizationUiFlag.isEnabled()
212                 || (isConversation && (AsyncHybridViewInflation.isEnabled()
213                 || LockscreenOtpRedaction.isSingleLineViewEnabled()))) {
214             messagingStyle = mConversationProcessor
215                     .processNotification(entry, builder, mLogger);
216         }
217         if (AsyncHybridViewInflation.isEnabled()) {
218             SingleLineViewModel viewModel = SingleLineViewInflater
219                     .inflateSingleLineViewModel(
220                             entry.getSbn().getNotification(),
221                             messagingStyle,
222                             builder,
223                             row.getContext(),
224                             false,
225                             entry.getRanking().getSummarization()
226                     );
227             // If the messagingStyle is null, we want to inflate the normal view
228             isConversation = viewModel.isConversation();
229             result.mInflatedSingleLineViewModel = viewModel;
230             result.mInflatedSingleLineView =
231                     SingleLineViewInflater.inflatePrivateSingleLineView(
232                             isConversation,
233                             reInflateFlags,
234                             entry,
235                             row.getContext(),
236                             mLogger
237                     );
238         }
239         if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
240             if (bindParams.redactionType == REDACTION_TYPE_OTP) {
241                 result.mPublicInflatedSingleLineViewModel =
242                         SingleLineViewInflater.inflateSingleLineViewModel(
243                                 entry.getSbn().getNotification(),
244                                 messagingStyle,
245                                 builder,
246                                 row.getContext(),
247                                 true,
248                                 entry.getRanking().getSummarization());
249             } else {
250                 result.mPublicInflatedSingleLineViewModel =
251                         SingleLineViewInflater.inflatePublicSingleLineViewModel(
252                                 row.getContext(),
253                                 isConversation
254                         );
255             }
256             result.mPublicInflatedSingleLineView =
257                     SingleLineViewInflater.inflatePublicSingleLineView(
258                             isConversation,
259                             reInflateFlags,
260                             entry,
261                             row.getContext(),
262                             mLogger
263                     );
264         }
265 
266         apply(
267                 mInflationExecutor,
268                 inflateSynchronously,
269                 bindParams.isMinimized,
270                 result,
271                 reInflateFlags,
272                 mRemoteViewCache,
273                 entry,
274                 row,
275                 mRemoteInputManager.getRemoteViewsOnClickHandler(),
276                 null /* callback */,
277                 mLogger);
278         return result;
279     }
280 
281     @Override
cancelBind( @onNull NotificationEntry entry, @NonNull ExpandableNotificationRow row)282     public boolean cancelBind(
283             @NonNull NotificationEntry entry,
284             @NonNull ExpandableNotificationRow row) {
285         final boolean abortedTask = entry.abortTask();
286         if (abortedTask) {
287             mLogger.logCancelBindAbortedTask(row.getLoggingKey());
288         }
289         return abortedTask;
290     }
291 
292     @Override
unbindContent( @onNull NotificationEntry entry, @NonNull ExpandableNotificationRow row, @InflationFlag int contentToUnbind)293     public void unbindContent(
294             @NonNull NotificationEntry entry,
295             @NonNull ExpandableNotificationRow row,
296             @InflationFlag int contentToUnbind) {
297         mLogger.logUnbinding(row.getLoggingKey(), contentToUnbind);
298         int curFlag = 1;
299         while (contentToUnbind != 0) {
300             if ((contentToUnbind & curFlag) != 0) {
301                 freeNotificationView(entry, row, curFlag);
302             }
303             contentToUnbind &= ~curFlag;
304             curFlag = curFlag << 1;
305         }
306     }
307 
308     /**
309      * Frees the content view associated with the inflation flag as soon as the view is not showing.
310      *
311      * @param inflateFlag the flag corresponding to the content view which should be freed
312      */
freeNotificationView( NotificationEntry entry, ExpandableNotificationRow row, @InflationFlag int inflateFlag)313     private void freeNotificationView(
314             NotificationEntry entry,
315             ExpandableNotificationRow row,
316             @InflationFlag int inflateFlag) {
317         switch (inflateFlag) {
318             case FLAG_CONTENT_VIEW_CONTRACTED:
319                 row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> {
320                     row.getPrivateLayout().setContractedChild(null);
321                     mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED);
322                 });
323                 break;
324             case FLAG_CONTENT_VIEW_EXPANDED:
325                 row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_EXPANDED, () -> {
326                     row.getPrivateLayout().setExpandedChild(null);
327                     mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
328                 });
329                 break;
330             case FLAG_CONTENT_VIEW_HEADS_UP:
331                 row.getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP, () -> {
332                     row.getPrivateLayout().setHeadsUpChild(null);
333                     mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
334                     row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
335                 });
336                 break;
337             case FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE:
338                 if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
339                     row.getPublicLayout()
340                             .performWhenContentInactive(VISIBLE_TYPE_SINGLELINE, () -> {
341                                 row.getPublicLayout().setSingleLineView(null);
342                                 mRemoteViewCache.removeCachedView(entry,
343                                         FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE);
344                             });
345                 }
346                 break;
347             case FLAG_CONTENT_VIEW_PUBLIC:
348                 row.getPublicLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED, () -> {
349                     row.getPublicLayout().setContractedChild(null);
350                     mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
351                 });
352                 break;
353             case FLAG_CONTENT_VIEW_SINGLE_LINE: {
354                 if (AsyncHybridViewInflation.isEnabled()) {
355                     row.getPrivateLayout().performWhenContentInactive(
356                             VISIBLE_TYPE_SINGLELINE,
357                             () -> row.getPrivateLayout().setSingleLineView(null)
358                     );
359                 }
360                 break;
361             }
362             default:
363                 break;
364         }
365     }
366 
367     /**
368      * Cancel any pending content view frees from {@link #freeNotificationView} for the provided
369      * content views.
370      *
371      * @param row          top level notification row containing the content views
372      * @param contentViews content views to cancel pending frees on
373      */
cancelContentViewFrees( ExpandableNotificationRow row, @InflationFlag int contentViews)374     private void cancelContentViewFrees(
375             ExpandableNotificationRow row,
376             @InflationFlag int contentViews) {
377         if ((contentViews & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
378             row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED);
379         }
380         if ((contentViews & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
381             row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_EXPANDED);
382         }
383         if ((contentViews & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
384             row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_HEADSUP);
385         }
386         if ((contentViews & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
387             row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED);
388         }
389         if (LockscreenOtpRedaction.isSingleLineViewEnabled()
390                 && (contentViews & FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE) != 0) {
391             row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE);
392         }
393         if (AsyncHybridViewInflation.isEnabled()
394                 && (contentViews & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
395             row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE);
396         }
397     }
398 
inflateSmartReplyViews( InflationProgress result, @InflationFlag int reInflateFlags, NotificationEntry entry, Context context, Context packageContext, InflatedSmartReplyState previousSmartReplyState, SmartReplyStateInflater inflater, NotificationRowContentBinderLogger logger)399     private static InflationProgress inflateSmartReplyViews(
400             InflationProgress result,
401             @InflationFlag int reInflateFlags,
402             NotificationEntry entry,
403             Context context,
404             Context packageContext,
405             InflatedSmartReplyState previousSmartReplyState,
406             SmartReplyStateInflater inflater,
407             NotificationRowContentBinderLogger logger) {
408         boolean inflateContracted = (reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0
409                 && result.newContentView != null;
410         boolean inflateExpanded = (reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0
411                 && result.newExpandedView != null;
412         boolean inflateHeadsUp = (reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0
413                 && result.newHeadsUpView != null;
414         String logKey = NotificationUtils.logKey(entry);
415         if (inflateContracted || inflateExpanded || inflateHeadsUp) {
416             logger.logAsyncTaskProgress(logKey, "inflating contracted smart reply state");
417             result.inflatedSmartReplyState = inflater.inflateSmartReplyState(entry);
418         }
419         if (inflateExpanded) {
420             logger.logAsyncTaskProgress(logKey, "inflating expanded smart reply state");
421             result.expandedInflatedSmartReplies = inflater.inflateSmartReplyViewHolder(
422                     context, packageContext, entry, previousSmartReplyState,
423                     result.inflatedSmartReplyState);
424         }
425         if (inflateHeadsUp) {
426             logger.logAsyncTaskProgress(logKey, "inflating heads up smart reply state");
427             result.headsUpInflatedSmartReplies = inflater.inflateSmartReplyViewHolder(
428                     context, packageContext, entry, previousSmartReplyState,
429                     result.inflatedSmartReplyState);
430         }
431         return result;
432     }
433 
createRemoteViews(@nflationFlag int reInflateFlags, Notification.Builder builder, BindParams bindParams, Context systemUiContext, Context packageContext, ExpandableNotificationRow row, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider, HeadsUpStyleProvider headsUpStyleProvider, NotificationRowContentBinderLogger logger)434     private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
435             Notification.Builder builder, BindParams bindParams, Context systemUiContext,
436             Context packageContext,
437             ExpandableNotificationRow row,
438             NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
439             HeadsUpStyleProvider headsUpStyleProvider,
440             NotificationRowContentBinderLogger logger) {
441         return TraceUtils.trace("NotificationContentInflater.createRemoteViews", () -> {
442             InflationProgress result = new InflationProgress();
443 
444             // inflating the contracted view is the legacy invalidation trigger
445             boolean reinflating = (reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0;
446             // create an image inflater
447             result.mRowImageInflater = RowImageInflater.newInstance(row.mImageModelIndex,
448                     reinflating);
449 
450             if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
451                 logger.logAsyncTaskProgress(row.getLoggingKey(), "creating contracted remote view");
452                 result.newContentView = createContentView(builder, bindParams.isMinimized);
453             }
454 
455             if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
456                 logger.logAsyncTaskProgress(row.getLoggingKey(), "creating expanded remote view");
457                 result.newExpandedView = createExpandedView(builder, bindParams.isMinimized);
458             }
459 
460             if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
461                 logger.logAsyncTaskProgress(row.getLoggingKey(), "creating heads up remote view");
462                 final boolean isHeadsUpCompact = headsUpStyleProvider.shouldApplyCompactStyle();
463                 if (isHeadsUpCompact) {
464                     result.newHeadsUpView = builder.createCompactHeadsUpContentView();
465                 } else {
466                     result.newHeadsUpView = builder.createHeadsUpContentView();
467                 }
468             }
469 
470             if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
471                 logger.logAsyncTaskProgress(row.getLoggingKey(), "creating public remote view");
472                 if (LockscreenOtpRedaction.isEnabled()
473                         && bindParams.redactionType == REDACTION_TYPE_OTP) {
474                     result.newPublicView = createSensitiveContentMessageNotification(
475                             NotificationBundleUi.isEnabled()
476                                     ? row.getEntryAdapter().getSbn().getNotification()
477                                     : row.getEntryLegacy().getSbn().getNotification(),
478                             builder.getStyle(),
479                             systemUiContext, packageContext).createContentView();
480                 } else {
481                     result.newPublicView = builder.makePublicContentView(bindParams.isMinimized);
482                 }
483             }
484 
485             if (AsyncGroupHeaderViewInflation.isEnabled()) {
486                 if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
487                     logger.logAsyncTaskProgress(row.getLoggingKey(),
488                             "creating group summary remote view");
489                     result.mNewGroupHeaderView = builder.makeNotificationGroupHeader();
490                 }
491 
492                 if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
493                     logger.logAsyncTaskProgress(row.getLoggingKey(),
494                             "creating low-priority group summary remote view");
495                     result.mNewMinimizedGroupHeaderView =
496                             builder.makeLowPriorityContentView(true /* useRegularSubtext */);
497                 }
498             }
499             setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
500             result.packageContext = packageContext;
501             result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(
502                     false /* showingPublic */);
503             result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
504                     true /* showingPublic */);
505 
506             return result;
507         });
508     }
509 
createSensitiveContentMessageNotification( Notification original, Notification.Style originalStyle, Context systemUiContext, Context packageContext)510     private static Notification.Builder createSensitiveContentMessageNotification(
511             Notification original,
512             Notification.Style originalStyle,
513             Context systemUiContext,
514             Context packageContext) {
515         Notification.Builder redacted =
516                 new Notification.Builder(packageContext, original.getChannelId());
517         redacted.setContentTitle(original.extras.getCharSequence(Notification.EXTRA_TITLE));
518         CharSequence redactedMessage = systemUiContext.getString(
519                 R.string.redacted_otp_notification_single_line_text
520         );
521         redacted.setWhen(original.getWhen());
522 
523         if (originalStyle instanceof MessagingStyle oldStyle) {
524             MessagingStyle newStyle = new MessagingStyle(oldStyle.getUser());
525             newStyle.setConversationTitle(oldStyle.getConversationTitle());
526             newStyle.setGroupConversation(false);
527             newStyle.setConversationType(oldStyle.getConversationType());
528             newStyle.setShortcutIcon(oldStyle.getShortcutIcon());
529             newStyle.setBuilder(redacted);
530             MessagingStyle.Message latestMessage =
531                     MessagingStyle.findLatestIncomingMessage(oldStyle.getMessages());
532             if (latestMessage != null) {
533                 MessagingStyle.Message newMessage = new MessagingStyle.Message(redactedMessage,
534                         latestMessage.getTimestamp(), latestMessage.getSenderPerson());
535                 newStyle.addMessage(newMessage);
536             }
537             redacted.setStyle(newStyle);
538         } else {
539             redacted.setContentText(redactedMessage);
540         }
541         redacted.setLargeIcon(original.getLargeIcon());
542         redacted.setSmallIcon(original.getSmallIcon());
543         return redacted;
544     }
545 
546 
setNotifsViewsInflaterFactory(InflationProgress result, ExpandableNotificationRow row, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider)547     private static void setNotifsViewsInflaterFactory(InflationProgress result,
548             ExpandableNotificationRow row,
549             NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider) {
550         setRemoteViewsInflaterFactory(result.newContentView,
551                 notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED));
552         setRemoteViewsInflaterFactory(result.newExpandedView,
553                 notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_EXPANDED));
554         setRemoteViewsInflaterFactory(result.newHeadsUpView,
555                 notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_HEADS_UP));
556         setRemoteViewsInflaterFactory(result.newPublicView,
557                 notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_PUBLIC));
558         if (android.app.Flags.notificationsRedesignAppIcons()) {
559             setRemoteViewsInflaterFactory(result.mNewGroupHeaderView,
560                     notifLayoutInflaterFactoryProvider.provide(row, FLAG_GROUP_SUMMARY_HEADER));
561             setRemoteViewsInflaterFactory(result.mNewMinimizedGroupHeaderView,
562                     notifLayoutInflaterFactoryProvider.provide(row,
563                             FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER));
564         }
565     }
566 
setRemoteViewsInflaterFactory(RemoteViews remoteViews, NotifLayoutInflaterFactory notifLayoutInflaterFactory)567     private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews,
568             NotifLayoutInflaterFactory notifLayoutInflaterFactory) {
569         if (remoteViews != null) {
570             remoteViews.setLayoutInflaterFactory(notifLayoutInflaterFactory);
571         }
572     }
573 
apply( Executor inflationExecutor, boolean inflateSynchronously, boolean isMinimized, InflationProgress result, @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache, NotificationEntry entry, ExpandableNotificationRow row, RemoteViews.InteractionHandler remoteViewClickHandler, @Nullable InflationCallback callback, NotificationRowContentBinderLogger logger)574     private static CancellationSignal apply(
575             Executor inflationExecutor,
576             boolean inflateSynchronously,
577             boolean isMinimized,
578             InflationProgress result,
579             @InflationFlag int reInflateFlags,
580             NotifRemoteViewCache remoteViewCache,
581             NotificationEntry entry,
582             ExpandableNotificationRow row,
583             RemoteViews.InteractionHandler remoteViewClickHandler,
584             @Nullable InflationCallback callback,
585             NotificationRowContentBinderLogger logger) {
586         Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
587         String logKey = NotificationUtils.logKey(entry);
588 
589         NotificationContentView privateLayout = row.getPrivateLayout();
590         NotificationContentView publicLayout = row.getPublicLayout();
591         final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
592 
593         int flag = FLAG_CONTENT_VIEW_CONTRACTED;
594         if ((reInflateFlags & flag) != 0) {
595             boolean isNewView =
596                     !canReapplyRemoteView(result.newContentView,
597                             remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED));
598             ApplyCallback applyCallback = new ApplyCallback() {
599                 @Override
600                 public void setResultView(View v) {
601                     logger.logAsyncTaskProgress(logKey, "contracted view applied");
602                     result.inflatedContentView = v;
603                 }
604 
605                 @Override
606                 public RemoteViews getRemoteView() {
607                     return result.newContentView;
608                 }
609             };
610             logger.logAsyncTaskProgress(logKey, "applying contracted view");
611             applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized, result,
612                     reInflateFlags, flag,
613                     remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
614                     privateLayout, privateLayout.getContractedChild(),
615                     privateLayout.getVisibleWrapper(
616                             NotificationContentView.VISIBLE_TYPE_CONTRACTED),
617                     runningInflations, applyCallback, logger);
618         }
619 
620         flag = FLAG_CONTENT_VIEW_EXPANDED;
621         if ((reInflateFlags & flag) != 0) {
622             if (result.newExpandedView != null) {
623                 boolean isNewView =
624                         !canReapplyRemoteView(result.newExpandedView,
625                                 remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED));
626                 ApplyCallback applyCallback = new ApplyCallback() {
627                     @Override
628                     public void setResultView(View v) {
629                         logger.logAsyncTaskProgress(logKey, "expanded view applied");
630                         result.inflatedExpandedView = v;
631                     }
632 
633                     @Override
634                     public RemoteViews getRemoteView() {
635                         return result.newExpandedView;
636                     }
637                 };
638                 logger.logAsyncTaskProgress(logKey, "applying expanded view");
639                 applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized, result,
640                         reInflateFlags,
641                         flag, remoteViewCache, entry, row, isNewView, remoteViewClickHandler,
642                         callback, privateLayout, privateLayout.getExpandedChild(),
643                         privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED), runningInflations,
644                         applyCallback, logger);
645             }
646         }
647 
648         flag = FLAG_CONTENT_VIEW_HEADS_UP;
649         if ((reInflateFlags & flag) != 0) {
650             if (result.newHeadsUpView != null) {
651                 boolean isNewView =
652                         !canReapplyRemoteView(result.newHeadsUpView,
653                                 remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP));
654                 ApplyCallback applyCallback = new ApplyCallback() {
655                     @Override
656                     public void setResultView(View v) {
657                         logger.logAsyncTaskProgress(logKey, "heads up view applied");
658                         result.inflatedHeadsUpView = v;
659                     }
660 
661                     @Override
662                     public RemoteViews getRemoteView() {
663                         return result.newHeadsUpView;
664                     }
665                 };
666                 logger.logAsyncTaskProgress(logKey, "applying heads up view");
667                 applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized,
668                         result, reInflateFlags,
669                         flag, remoteViewCache, entry, row, isNewView, remoteViewClickHandler,
670                         callback, privateLayout, privateLayout.getHeadsUpChild(),
671                         privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP), runningInflations,
672                         applyCallback, logger);
673             }
674         }
675 
676         flag = FLAG_CONTENT_VIEW_PUBLIC;
677         if ((reInflateFlags & flag) != 0) {
678             boolean isNewView =
679                     !canReapplyRemoteView(result.newPublicView,
680                             remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC));
681             ApplyCallback applyCallback = new ApplyCallback() {
682                 @Override
683                 public void setResultView(View v) {
684                     logger.logAsyncTaskProgress(logKey, "public view applied");
685                     result.inflatedPublicView = v;
686                 }
687 
688                 @Override
689                 public RemoteViews getRemoteView() {
690                     return result.newPublicView;
691                 }
692             };
693             logger.logAsyncTaskProgress(logKey, "applying public view");
694             applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized,
695                     result, reInflateFlags, flag,
696                     remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
697                     publicLayout, publicLayout.getContractedChild(),
698                     publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
699                     runningInflations, applyCallback, logger);
700         }
701 
702         if (AsyncGroupHeaderViewInflation.isEnabled()) {
703             NotificationChildrenContainer childrenContainer = row.getChildrenContainerNonNull();
704             if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
705                 boolean isNewView =
706                         !canReapplyRemoteView(
707                                 /* newView = */ result.mNewGroupHeaderView,
708                                 /* oldView = */ remoteViewCache
709                                         .getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER));
710                 ApplyCallback applyCallback = new ApplyCallback() {
711                     @Override
712                     public void setResultView(View v) {
713                         logger.logAsyncTaskProgress(logKey, "group header view applied");
714                         result.mInflatedGroupHeaderView = (NotificationHeaderView) v;
715                     }
716 
717                     @Override
718                     public RemoteViews getRemoteView() {
719                         return result.mNewGroupHeaderView;
720                     }
721                 };
722                 logger.logAsyncTaskProgress(logKey, "applying group header view");
723                 applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized,
724                         result, reInflateFlags,
725                         /* inflationId = */ FLAG_GROUP_SUMMARY_HEADER,
726                         remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
727                         /* parentLayout = */ childrenContainer,
728                         /* existingView = */ childrenContainer.getGroupHeader(),
729                         /* existingWrapper = */ childrenContainer.getNotificationHeaderWrapper(),
730                         runningInflations, applyCallback, logger);
731             }
732 
733             if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
734                 boolean isNewView =
735                         !canReapplyRemoteView(
736                                 /* newView = */ result.mNewMinimizedGroupHeaderView,
737                                 /* oldView = */ remoteViewCache.getCachedView(
738                                         entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER));
739                 ApplyCallback applyCallback = new ApplyCallback() {
740                     @Override
741                     public void setResultView(View v) {
742                         logger.logAsyncTaskProgress(logKey,
743                                 "low-priority group header view applied");
744                         result.mInflatedMinimizedGroupHeaderView = (NotificationHeaderView) v;
745                     }
746 
747                     @Override
748                     public RemoteViews getRemoteView() {
749                         return result.mNewMinimizedGroupHeaderView;
750                     }
751                 };
752                 logger.logAsyncTaskProgress(logKey, "applying low priority group header view");
753                 applyRemoteView(inflationExecutor, inflateSynchronously, isMinimized,
754                         result, reInflateFlags,
755                         /* inflationId = */ FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
756                         remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,
757                         /* parentLayout = */ childrenContainer,
758                         /* existingView = */ childrenContainer.getMinimizedNotificationHeader(),
759                         /* existingWrapper = */ childrenContainer
760                                 .getMinimizedGroupHeaderWrapper(),
761                         runningInflations, applyCallback, logger);
762             }
763         }
764 
765         // Let's try to finish, maybe nobody is even inflating anything
766         finishIfDone(result, isMinimized, reInflateFlags, remoteViewCache, runningInflations,
767                 callback, entry, row, logger);
768         CancellationSignal cancellationSignal = new CancellationSignal();
769         cancellationSignal.setOnCancelListener(
770                 () -> {
771                     logger.logAsyncTaskProgress(logKey, "apply cancelled");
772                     Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
773                     runningInflations.values().forEach(CancellationSignal::cancel);
774                 });
775 
776         return cancellationSignal;
777     }
778 
779     @VisibleForTesting
applyRemoteView( Executor inflationExecutor, boolean inflateSynchronously, boolean isMinimized, final InflationProgress result, final @InflationFlag int reInflateFlags, @InflationFlag int inflationId, final NotifRemoteViewCache remoteViewCache, final NotificationEntry entry, final ExpandableNotificationRow row, boolean isNewView, RemoteViews.InteractionHandler remoteViewClickHandler, @Nullable final InflationCallback callback, ViewGroup parentLayout, View existingView, NotificationViewWrapper existingWrapper, final HashMap<Integer, CancellationSignal> runningInflations, ApplyCallback applyCallback, NotificationRowContentBinderLogger logger)780     static void applyRemoteView(
781             Executor inflationExecutor,
782             boolean inflateSynchronously,
783             boolean isMinimized,
784             final InflationProgress result,
785             final @InflationFlag int reInflateFlags,
786             @InflationFlag int inflationId,
787             final NotifRemoteViewCache remoteViewCache,
788             final NotificationEntry entry,
789             final ExpandableNotificationRow row,
790             boolean isNewView,
791             RemoteViews.InteractionHandler remoteViewClickHandler,
792             @Nullable final InflationCallback callback,
793             ViewGroup parentLayout,
794             View existingView,
795             NotificationViewWrapper existingWrapper,
796             final HashMap<Integer, CancellationSignal> runningInflations,
797             ApplyCallback applyCallback,
798             NotificationRowContentBinderLogger logger) {
799         RemoteViews newContentView = applyCallback.getRemoteView();
800         if (inflateSynchronously) {
801             try {
802                 if (isNewView) {
803                     View v = newContentView.apply(
804                             result.packageContext,
805                             parentLayout,
806                             remoteViewClickHandler);
807                     validateView(v, entry, row.getResources());
808                     applyCallback.setResultView(v);
809                 } else {
810                     newContentView.reapply(
811                             result.packageContext,
812                             existingView,
813                             remoteViewClickHandler);
814                     validateView(existingView, entry, row.getResources());
815                     existingWrapper.onReinflated();
816                 }
817             } catch (Exception e) {
818                 handleInflationError(runningInflations, e, row, entry, callback, logger,
819                         "applying view synchronously");
820                 // Add a running inflation to make sure we don't trigger callbacks.
821                 // Safe to do because only happens in tests.
822                 runningInflations.put(inflationId, new CancellationSignal());
823             }
824             return;
825         }
826         RemoteViews.OnViewAppliedListener listener = new RemoteViews.OnViewAppliedListener() {
827 
828             @Override
829             public void onViewInflated(View v) {
830                 if (v instanceof ImageMessageConsumer) {
831                     ((ImageMessageConsumer) v).setImageResolver(row.getImageResolver());
832                 }
833             }
834 
835             @Override
836             public void onViewApplied(View v) {
837                 String invalidReason = isValidView(v, entry, row.getResources());
838                 if (invalidReason != null) {
839                     handleInflationError(runningInflations, new InflationException(invalidReason),
840                             row, entry, callback, logger, "applied invalid view");
841                     runningInflations.remove(inflationId);
842                     return;
843                 }
844                 if (isNewView) {
845                     applyCallback.setResultView(v);
846                 } else if (existingWrapper != null) {
847                     existingWrapper.onReinflated();
848                 }
849                 runningInflations.remove(inflationId);
850                 finishIfDone(result, isMinimized,
851                         reInflateFlags, remoteViewCache, runningInflations,
852                         callback, entry, row, logger);
853             }
854 
855             @Override
856             public void onError(Exception e) {
857                 // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this could
858                 // actually also be a system issue, so let's try on the UI thread again to be safe.
859                 try {
860                     View newView = existingView;
861                     if (isNewView) {
862                         newView = newContentView.apply(
863                                 result.packageContext,
864                                 parentLayout,
865                                 remoteViewClickHandler);
866                     } else {
867                         newContentView.reapply(
868                                 result.packageContext,
869                                 existingView,
870                                 remoteViewClickHandler);
871                     }
872                     Log.wtf(TAG, "Async Inflation failed but normal inflation finished normally.",
873                             e);
874                     onViewApplied(newView);
875                 } catch (Exception anotherException) {
876                     runningInflations.remove(inflationId);
877                     handleInflationError(runningInflations, e, row, entry,
878                             callback, logger, "applying view");
879                 }
880             }
881         };
882         CancellationSignal cancellationSignal;
883         if (isNewView) {
884             cancellationSignal = newContentView.applyAsync(
885                     result.packageContext,
886                     parentLayout,
887                     inflationExecutor,
888                     listener,
889                     remoteViewClickHandler);
890         } else {
891             cancellationSignal = newContentView.reapplyAsync(
892                     result.packageContext,
893                     existingView,
894                     inflationExecutor,
895                     listener,
896                     remoteViewClickHandler);
897         }
898         runningInflations.put(inflationId, cancellationSignal);
899     }
900 
901     /**
902      * Checks if the given View is a valid notification View.
903      *
904      * @return null == valid, non-null == invalid, String represents reason for rejection.
905      */
906     @VisibleForTesting
907     @Nullable
isValidView(View view, NotificationEntry entry, Resources resources)908     static String isValidView(View view,
909             NotificationEntry entry,
910             Resources resources) {
911         if (!satisfiesMinHeightRequirement(view, entry, resources)) {
912             return "inflated notification does not meet minimum height requirement";
913         }
914 
915         if (NotificationCustomContentMemoryVerifier.requiresImageViewMemorySizeCheck(entry)) {
916             if (!NotificationCustomContentMemoryVerifier.satisfiesMemoryLimits(view, entry)) {
917                 return "inflated notification does not meet maximum memory size requirement";
918             }
919         }
920 
921         return null;
922     }
923 
satisfiesMinHeightRequirement(View view, NotificationEntry entry, Resources resources)924     private static boolean satisfiesMinHeightRequirement(View view,
925             NotificationEntry entry,
926             Resources resources) {
927         if (!requiresHeightCheck(entry)) {
928             return true;
929         }
930         return TraceUtils.trace("NotificationContentInflater#satisfiesMinHeightRequirement", () -> {
931             int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
932             int referenceWidth = resources.getDimensionPixelSize(
933                     R.dimen.notification_validation_reference_width);
934             int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth,
935                     View.MeasureSpec.EXACTLY);
936             view.measure(widthSpec, heightSpec);
937             int minHeight = resources.getDimensionPixelSize(
938                     R.dimen.notification_validation_minimum_allowed_height);
939             return view.getMeasuredHeight() >= minHeight;
940         });
941     }
942 
943     /**
944      * Notifications with undecorated custom views need to satisfy a minimum height to avoid visual
945      * issues.
946      */
947     private static boolean requiresHeightCheck(NotificationEntry entry) {
948         // Undecorated custom views are disallowed from S onwards
949         if (entry.targetSdk >= Build.VERSION_CODES.S) {
950             return false;
951         }
952         // No need to check if the app isn't using any custom views
953         Notification notification = entry.getSbn().getNotification();
954         if (notification.contentView == null
955                 && notification.bigContentView == null
956                 && notification.headsUpContentView == null) {
957             return false;
958         }
959         return true;
960     }
961 
962     private static void validateView(View view,
963             NotificationEntry entry,
964             Resources resources) throws InflationException {
965         String invalidReason = isValidView(view, entry, resources);
966         if (invalidReason != null) {
967             throw new InflationException(invalidReason);
968         }
969     }
970 
971     private static void handleInflationError(
972             HashMap<Integer, CancellationSignal> runningInflations, Exception e,
973             ExpandableNotificationRow row, NotificationEntry entry,
974             @Nullable InflationCallback callback,
975             NotificationRowContentBinderLogger logger, String logContext) {
976         Assert.isMainThread();
977         logger.logAsyncTaskException(row.getLoggingKey(), logContext, e);
978         runningInflations.values().forEach(CancellationSignal::cancel);
979         if (callback != null) {
980             callback.handleInflationException(entry, e);
981         }
982     }
983 
984     /**
985      * Finish the inflation of the views
986      *
987      * @return true if the inflation was finished
988      */
989     private static boolean finishIfDone(InflationProgress result,
990             boolean isMinimized,
991             @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache,
992             HashMap<Integer, CancellationSignal> runningInflations,
993             @Nullable InflationCallback endListener, NotificationEntry entry,
994             ExpandableNotificationRow row, NotificationRowContentBinderLogger logger) {
995         Assert.isMainThread();
996         if (!runningInflations.isEmpty()) {
997             return false;
998         }
999         NotificationContentView privateLayout = row.getPrivateLayout();
1000         NotificationContentView publicLayout = row.getPublicLayout();
1001         logger.logAsyncTaskProgress(NotificationUtils.logKey(entry), "finishing");
1002 
1003         // Put the new image index on the row
1004         row.mImageModelIndex = result.mRowImageInflater.getNewImageIndex();
1005 
1006         if (PromotedNotificationContentModel.featureFlagEnabled()) {
1007             entry.setPromotedNotificationContentModels(result.mPromotedContent);
1008         }
1009 
1010         boolean setRepliesAndActions = true;
1011         if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
1012             if (result.inflatedContentView != null) {
1013                 // New view case
1014                 privateLayout.setContractedChild(result.inflatedContentView);
1015                 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
1016                         result.newContentView);
1017             } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
1018                 // Reinflation case. Only update if it's still cached (i.e. view has not been
1019                 // freed while inflating).
1020                 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
1021                         result.newContentView);
1022             }
1023             setRepliesAndActions = true;
1024         }
1025 
1026         if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
1027             if (result.inflatedExpandedView != null) {
1028                 privateLayout.setExpandedChild(result.inflatedExpandedView);
1029                 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
1030                         result.newExpandedView);
1031             } else if (result.newExpandedView == null) {
1032                 privateLayout.setExpandedChild(null);
1033                 remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
1034             } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
1035                 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
1036                         result.newExpandedView);
1037             }
1038             if (result.newExpandedView != null) {
1039                 privateLayout.setExpandedInflatedSmartReplies(
1040                         result.expandedInflatedSmartReplies);
1041             } else {
1042                 privateLayout.setExpandedInflatedSmartReplies(null);
1043             }
1044             row.setExpandable(result.newExpandedView != null);
1045             setRepliesAndActions = true;
1046         }
1047 
1048         if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
1049             if (result.inflatedHeadsUpView != null) {
1050                 privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
1051                 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
1052                         result.newHeadsUpView);
1053             } else if (result.newHeadsUpView == null) {
1054                 privateLayout.setHeadsUpChild(null);
1055                 remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
1056             } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
1057                 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
1058                         result.newHeadsUpView);
1059             }
1060             if (result.newHeadsUpView != null) {
1061                 privateLayout.setHeadsUpInflatedSmartReplies(
1062                         result.headsUpInflatedSmartReplies);
1063             } else {
1064                 privateLayout.setHeadsUpInflatedSmartReplies(null);
1065             }
1066             setRepliesAndActions = true;
1067         }
1068 
1069         if (AsyncHybridViewInflation.isEnabled()
1070                 && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
1071             HybridNotificationView view = result.mInflatedSingleLineView;
1072             SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
1073             if (view != null && viewModel != null) {
1074                 SingleLineViewBinder.bind(viewModel, view);
1075                 privateLayout.setSingleLineView(result.mInflatedSingleLineView);
1076             }
1077         }
1078 
1079         if (LockscreenOtpRedaction.isSingleLineViewEnabled()
1080                 && (reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE) != 0) {
1081             HybridNotificationView view = result.mPublicInflatedSingleLineView;
1082             SingleLineViewModel viewModel = result.mPublicInflatedSingleLineViewModel;
1083             if (view != null && viewModel != null) {
1084                 SingleLineViewBinder.bind(viewModel, view);
1085                 publicLayout.setSingleLineView(result.mPublicInflatedSingleLineView);
1086             }
1087         }
1088 
1089         if (setRepliesAndActions) {
1090             privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
1091         }
1092 
1093         if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
1094             if (result.inflatedPublicView != null) {
1095                 publicLayout.setContractedChild(result.inflatedPublicView);
1096                 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
1097                         result.newPublicView);
1098             } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
1099                 remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
1100                         result.newPublicView);
1101             }
1102         }
1103 
1104         if (AsyncGroupHeaderViewInflation.isEnabled()) {
1105             if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
1106                 if (result.mInflatedGroupHeaderView != null) {
1107                     // We need to set if the row is minimized before setting the group header to
1108                     // make sure the setting of header view works correctly
1109                     row.setIsMinimized(isMinimized);
1110                     row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView);
1111                     remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
1112                             result.mNewGroupHeaderView);
1113                 } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) {
1114                     // Re-inflation case. Only update if it's still cached (i.e. view has not
1115                     // been freed while inflating).
1116                     remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
1117                             result.mNewGroupHeaderView);
1118                 }
1119             }
1120 
1121             if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
1122                 if (result.mInflatedMinimizedGroupHeaderView != null) {
1123                     // We need to set if the row is minimized before setting the group header to
1124                     // make sure the setting of header view works correctly
1125                     row.setIsMinimized(isMinimized);
1126                     row.setMinimizedGroupHeader(
1127                             /* headerView= */ result.mInflatedMinimizedGroupHeaderView);
1128                     remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
1129                             result.mNewMinimizedGroupHeaderView);
1130                 } else if (remoteViewCache.hasCachedView(entry,
1131                         FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) {
1132                     // Re-inflation case. Only update if it's still cached (i.e. view has not
1133                     // been freed while inflating).
1134                     remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
1135                             result.mNewGroupHeaderView);
1136                 }
1137             }
1138         }
1139 
1140         entry.setHeadsUpStatusBarText(result.headsUpStatusBarText);
1141         entry.setHeadsUpStatusBarTextPublic(result.headsUpStatusBarTextPublic);
1142 
1143         Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
1144         if (endListener != null) {
1145             endListener.onAsyncInflationFinished(entry);
1146         }
1147         return true;
1148     }
1149 
1150     private static RemoteViews createExpandedView(Notification.Builder builder,
1151             boolean isMinimized) {
1152         RemoteViews bigContentView = builder.createBigContentView();
1153         if (bigContentView != null) {
1154             return bigContentView;
1155         }
1156         if (isMinimized) {
1157             RemoteViews contentView = builder.createContentView();
1158             Notification.Builder.makeHeaderExpanded(contentView);
1159             return contentView;
1160         }
1161         return null;
1162     }
1163 
1164     private static RemoteViews createContentView(Notification.Builder builder,
1165             boolean isMinimized) {
1166         if (isMinimized) {
1167             return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
1168         }
1169         return builder.createContentView();
1170     }
1171 
1172     /**
1173      * @param newView The new view that will be applied
1174      * @param oldView The old view that was applied to the existing view before
1175      * @return {@code true} if the RemoteViews are the same and the view can be reused to reapply.
1176      */
1177     @VisibleForTesting
1178     static boolean canReapplyRemoteView(final RemoteViews newView,
1179             final RemoteViews oldView) {
1180         return (newView == null && oldView == null) ||
1181                 (newView != null && oldView != null
1182                         && oldView.getPackage() != null
1183                         && newView.getPackage() != null
1184                         && newView.getPackage().equals(oldView.getPackage())
1185                         && newView.getLayoutId() == oldView.getLayoutId()
1186                         && !oldView.hasFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED));
1187     }
1188 
1189     /**
1190      * Sets whether to perform inflation on the same thread as the caller. This method should only
1191      * be used in tests, not in production.
1192      */
1193     @VisibleForTesting
1194     public void setInflateSynchronously(boolean inflateSynchronously) {
1195         mInflateSynchronously = inflateSynchronously;
1196     }
1197 
1198     public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
1199             implements InflationCallback, InflationTask {
1200 
1201         private static final long IMG_PRELOAD_TIMEOUT_MS = 1000L;
1202         private final NotificationEntry mEntry;
1203         private final Context mContext;
1204         private final boolean mInflateSynchronously;
1205         private final BindParams mBindParams;
1206         private final InflationCallback mCallback;
1207         private final @InflationFlag int mReInflateFlags;
1208         private final NotifRemoteViewCache mRemoteViewCache;
1209         private final Executor mInflationExecutor;
1210         private ExpandableNotificationRow mRow;
1211         private Exception mError;
1212         private RemoteViews.InteractionHandler mRemoteViewClickHandler;
1213         private CancellationSignal mCancellationSignal;
1214         private final ConversationNotificationProcessor mConversationProcessor;
1215         private final boolean mIsMediaInQS;
1216         private final SmartReplyStateInflater mSmartRepliesInflater;
1217         private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
1218         private final HeadsUpStyleProvider mHeadsUpStyleProvider;
1219         private final PromotedNotificationContentExtractor mPromotedNotificationContentExtractor;
1220         private final NotificationRowContentBinderLogger mLogger;
1221 
1222         private AsyncInflationTask(
1223                 Executor inflationExecutor,
1224                 boolean inflateSynchronously,
1225                 @InflationFlag int reInflateFlags,
1226                 NotifRemoteViewCache cache,
1227                 NotificationEntry entry,
1228                 ConversationNotificationProcessor conversationProcessor,
1229                 ExpandableNotificationRow row,
1230                 BindParams bindParams,
1231                 InflationCallback callback,
1232                 RemoteViews.InteractionHandler remoteViewClickHandler,
1233                 boolean isMediaFlagEnabled,
1234                 SmartReplyStateInflater smartRepliesInflater,
1235                 NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
1236                 HeadsUpStyleProvider headsUpStyleProvider,
1237                 PromotedNotificationContentExtractor promotedNotificationContentExtractor,
1238                 NotificationRowContentBinderLogger logger) {
1239             mEntry = entry;
1240             mRow = row;
1241             mInflationExecutor = inflationExecutor;
1242             mInflateSynchronously = inflateSynchronously;
1243             mReInflateFlags = reInflateFlags;
1244             mRemoteViewCache = cache;
1245             mSmartRepliesInflater = smartRepliesInflater;
1246             mContext = mRow.getContext();
1247             mBindParams = bindParams;
1248             mRemoteViewClickHandler = remoteViewClickHandler;
1249             mCallback = callback;
1250             mConversationProcessor = conversationProcessor;
1251             mIsMediaInQS = isMediaFlagEnabled;
1252             mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider;
1253             mHeadsUpStyleProvider = headsUpStyleProvider;
1254             mPromotedNotificationContentExtractor = promotedNotificationContentExtractor;
1255             mLogger = logger;
1256             entry.setInflationTask(this);
1257         }
1258 
1259         @VisibleForTesting
1260         @InflationFlag
1261         public int getReInflateFlags() {
1262             return mReInflateFlags;
1263         }
1264 
1265         void updateApplicationInfo(StatusBarNotification sbn) {
1266             String packageName = sbn.getPackageName();
1267             int userId = UserHandle.getUserId(sbn.getUid());
1268             final ApplicationInfo appInfo;
1269             try {
1270                 // This method has an internal cache, so we don't need to add our own caching here.
1271                 appInfo = mContext.getPackageManager().getApplicationInfoAsUser(packageName,
1272                         PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
1273             } catch (PackageManager.NameNotFoundException e) {
1274                 return;
1275             }
1276             Notification.addFieldsFromContext(appInfo, sbn.getNotification());
1277         }
1278 
1279         @Override
1280         protected void onPreExecute() {
1281             Trace.beginAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
1282         }
1283 
1284         @Override
1285         protected InflationProgress doInBackground(Void... params) {
1286             return TraceUtils.trace("NotificationContentInflater.AsyncInflationTask#doInBackground",
1287                     () -> {
1288                         try {
1289                             return doInBackgroundInternal();
1290                         } catch (Exception e) {
1291                             mError = e;
1292                             mLogger.logAsyncTaskException(
1293                                     NotificationUtils.logKey(mEntry), "inflating", e);
1294                             return null;
1295                         }
1296                     });
1297         }
1298 
1299         private InflationProgress doInBackgroundInternal() {
1300             final StatusBarNotification sbn = mEntry.getSbn();
1301             // Ensure the ApplicationInfo is updated before a builder is recovered.
1302             updateApplicationInfo(sbn);
1303             final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(
1304                     mContext, sbn.getNotification());
1305 
1306             Context packageContext = sbn.getPackageContext(mContext);
1307             if (recoveredBuilder.usesTemplate()) {
1308                 // For all of our templates, we want it to be RTL
1309                 packageContext = new RtlEnabledContext(packageContext);
1310             }
1311             boolean isConversation = mEntry.getRanking().isConversation();
1312             Notification.MessagingStyle messagingStyle = null;
1313             if (isConversation) {
1314                 messagingStyle = mConversationProcessor.processNotification(
1315                         mEntry, recoveredBuilder, mLogger);
1316             }
1317             InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
1318                     recoveredBuilder, mBindParams, mContext, packageContext, mRow,
1319                     mNotifLayoutInflaterFactoryProvider, mHeadsUpStyleProvider, mLogger);
1320             String logKey = NotificationUtils.logKey(mEntry);
1321             mLogger.logAsyncTaskProgress(logKey,
1322                     "getting existing smart reply state (on wrong thread!)");
1323             InflatedSmartReplyState previousSmartReplyState =
1324                     mRow.getExistingSmartReplyState();
1325             mLogger.logAsyncTaskProgress(logKey, "inflating smart reply views");
1326             InflationProgress result = inflateSmartReplyViews(
1327                     /* result = */ inflationProgress,
1328                     mReInflateFlags,
1329                     mEntry,
1330                     mContext,
1331                     packageContext,
1332                     previousSmartReplyState,
1333                     mSmartRepliesInflater,
1334                     mLogger);
1335 
1336             if (AsyncHybridViewInflation.isEnabled()) {
1337                 // Inflate the single-line content view's ViewModel and ViewHolder from the
1338                 // background thread, the ViewHolder needs to be bind with ViewModel later from
1339                 // the main thread.
1340                 result.mInflatedSingleLineViewModel = SingleLineViewInflater
1341                         .inflateSingleLineViewModel(
1342                                 mEntry.getSbn().getNotification(),
1343                                 messagingStyle,
1344                                 recoveredBuilder,
1345                                 mContext,
1346                                 false,
1347                                 mEntry.getRanking().getSummarization()
1348                         );
1349                 result.mInflatedSingleLineView =
1350                         SingleLineViewInflater.inflatePrivateSingleLineView(
1351                                 isConversation,
1352                                 mReInflateFlags,
1353                                 mEntry,
1354                                 mContext,
1355                                 mLogger
1356                         );
1357             }
1358 
1359             if (LockscreenOtpRedaction.isSingleLineViewEnabled()) {
1360                 if (mBindParams.redactionType == REDACTION_TYPE_OTP) {
1361                     result.mPublicInflatedSingleLineViewModel =
1362                             SingleLineViewInflater.inflateSingleLineViewModel(
1363                                     mEntry.getSbn().getNotification(),
1364                                     messagingStyle,
1365                                     recoveredBuilder,
1366                                     mContext,
1367                                     true,
1368                                     null
1369                             );
1370                 } else {
1371                     result.mPublicInflatedSingleLineViewModel =
1372                             SingleLineViewInflater.inflatePublicSingleLineViewModel(
1373                                     mContext,
1374                                     isConversation
1375                             );
1376                 }
1377                 result.mPublicInflatedSingleLineView =
1378                         SingleLineViewInflater.inflatePublicSingleLineView(
1379                                 isConversation,
1380                                 mReInflateFlags,
1381                                 mEntry,
1382                                 mContext,
1383                                 mLogger
1384                         );
1385             }
1386 
1387             if (PromotedNotificationContentModel.featureFlagEnabled()) {
1388                 mLogger.logAsyncTaskProgress(logKey, "extracting promoted notification content");
1389                 final ImageModelProvider imageModelProvider =
1390                         result.mRowImageInflater.useForContentModel();
1391                 final PromotedNotificationContentModels promotedContent =
1392                         mPromotedNotificationContentExtractor.extractContent(mEntry,
1393                                 recoveredBuilder, mBindParams.redactionType, imageModelProvider);
1394                 mLogger.logAsyncTaskProgress(logKey, "extracted promoted notification content: "
1395                         + (promotedContent != null ? promotedContent.toRedactedString() : null));
1396 
1397                 result.mPromotedContent = promotedContent;
1398             }
1399 
1400             mLogger.logAsyncTaskProgress(logKey, "loading RON images");
1401             inflationProgress.mRowImageInflater.loadImagesSynchronously(packageContext);
1402 
1403             mLogger.logAsyncTaskProgress(logKey,
1404                     "getting row image resolver (on wrong thread!)");
1405             final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
1406             // wait for image resolver to finish preloading
1407             mLogger.logAsyncTaskProgress(logKey, "waiting for preloaded images");
1408             imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
1409 
1410             return result;
1411         }
1412 
1413         @Override
1414         protected void onPostExecute(InflationProgress result) {
1415             Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
1416 
1417             if (mError == null) {
1418                 // Logged in detail in apply.
1419                 mCancellationSignal = apply(
1420                         mInflationExecutor,
1421                         mInflateSynchronously,
1422                         mBindParams.isMinimized,
1423                         result,
1424                         mReInflateFlags,
1425                         mRemoteViewCache,
1426                         mEntry,
1427                         mRow,
1428                         mRemoteViewClickHandler,
1429                         this /* callback */,
1430                         mLogger);
1431             } else {
1432                 handleError(mError);
1433             }
1434         }
1435 
1436         @Override
1437         protected void onCancelled(InflationProgress result) {
1438             Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
1439         }
1440 
1441         private void handleError(Exception e) {
1442             mEntry.onInflationTaskFinished();
1443             StatusBarNotification sbn = mEntry.getSbn();
1444             final String ident = sbn.getPackageName() + "/0x"
1445                     + Integer.toHexString(sbn.getId());
1446             Log.e(CentralSurfaces.TAG, "couldn't inflate view for notification " + ident, e);
1447             if (mCallback != null) {
1448                 mCallback.handleInflationException(mEntry,
1449                         new InflationException("Couldn't inflate contentViews" + e));
1450             }
1451 
1452             // Cancel any image loading tasks, not useful any more
1453             mRow.getImageResolver().cancelRunningTasks();
1454         }
1455 
1456         @Override
1457         public void abort() {
1458             String logKey = NotificationUtils.logKey(mEntry);
1459             mLogger.logAsyncTaskProgress(logKey, "cancelling inflate");
1460             cancel(true /* mayInterruptIfRunning */);
1461             if (mCancellationSignal != null) {
1462                 mLogger.logAsyncTaskProgress(logKey, "cancelling apply");
1463                 mCancellationSignal.cancel();
1464             }
1465             mLogger.logAsyncTaskProgress(logKey, "aborted");
1466         }
1467 
1468         @Override
1469         public void handleInflationException(Exception e) {
1470             handleError(e);
1471         }
1472 
1473         @Override
1474         public void onAsyncInflationFinished() {
1475             mEntry.onInflationTaskFinished();
1476             mRow.onNotificationUpdated();
1477             if (mCallback != null) {
1478                 mCallback.onAsyncInflationFinished(mEntry);
1479             }
1480 
1481             // Notify the resolver that the inflation task has finished,
1482             // try to purge unnecessary cached entries.
1483             mRow.getImageResolver().purgeCache();
1484 
1485             // Cancel any image loading tasks that have not completed at this point
1486             mRow.getImageResolver().cancelRunningTasks();
1487         }
1488 
1489         private static class RtlEnabledContext extends ContextWrapper {
1490             private RtlEnabledContext(Context packageContext) {
1491                 super(packageContext);
1492             }
1493 
1494             @Override
1495             public ApplicationInfo getApplicationInfo() {
1496                 ApplicationInfo applicationInfo = new ApplicationInfo(super.getApplicationInfo());
1497                 applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_RTL;
1498                 return applicationInfo;
1499             }
1500         }
1501     }
1502 
1503     @VisibleForTesting
1504     static class InflationProgress {
1505         RowImageInflater mRowImageInflater;
1506 
1507         PromotedNotificationContentModels mPromotedContent;
1508 
1509         private RemoteViews newContentView;
1510         private RemoteViews newHeadsUpView;
1511         private RemoteViews newExpandedView;
1512         private RemoteViews newPublicView;
1513         private RemoteViews mNewGroupHeaderView;
1514         private RemoteViews mNewMinimizedGroupHeaderView;
1515 
1516         @VisibleForTesting
1517         Context packageContext;
1518 
1519         private View inflatedContentView;
1520         private View inflatedHeadsUpView;
1521         private View inflatedExpandedView;
1522         private View inflatedPublicView;
1523         private NotificationHeaderView mInflatedGroupHeaderView;
1524         private NotificationHeaderView mInflatedMinimizedGroupHeaderView;
1525         private CharSequence headsUpStatusBarText;
1526         private CharSequence headsUpStatusBarTextPublic;
1527 
1528         private InflatedSmartReplyState inflatedSmartReplyState;
1529         private InflatedSmartReplyViewHolder expandedInflatedSmartReplies;
1530         private InflatedSmartReplyViewHolder headsUpInflatedSmartReplies;
1531 
1532         // ViewModel for SingleLineView, holds the UI State
1533         SingleLineViewModel mInflatedSingleLineViewModel;
1534         // ViewModel for public SingleLineView, holds the UI State
1535         SingleLineViewModel mPublicInflatedSingleLineViewModel;
1536         // Inflated SingleLineViewHolder, SingleLineView that lacks the UI State
1537         HybridNotificationView mInflatedSingleLineView;
1538         // Inflated SingleLineViewHolder, SingleLineView that lacks the UI State for the public
1539         // single line view
1540         HybridNotificationView mPublicInflatedSingleLineView;
1541     }
1542 
1543     @VisibleForTesting
1544     abstract static class ApplyCallback {
1545         public abstract void setResultView(View v);
1546 
1547         public abstract RemoteViews getRemoteView();
1548     }
1549 
1550     private static final String ASYNC_TASK_TRACE_METHOD =
1551             "NotificationContentInflater.AsyncInflationTask";
1552     private static final String APPLY_TRACE_METHOD = "NotificationContentInflater#apply";
1553 }
1554