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