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.row;
18 
19 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
20 import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
21 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
22 import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
23 
24 import android.net.Uri;
25 import android.os.UserHandle;
26 import android.provider.Settings;
27 import android.util.Log;
28 import android.view.View;
29 import android.view.ViewGroup;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.internal.logging.MetricsLogger;
36 import com.android.internal.statusbar.IStatusBarService;
37 import com.android.systemui.classifier.FalsingCollector;
38 import com.android.systemui.flags.FeatureFlags;
39 import com.android.systemui.flags.Flags;
40 import com.android.systemui.plugins.FalsingManager;
41 import com.android.systemui.plugins.PluginManager;
42 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
43 import com.android.systemui.plugins.statusbar.StatusBarStateController;
44 import com.android.systemui.statusbar.SmartReplyController;
45 import com.android.systemui.statusbar.notification.FeedbackIcon;
46 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
47 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
48 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
49 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
50 import com.android.systemui.statusbar.notification.collection.render.NodeController;
51 import com.android.systemui.statusbar.notification.collection.render.NotifViewController;
52 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
53 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
54 import com.android.systemui.statusbar.notification.row.dagger.AppName;
55 import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
56 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
57 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
58 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
59 import com.android.systemui.statusbar.phone.KeyguardBypassController;
60 import com.android.systemui.statusbar.policy.HeadsUpManager;
61 import com.android.systemui.statusbar.policy.SmartReplyConstants;
62 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
63 import com.android.systemui.util.time.SystemClock;
64 import com.android.systemui.wmshell.BubblesManager;
65 
66 import java.util.List;
67 import java.util.Optional;
68 
69 import javax.inject.Inject;
70 import javax.inject.Named;
71 
72 /**
73  * Controller for {@link ExpandableNotificationRow}.
74  */
75 @NotificationRowScope
76 public class ExpandableNotificationRowController implements NotifViewController {
77     private static final String TAG = "NotifRowController";
78 
79     static final Uri BUBBLES_SETTING_URI =
80             Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
81     private static final String BUBBLES_SETTING_ENABLED_VALUE = "1";
82     private final ExpandableNotificationRow mView;
83     private final NotificationListContainer mListContainer;
84     private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
85     private final ActivatableNotificationViewController mActivatableNotificationViewController;
86     private final PluginManager mPluginManager;
87     private final SystemClock mClock;
88     private final String mAppName;
89     private final String mNotificationKey;
90     private final KeyguardBypassController mKeyguardBypassController;
91     private final GroupMembershipManager mGroupMembershipManager;
92     private final GroupExpansionManager mGroupExpansionManager;
93     private final RowContentBindStage mRowContentBindStage;
94     private final NotificationLogger mNotificationLogger;
95     private final NotificationRowLogger mLogBufferLogger;
96     private final HeadsUpManager mHeadsUpManager;
97     private final ExpandableNotificationRow.OnExpandClickListener mOnExpandClickListener;
98     private final StatusBarStateController mStatusBarStateController;
99     private final MetricsLogger mMetricsLogger;
100     private final NotificationChildrenContainerLogger mChildrenContainerLogger;
101     private final ExpandableNotificationRow.CoordinateOnClickListener mOnFeedbackClickListener;
102     private final NotificationGutsManager mNotificationGutsManager;
103     private final OnUserInteractionCallback mOnUserInteractionCallback;
104     private final FalsingManager mFalsingManager;
105     private final FalsingCollector mFalsingCollector;
106     private final FeatureFlags mFeatureFlags;
107     private final boolean mAllowLongPress;
108     private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
109     private final Optional<BubblesManager> mBubblesManagerOptional;
110     private final SmartReplyConstants mSmartReplyConstants;
111     private final SmartReplyController mSmartReplyController;
112     private final ExpandableNotificationRowDragController mDragController;
113     private final NotificationDismissibilityProvider mDismissibilityProvider;
114     private final IStatusBarService mStatusBarService;
115 
116     private final NotificationSettingsController mSettingsController;
117 
118     @VisibleForTesting
119     final NotificationSettingsController.Listener mSettingsListener =
120             new NotificationSettingsController.Listener() {
121                 @Override
122                 public void onSettingChanged(Uri setting, int userId, String value) {
123                     if (BUBBLES_SETTING_URI.equals(setting)) {
124                         final int viewUserId = mView.getEntry().getSbn().getUserId();
125                         if (viewUserId == UserHandle.USER_ALL || viewUserId == userId) {
126                             mView.getPrivateLayout().setBubblesEnabledForUser(
127                                     BUBBLES_SETTING_ENABLED_VALUE.equals(value));
128                         }
129                     }
130                 }
131             };
132     private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
133             new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
134                 @Override
135                 public void logNotificationExpansion(String key, boolean userAction,
136                         boolean expanded) {
137                     mNotificationLogger.onExpansionChanged(key, userAction, expanded);
138                 }
139 
140                 @Override
141                 public void logKeepInParentChildDetached(
142                         NotificationEntry child,
143                         NotificationEntry oldParent
144                 ) {
145                     mLogBufferLogger.logKeepInParentChildDetached(child, oldParent);
146                 }
147 
148                 @Override
149                 public void logSkipAttachingKeepInParentChild(
150                         NotificationEntry child,
151                         NotificationEntry newParent
152                 ) {
153                     mLogBufferLogger.logSkipAttachingKeepInParentChild(child, newParent);
154                 }
155 
156                 @Override
157                 public void logRemoveTransientFromContainer(
158                         NotificationEntry childEntry,
159                         NotificationEntry containerEntry
160                 ) {
161                     mLogBufferLogger.logRemoveTransientFromContainer(childEntry, containerEntry);
162                 }
163 
164                 @Override
165                 public void logRemoveTransientFromNssl(
166                         NotificationEntry childEntry
167                 ) {
168                     mLogBufferLogger.logRemoveTransientFromNssl(childEntry);
169                 }
170 
171                 @Override
172                 public void logRemoveTransientFromViewGroup(
173                         NotificationEntry childEntry,
174                         ViewGroup containerView
175                 ) {
176                     mLogBufferLogger.logRemoveTransientFromViewGroup(childEntry, containerView);
177                 }
178 
179                 @Override
180                 public void logAddTransientRow(
181                         NotificationEntry childEntry,
182                         NotificationEntry containerEntry,
183                         int index
184                 ) {
185                     mLogBufferLogger.logAddTransientRow(childEntry, containerEntry, index);
186                 }
187 
188                 @Override
189                 public void logRemoveTransientRow(
190                         NotificationEntry childEntry,
191                         NotificationEntry containerEntry
192                 ) {
193                     mLogBufferLogger.logRemoveTransientRow(childEntry, containerEntry);
194                 }
195             };
196 
197 
198     @Inject
ExpandableNotificationRowController( ExpandableNotificationRow view, ActivatableNotificationViewController activatableNotificationViewController, RemoteInputViewSubcomponent.Factory rivSubcomponentFactory, MetricsLogger metricsLogger, NotificationRowLogger logBufferLogger, NotificationChildrenContainerLogger childrenContainerLogger, NotificationListContainer listContainer, SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController, PluginManager pluginManager, SystemClock clock, @AppName String appName, @NotificationKey String notificationKey, KeyguardBypassController keyguardBypassController, GroupMembershipManager groupMembershipManager, GroupExpansionManager groupExpansionManager, RowContentBindStage rowContentBindStage, NotificationLogger notificationLogger, HeadsUpManager headsUpManager, ExpandableNotificationRow.OnExpandClickListener onExpandClickListener, StatusBarStateController statusBarStateController, NotificationGutsManager notificationGutsManager, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress, OnUserInteractionCallback onUserInteractionCallback, FalsingManager falsingManager, FalsingCollector falsingCollector, FeatureFlags featureFlags, PeopleNotificationIdentifier peopleNotificationIdentifier, Optional<BubblesManager> bubblesManagerOptional, NotificationSettingsController settingsController, ExpandableNotificationRowDragController dragController, NotificationDismissibilityProvider dismissibilityProvider, IStatusBarService statusBarService)199     public ExpandableNotificationRowController(
200             ExpandableNotificationRow view,
201             ActivatableNotificationViewController activatableNotificationViewController,
202             RemoteInputViewSubcomponent.Factory rivSubcomponentFactory,
203             MetricsLogger metricsLogger,
204             NotificationRowLogger logBufferLogger,
205             NotificationChildrenContainerLogger childrenContainerLogger,
206             NotificationListContainer listContainer,
207             SmartReplyConstants smartReplyConstants,
208             SmartReplyController smartReplyController,
209             PluginManager pluginManager,
210             SystemClock clock,
211             @AppName String appName,
212             @NotificationKey String notificationKey,
213             KeyguardBypassController keyguardBypassController,
214             GroupMembershipManager groupMembershipManager,
215             GroupExpansionManager groupExpansionManager,
216             RowContentBindStage rowContentBindStage,
217             NotificationLogger notificationLogger,
218             HeadsUpManager headsUpManager,
219             ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,
220             StatusBarStateController statusBarStateController,
221             NotificationGutsManager notificationGutsManager,
222             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
223             OnUserInteractionCallback onUserInteractionCallback,
224             FalsingManager falsingManager,
225             FalsingCollector falsingCollector,
226             FeatureFlags featureFlags,
227             PeopleNotificationIdentifier peopleNotificationIdentifier,
228             Optional<BubblesManager> bubblesManagerOptional,
229             NotificationSettingsController settingsController,
230             ExpandableNotificationRowDragController dragController,
231             NotificationDismissibilityProvider dismissibilityProvider,
232             IStatusBarService statusBarService) {
233         mView = view;
234         mListContainer = listContainer;
235         mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory;
236         mActivatableNotificationViewController = activatableNotificationViewController;
237         mPluginManager = pluginManager;
238         mClock = clock;
239         mAppName = appName;
240         mNotificationKey = notificationKey;
241         mKeyguardBypassController = keyguardBypassController;
242         mGroupMembershipManager = groupMembershipManager;
243         mGroupExpansionManager = groupExpansionManager;
244         mRowContentBindStage = rowContentBindStage;
245         mNotificationLogger = notificationLogger;
246         mHeadsUpManager = headsUpManager;
247         mOnExpandClickListener = onExpandClickListener;
248         mStatusBarStateController = statusBarStateController;
249         mNotificationGutsManager = notificationGutsManager;
250         mOnUserInteractionCallback = onUserInteractionCallback;
251         mFalsingManager = falsingManager;
252         mOnFeedbackClickListener = mNotificationGutsManager::openGuts;
253         mAllowLongPress = allowLongPress;
254         mFalsingCollector = falsingCollector;
255         mFeatureFlags = featureFlags;
256         mPeopleNotificationIdentifier = peopleNotificationIdentifier;
257         mBubblesManagerOptional = bubblesManagerOptional;
258         mSettingsController = settingsController;
259         mDragController = dragController;
260         mMetricsLogger = metricsLogger;
261         mChildrenContainerLogger = childrenContainerLogger;
262         mLogBufferLogger = logBufferLogger;
263         mSmartReplyConstants = smartReplyConstants;
264         mSmartReplyController = smartReplyController;
265         mDismissibilityProvider = dismissibilityProvider;
266         mStatusBarService = statusBarService;
267     }
268 
269     /**
270      * Initialize the controller.
271      */
init(NotificationEntry entry)272     public void init(NotificationEntry entry) {
273         mActivatableNotificationViewController.init();
274         mView.initialize(
275                 entry,
276                 mRemoteInputViewSubcomponentFactory,
277                 mAppName,
278                 mNotificationKey,
279                 mLoggerCallback,
280                 mKeyguardBypassController,
281                 mGroupMembershipManager,
282                 mGroupExpansionManager,
283                 mHeadsUpManager,
284                 mRowContentBindStage,
285                 mOnExpandClickListener,
286                 mOnFeedbackClickListener,
287                 mFalsingManager,
288                 mFalsingCollector,
289                 mStatusBarStateController,
290                 mPeopleNotificationIdentifier,
291                 mOnUserInteractionCallback,
292                 mBubblesManagerOptional,
293                 mNotificationGutsManager,
294                 mDismissibilityProvider,
295                 mMetricsLogger,
296                 mChildrenContainerLogger,
297                 mSmartReplyConstants,
298                 mSmartReplyController,
299                 mFeatureFlags,
300                 mStatusBarService
301         );
302         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
303         if (mAllowLongPress) {
304             if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_DRAG_TO_CONTENTS)) {
305                 mView.setDragController(mDragController);
306             }
307 
308             mView.setLongPressListener((v, x, y, item) -> {
309                 if (mView.isSummaryWithChildren()) {
310                     mView.expandNotification();
311                     return true;
312                 }
313                 return mNotificationGutsManager.openGuts(v, x, y, item);
314             });
315         }
316         if (ENABLE_REMOTE_INPUT) {
317             mView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
318         }
319 
320         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
321             @Override
322             public void onViewAttachedToWindow(View v) {
323                 mView.getEntry().setInitializationTime(mClock.elapsedRealtime());
324                 mPluginManager.addPluginListener(mView,
325                         NotificationMenuRowPlugin.class, false /* Allow multiple */);
326                 mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
327                 mStatusBarStateController.addCallback(mStatusBarStateListener);
328                 mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener);
329             }
330 
331             @Override
332             public void onViewDetachedFromWindow(View v) {
333                 mPluginManager.removePluginListener(mView);
334                 mStatusBarStateController.removeCallback(mStatusBarStateListener);
335                 mSettingsController.removeCallback(BUBBLES_SETTING_URI, mSettingsListener);
336             }
337         });
338     }
339 
340     private final StatusBarStateController.StateListener mStatusBarStateListener =
341             new StatusBarStateController.StateListener() {
342                 @Override
343                 public void onStateChanged(int newState) {
344                     mView.setOnKeyguard(newState == KEYGUARD);
345                 }
346             };
347 
348     @Override
349     @NonNull
getNodeLabel()350     public String getNodeLabel() {
351         return logKey(mView.getEntry());
352     }
353 
354     @Override
355     @NonNull
getView()356     public View getView() {
357         return mView;
358     }
359 
360     @Override
getChildAt(int index)361     public View getChildAt(int index) {
362         return mView.getChildNotificationAt(index);
363     }
364 
365     @Override
addChildAt(NodeController child, int index)366     public void addChildAt(NodeController child, int index) {
367         ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
368 
369         mView.addChildNotification((ExpandableNotificationRow) child.getView(), index);
370         mListContainer.notifyGroupChildAdded(childView);
371         childView.setChangingPosition(false);
372     }
373 
374     @Override
moveChildTo(NodeController child, int index)375     public void moveChildTo(NodeController child, int index) {
376         ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
377         childView.setChangingPosition(true);
378         mView.removeChildNotification(childView);
379         mView.addChildNotification(childView, index);
380         childView.setChangingPosition(false);
381     }
382 
383     @Override
removeChild(NodeController child, boolean isTransfer)384     public void removeChild(NodeController child, boolean isTransfer) {
385         ExpandableNotificationRow childView = (ExpandableNotificationRow) child.getView();
386 
387         if (isTransfer) {
388             childView.setChangingPosition(true);
389         }
390         mView.removeChildNotification(childView);
391         if (!isTransfer) {
392             mListContainer.notifyGroupChildRemoved(childView, mView.getChildrenContainer());
393         }
394     }
395 
396     @Override
onViewAdded()397     public void onViewAdded() {
398     }
399 
400     @Override
onViewMoved()401     public void onViewMoved() {
402     }
403 
404     @Override
onViewRemoved()405     public void onViewRemoved() {
406     }
407 
408     @Override
getChildCount()409     public int getChildCount() {
410         final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren();
411         return mChildren != null ? mChildren.size() : 0;
412     }
413 
414     @Override
setUntruncatedChildCount(int childCount)415     public void setUntruncatedChildCount(int childCount) {
416         if (mView.isSummaryWithChildren()) {
417             mView.setUntruncatedChildCount(childCount);
418         } else {
419             Log.w(TAG, "Called setUntruncatedChildCount(" + childCount + ") on a leaf row");
420         }
421     }
422 
423     @Override
setNotificationGroupWhen(long whenMillis)424     public void setNotificationGroupWhen(long whenMillis) {
425         if (mView.isSummaryWithChildren()) {
426             mView.setNotificationGroupWhen(whenMillis);
427         } else {
428             Log.w(TAG, "Called setNotificationTime(" + whenMillis + ") on a leaf row");
429         }
430     }
431 
432     @Override
setSystemExpanded(boolean systemExpanded)433     public void setSystemExpanded(boolean systemExpanded) {
434         mView.setSystemExpanded(systemExpanded);
435     }
436 
437     @Override
setLastAudiblyAlertedMs(long lastAudiblyAlertedMs)438     public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
439         mView.setLastAudiblyAlertedMs(lastAudiblyAlertedMs);
440     }
441 
442     @Override
setFeedbackIcon(@ullable FeedbackIcon icon)443     public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
444         mView.setFeedbackIcon(icon);
445     }
446 
447     @Override
offerToKeepInParentForAnimation()448     public boolean offerToKeepInParentForAnimation() {
449         //If the User dismissed the notification's parent, we want to keep it attached until the
450         //dismiss animation is ongoing. Therefore we don't want to remove it in the ShadeViewDiffer.
451         if (mView.isParentDismissed()) {
452             mView.setKeepInParentForDismissAnimation(true);
453             return true;
454         }
455 
456         //Otherwise the view system doesn't do the removal, so we rely on the ShadeViewDiffer
457         return false;
458     }
459 
460     @Override
removeFromParentIfKeptForAnimation()461     public boolean removeFromParentIfKeptForAnimation() {
462         ExpandableNotificationRow parent = mView.getNotificationParent();
463         if (mView.keepInParentForDismissAnimation() && parent != null) {
464             parent.removeChildNotification(mView);
465             return true;
466         }
467 
468         return false;
469     }
470 
471     @Override
resetKeepInParentForAnimation()472     public void resetKeepInParentForAnimation() {
473         mView.setKeepInParentForDismissAnimation(false);
474     }
475 }
476