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