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