1 /*
2  * Copyright (C) 2020 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.collection.inflation;
18 
19 import static com.android.server.notification.Flags.screenshareNotificationHiding;
20 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
21 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
22 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
23 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE;
24 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_GROUP_SUMMARY_HEADER;
25 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER;
26 
27 import static java.util.Objects.requireNonNull;
28 
29 import android.annotation.NonNull;
30 import android.annotation.Nullable;
31 import android.content.Context;
32 import android.os.Build;
33 import android.view.ViewGroup;
34 
35 import com.android.internal.util.NotificationMessagingUtil;
36 import com.android.systemui.dagger.SysUISingleton;
37 import com.android.systemui.flags.FeatureFlags;
38 import com.android.systemui.flags.Flags;
39 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
40 import com.android.systemui.statusbar.NotificationPresenter;
41 import com.android.systemui.statusbar.NotificationRemoteInputManager;
42 import com.android.systemui.statusbar.notification.InflationException;
43 import com.android.systemui.statusbar.notification.NotificationClicker;
44 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
45 import com.android.systemui.statusbar.notification.icon.IconManager;
46 import com.android.systemui.statusbar.notification.row.BigPictureIconManager;
47 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
48 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
49 import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
50 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
51 import com.android.systemui.statusbar.notification.row.RowContentBindParams;
52 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
53 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
54 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
55 import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
56 import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
57 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
58 
59 import javax.inject.Inject;
60 import javax.inject.Provider;
61 
62 /** Handles inflating and updating views for notifications. */
63 @SysUISingleton
64 public class NotificationRowBinderImpl implements NotificationRowBinder {
65 
66     private static final String TAG = "NotificationViewManager";
67 
68     private final Context mContext;
69     private final NotificationMessagingUtil mMessagingUtil;
70     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
71     private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
72     private final NotifBindPipeline mNotifBindPipeline;
73     private final RowContentBindStage mRowContentBindStage;
74     private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
75     private final ExpandableNotificationRowComponent.Builder
76             mExpandableNotificationRowComponentBuilder;
77     private final IconManager mIconManager;
78     private final NotificationRowBinderLogger mLogger;
79 
80     private NotificationPresenter mPresenter;
81     private NotificationListContainer mListContainer;
82     private NotificationClicker mNotificationClicker;
83     private FeatureFlags mFeatureFlags;
84 
85     @Inject
NotificationRowBinderImpl( Context context, NotificationMessagingUtil notificationMessagingUtil, NotificationRemoteInputManager notificationRemoteInputManager, NotificationLockscreenUserManager notificationLockscreenUserManager, NotifBindPipeline notifBindPipeline, RowContentBindStage rowContentBindStage, Provider<RowInflaterTask> rowInflaterTaskProvider, ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder, IconManager iconManager, NotificationRowBinderLogger logger, FeatureFlags featureFlags)86     public NotificationRowBinderImpl(
87             Context context,
88             NotificationMessagingUtil notificationMessagingUtil,
89             NotificationRemoteInputManager notificationRemoteInputManager,
90             NotificationLockscreenUserManager notificationLockscreenUserManager,
91             NotifBindPipeline notifBindPipeline,
92             RowContentBindStage rowContentBindStage,
93             Provider<RowInflaterTask> rowInflaterTaskProvider,
94             ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
95             IconManager iconManager,
96             NotificationRowBinderLogger logger,
97             FeatureFlags featureFlags) {
98         mContext = context;
99         mNotifBindPipeline = notifBindPipeline;
100         mRowContentBindStage = rowContentBindStage;
101         mMessagingUtil = notificationMessagingUtil;
102         mNotificationRemoteInputManager = notificationRemoteInputManager;
103         mNotificationLockscreenUserManager = notificationLockscreenUserManager;
104         mRowInflaterTaskProvider = rowInflaterTaskProvider;
105         mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
106         mIconManager = iconManager;
107         mLogger = logger;
108         mFeatureFlags = featureFlags;
109     }
110 
111     /**
112      * Sets up late-bound dependencies for this component.
113      */
setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer)114     public void setUpWithPresenter(NotificationPresenter presenter,
115             NotificationListContainer listContainer) {
116         mPresenter = presenter;
117         mListContainer = listContainer;
118 
119         mIconManager.attach();
120     }
121 
setNotificationClicker(NotificationClicker clicker)122     public void setNotificationClicker(NotificationClicker clicker) {
123         mNotificationClicker = clicker;
124     }
125 
126     /**
127      * Inflates the views for the given entry (possibly asynchronously).
128      */
129     @Override
inflateViews( NotificationEntry entry, @NonNull NotifInflater.Params params, NotificationRowContentBinder.InflationCallback callback)130     public void inflateViews(
131             NotificationEntry entry,
132             @NonNull NotifInflater.Params params,
133             NotificationRowContentBinder.InflationCallback callback)
134             throws InflationException {
135         //TODO(b/217799515): Remove the entry parameter from getViewParentForNotification(), this
136         // function returns the NotificationStackScrollLayout regardless of the entry.
137         ViewGroup parent = mListContainer.getViewParentForNotification(entry);
138 
139         if (entry.rowExists()) {
140             mLogger.logUpdatingRow(entry, params);
141             mIconManager.updateIcons(entry, /* usingCache = */ false);
142             ExpandableNotificationRow row = entry.getRow();
143             row.reset();
144             updateRow(entry, row);
145             inflateContentViews(entry, params, row, callback);
146         } else {
147             mLogger.logCreatingRow(entry, params);
148             mIconManager.createIcons(entry);
149             mLogger.logInflatingRow(entry);
150             mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
151                     row -> {
152                         mLogger.logInflatedRow(entry);
153                         // Setup the controller for the view.
154                         ExpandableNotificationRowComponent component =
155                                 mExpandableNotificationRowComponentBuilder
156                                         .expandableNotificationRow(row)
157                                         .notificationEntry(entry)
158                                         .onExpandClickListener(mPresenter)
159                                         .build();
160                         ExpandableNotificationRowController rowController =
161                                 component.getExpandableNotificationRowController();
162                         rowController.init(entry);
163                         entry.setRowController(rowController);
164                         maybeSetBigPictureIconManager(row, component);
165                         bindRow(entry, row);
166                         updateRow(entry, row);
167                         inflateContentViews(entry, params, row, callback);
168                     });
169         }
170     }
171 
172     @Override
releaseViews(NotificationEntry entry)173     public void releaseViews(NotificationEntry entry) {
174         if (!entry.rowExists()) {
175             mLogger.logNotReleasingViewsRowDoesntExist(entry);
176             return;
177         }
178         mLogger.logReleasingViews(entry);
179         cancelRunningJobs(entry.getRow());
180         final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
181         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
182         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
183         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
184         if (AsyncHybridViewInflation.isEnabled()) {
185             params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
186         }
187         mRowContentBindStage.requestRebind(entry, null);
188     }
189 
maybeSetBigPictureIconManager(ExpandableNotificationRow row, ExpandableNotificationRowComponent component)190     private void maybeSetBigPictureIconManager(ExpandableNotificationRow row,
191             ExpandableNotificationRowComponent component) {
192         if (mFeatureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
193             row.setBigPictureIconManager(component.getBigPictureIconManager());
194         }
195     }
196 
cancelRunningJobs(ExpandableNotificationRow row)197     private void cancelRunningJobs(ExpandableNotificationRow row) {
198         if (row == null) {
199             return;
200         }
201         BigPictureIconManager iconManager = row.getBigPictureIconManager();
202         if (iconManager != null) {
203             iconManager.cancelJobs();
204         }
205     }
206 
207     /**
208      * Bind row to various controllers and managers. This is only called when the row is first
209      * created.
210      *
211      * TODO: This method associates a row with an entry, but eventually needs to not do that
212      */
bindRow(NotificationEntry entry, ExpandableNotificationRow row)213     private void bindRow(NotificationEntry entry, ExpandableNotificationRow row) {
214         mListContainer.bindRow(row);
215         mNotificationRemoteInputManager.bindRow(row);
216         entry.setRow(row);
217         mNotifBindPipeline.manageRow(entry, row);
218         mPresenter.onBindRow(row);
219     }
220 
221     /**
222      * Update row after the notification has updated.
223      *
224      * @param entry notification that has updated
225      */
updateRow( NotificationEntry entry, ExpandableNotificationRow row)226     private void updateRow(
227             NotificationEntry entry,
228             ExpandableNotificationRow row) {
229         row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
230                 && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
231 
232         // bind the click event to the content area
233         requireNonNull(mNotificationClicker).register(row, entry.getSbn());
234     }
235 
236     /**
237      * Inflate the row's basic content views.
238      */
inflateContentViews( NotificationEntry entry, @NonNull NotifInflater.Params inflaterParams, ExpandableNotificationRow row, @Nullable NotificationRowContentBinder.InflationCallback inflationCallback)239     private void inflateContentViews(
240             NotificationEntry entry,
241             @NonNull NotifInflater.Params inflaterParams,
242             ExpandableNotificationRow row,
243             @Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {
244         final boolean useIncreasedCollapsedHeight =
245                 mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance());
246         final boolean isMinimized = inflaterParams.isMinimized();
247 
248         // Set show snooze action
249         row.setShowSnooze(inflaterParams.getShowSnooze());
250 
251         RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
252         params.requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
253         params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
254         params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
255         params.setUseMinimized(isMinimized);
256 
257         if (screenshareNotificationHiding()
258                 ? inflaterParams.getNeedsRedaction()
259                 : mNotificationLockscreenUserManager.needsRedaction(entry)) {
260             params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
261         } else {
262             params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
263         }
264 
265         if (AsyncHybridViewInflation.isEnabled()) {
266             if (inflaterParams.isChildInGroup()) {
267                 params.requireContentViews(FLAG_CONTENT_VIEW_SINGLE_LINE);
268             } else {
269                 // TODO(b/217799515): here we decide whether to free the single-line view
270                 //  when the group status changes
271                 params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
272             }
273         }
274 
275         if (AsyncGroupHeaderViewInflation.isEnabled()) {
276             if (inflaterParams.isGroupSummary()) {
277                 params.requireContentViews(FLAG_GROUP_SUMMARY_HEADER);
278                 if (isMinimized) {
279                     params.requireContentViews(FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER);
280                 }
281             } else {
282                 params.markContentViewsFreeable(FLAG_GROUP_SUMMARY_HEADER);
283                 params.markContentViewsFreeable(FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER);
284             }
285         }
286         params.rebindAllContentViews();
287         mLogger.logRequestingRebind(entry, inflaterParams);
288         mRowContentBindStage.requestRebind(entry, en -> {
289             mLogger.logRebindComplete(entry);
290             row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
291             row.setIsMinimized(isMinimized);
292             if (inflationCallback != null) {
293                 inflationCallback.onAsyncInflationFinished(en);
294             }
295         });
296     }
297 }
298