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