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 java.util.Objects.requireNonNull;
20 
21 import android.annotation.Nullable;
22 import android.content.Context;
23 import android.os.Build;
24 import android.view.ViewGroup;
25 
26 import com.android.internal.util.NotificationMessagingUtil;
27 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
28 import com.android.systemui.statusbar.NotificationPresenter;
29 import com.android.systemui.statusbar.NotificationRemoteInputManager;
30 import com.android.systemui.statusbar.NotificationUiAdjustment;
31 import com.android.systemui.statusbar.notification.InflationException;
32 import com.android.systemui.statusbar.notification.NotificationClicker;
33 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
34 import com.android.systemui.statusbar.notification.icon.IconManager;
35 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
36 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
37 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
38 import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
39 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
40 import com.android.systemui.statusbar.notification.row.RowContentBindParams;
41 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
42 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
43 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
44 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
45 
46 import javax.inject.Inject;
47 import javax.inject.Provider;
48 import javax.inject.Singleton;
49 
50 /** Handles inflating and updating views for notifications. */
51 @Singleton
52 public class NotificationRowBinderImpl implements NotificationRowBinder {
53 
54     private static final String TAG = "NotificationViewManager";
55 
56     private final Context mContext;
57     private final NotificationMessagingUtil mMessagingUtil;
58     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
59     private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
60     private final NotifBindPipeline mNotifBindPipeline;
61     private final RowContentBindStage mRowContentBindStage;
62     private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
63     private final Provider<RowInflaterTask> mRowInflaterTaskProvider;
64     private final ExpandableNotificationRowComponent.Builder
65             mExpandableNotificationRowComponentBuilder;
66     private final IconManager mIconManager;
67     private final LowPriorityInflationHelper mLowPriorityInflationHelper;
68 
69     private NotificationPresenter mPresenter;
70     private NotificationListContainer mListContainer;
71     private BindRowCallback mBindRowCallback;
72     private NotificationClicker mNotificationClicker;
73 
74     @Inject
NotificationRowBinderImpl( Context context, NotificationMessagingUtil notificationMessagingUtil, NotificationRemoteInputManager notificationRemoteInputManager, NotificationLockscreenUserManager notificationLockscreenUserManager, NotifBindPipeline notifBindPipeline, RowContentBindStage rowContentBindStage, NotificationInterruptStateProvider notificationInterruptionStateProvider, Provider<RowInflaterTask> rowInflaterTaskProvider, ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder, IconManager iconManager, LowPriorityInflationHelper lowPriorityInflationHelper)75     public NotificationRowBinderImpl(
76             Context context,
77             NotificationMessagingUtil notificationMessagingUtil,
78             NotificationRemoteInputManager notificationRemoteInputManager,
79             NotificationLockscreenUserManager notificationLockscreenUserManager,
80             NotifBindPipeline notifBindPipeline,
81             RowContentBindStage rowContentBindStage,
82             NotificationInterruptStateProvider notificationInterruptionStateProvider,
83             Provider<RowInflaterTask> rowInflaterTaskProvider,
84             ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
85             IconManager iconManager,
86             LowPriorityInflationHelper lowPriorityInflationHelper) {
87         mContext = context;
88         mNotifBindPipeline = notifBindPipeline;
89         mRowContentBindStage = rowContentBindStage;
90         mMessagingUtil = notificationMessagingUtil;
91         mNotificationRemoteInputManager = notificationRemoteInputManager;
92         mNotificationLockscreenUserManager = notificationLockscreenUserManager;
93         mNotificationInterruptStateProvider = notificationInterruptionStateProvider;
94         mRowInflaterTaskProvider = rowInflaterTaskProvider;
95         mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
96         mIconManager = iconManager;
97         mLowPriorityInflationHelper = lowPriorityInflationHelper;
98     }
99 
100     /**
101      * Sets up late-bound dependencies for this component.
102      */
setUpWithPresenter(NotificationPresenter presenter, NotificationListContainer listContainer, BindRowCallback bindRowCallback)103     public void setUpWithPresenter(NotificationPresenter presenter,
104             NotificationListContainer listContainer,
105             BindRowCallback bindRowCallback) {
106         mPresenter = presenter;
107         mListContainer = listContainer;
108         mBindRowCallback = bindRowCallback;
109 
110         mIconManager.attach();
111     }
112 
setNotificationClicker(NotificationClicker clicker)113     public void setNotificationClicker(NotificationClicker clicker) {
114         mNotificationClicker = clicker;
115     }
116 
117     /**
118      * Inflates the views for the given entry (possibly asynchronously).
119      */
120     @Override
inflateViews( NotificationEntry entry, Runnable onDismissRunnable, NotificationRowContentBinder.InflationCallback callback)121     public void inflateViews(
122             NotificationEntry entry,
123             Runnable onDismissRunnable,
124             NotificationRowContentBinder.InflationCallback callback)
125             throws InflationException {
126         ViewGroup parent = mListContainer.getViewParentForNotification(entry);
127 
128         if (entry.rowExists()) {
129             mIconManager.updateIcons(entry);
130             ExpandableNotificationRow row = entry.getRow();
131             row.reset();
132             updateRow(entry, row);
133             inflateContentViews(entry, row, callback);
134             entry.getRowController().setOnDismissRunnable(onDismissRunnable);
135         } else {
136             mIconManager.createIcons(entry);
137             mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
138                     row -> {
139                         // Setup the controller for the view.
140                         ExpandableNotificationRowComponent component =
141                                 mExpandableNotificationRowComponentBuilder
142                                         .expandableNotificationRow(row)
143                                         .notificationEntry(entry)
144                                         .onDismissRunnable(onDismissRunnable)
145                                         .rowContentBindStage(mRowContentBindStage)
146                                         .onExpandClickListener(mPresenter)
147                                         .build();
148                         ExpandableNotificationRowController rowController =
149                                 component.getExpandableNotificationRowController();
150                         rowController.init();
151                         entry.setRowController(rowController);
152                         bindRow(entry, row);
153                         updateRow(entry, row);
154                         inflateContentViews(entry, row, callback);
155                     });
156         }
157     }
158 
159     /**
160      * Bind row to various controllers and managers. This is only called when the row is first
161      * created.
162      *
163      * TODO: This method associates a row with an entry, but eventually needs to not do that
164      */
bindRow(NotificationEntry entry, ExpandableNotificationRow row)165     private void bindRow(NotificationEntry entry, ExpandableNotificationRow row) {
166         mListContainer.bindRow(row);
167         mNotificationRemoteInputManager.bindRow(row);
168         row.setOnActivatedListener(mPresenter);
169         entry.setRow(row);
170         row.setEntry(entry);
171         mNotifBindPipeline.manageRow(entry, row);
172         mBindRowCallback.onBindRow(row);
173     }
174 
175     /**
176      * Updates the views bound to an entry when the entry's ranking changes, either in-place or by
177      * reinflating them.
178      *
179      * TODO: Should this method be in this class?
180      */
181     @Override
onNotificationRankingUpdated( NotificationEntry entry, @Nullable Integer oldImportance, NotificationUiAdjustment oldAdjustment, NotificationUiAdjustment newAdjustment, NotificationRowContentBinder.InflationCallback callback)182     public void onNotificationRankingUpdated(
183             NotificationEntry entry,
184             @Nullable Integer oldImportance,
185             NotificationUiAdjustment oldAdjustment,
186             NotificationUiAdjustment newAdjustment,
187             NotificationRowContentBinder.InflationCallback callback) {
188         if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) {
189             if (entry.rowExists()) {
190                 ExpandableNotificationRow row = entry.getRow();
191                 row.reset();
192                 updateRow(entry, row);
193                 inflateContentViews(entry, row, callback);
194             } else {
195                 // Once the RowInflaterTask is done, it will pick up the updated entry, so
196                 // no-op here.
197             }
198         } else {
199             if (oldImportance != null && entry.getImportance() != oldImportance) {
200                 if (entry.rowExists()) {
201                     entry.getRow().onNotificationRankingUpdated();
202                 }
203             }
204         }
205     }
206 
207     /**
208      * Update row after the notification has updated.
209      *
210      * @param entry notification that has updated
211      */
updateRow( NotificationEntry entry, ExpandableNotificationRow row)212     private void updateRow(
213             NotificationEntry entry,
214             ExpandableNotificationRow row) {
215         row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
216                 && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
217 
218         // bind the click event to the content area
219         requireNonNull(mNotificationClicker).register(row, entry.getSbn());
220     }
221 
222     /**
223      * Inflate the row's basic content views.
224      */
inflateContentViews( NotificationEntry entry, ExpandableNotificationRow row, @Nullable NotificationRowContentBinder.InflationCallback inflationCallback)225     private void inflateContentViews(
226             NotificationEntry entry,
227             ExpandableNotificationRow row,
228             @Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {
229         final boolean useIncreasedCollapsedHeight =
230                 mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance());
231         // If this is our first time inflating, we don't actually know the groupings for real
232         // yet, so we might actually inflate a low priority content view incorrectly here and have
233         // to correct it later in the pipeline. On subsequent inflations (i.e. updates), this
234         // should inflate the correct view.
235         final boolean isLowPriority = mLowPriorityInflationHelper.shouldUseLowPriorityView(entry);
236 
237         RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
238         params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
239         params.setUseLowPriority(isLowPriority);
240 
241         // TODO: Replace this API with RowContentBindParams directly. Also move to a separate
242         // redaction controller.
243         row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
244 
245         params.rebindAllContentViews();
246         mRowContentBindStage.requestRebind(entry, en -> {
247             row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
248             row.setIsLowPriority(isLowPriority);
249             if (inflationCallback != null) {
250                 inflationCallback.onAsyncInflationFinished(en);
251             }
252         });
253     }
254 
255     /** Callback for when a row is bound to an entry. */
256     public interface BindRowCallback {
257         /**
258          * Called when a new row is created and bound to a notification.
259          */
onBindRow(ExpandableNotificationRow row)260         void onBindRow(ExpandableNotificationRow row);
261     }
262 }
263