1 /* 2 * Copyright (C) 2017 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; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.os.Trace; 22 import android.service.notification.NotificationListenerService; 23 import android.util.Log; 24 import android.view.View; 25 import android.view.ViewGroup; 26 27 import com.android.systemui.Dependency; 28 import com.android.systemui.R; 29 import com.android.systemui.statusbar.notification.VisualStabilityManager; 30 import com.android.systemui.statusbar.phone.NotificationGroupManager; 31 32 import java.util.ArrayList; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Stack; 36 37 /** 38 * NotificationViewHierarchyManager manages updating the view hierarchy of notification views based 39 * on their group structure. For example, if a notification becomes bundled with another, 40 * NotificationViewHierarchyManager will update the view hierarchy to reflect that. It also will 41 * tell NotificationListContainer which notifications to display, and inform it of changes to those 42 * notifications that might affect their display. 43 */ 44 public class NotificationViewHierarchyManager { 45 private static final String TAG = "NotificationViewHierarchyManager"; 46 47 private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> 48 mTmpChildOrderMap = new HashMap<>(); 49 50 // Dependencies: 51 protected final NotificationLockscreenUserManager mLockscreenUserManager = 52 Dependency.get(NotificationLockscreenUserManager.class); 53 protected final NotificationGroupManager mGroupManager = 54 Dependency.get(NotificationGroupManager.class); 55 protected final VisualStabilityManager mVisualStabilityManager = 56 Dependency.get(VisualStabilityManager.class); 57 58 /** 59 * {@code true} if notifications not part of a group should by default be rendered in their 60 * expanded state. If {@code false}, then only the first notification will be expanded if 61 * possible. 62 */ 63 private final boolean mAlwaysExpandNonGroupedNotification; 64 65 private NotificationPresenter mPresenter; 66 private NotificationEntryManager mEntryManager; 67 private NotificationListContainer mListContainer; 68 NotificationViewHierarchyManager(Context context)69 public NotificationViewHierarchyManager(Context context) { 70 Resources res = context.getResources(); 71 mAlwaysExpandNonGroupedNotification = 72 res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications); 73 } 74 setUpWithPresenter(NotificationPresenter presenter, NotificationEntryManager entryManager, NotificationListContainer listContainer)75 public void setUpWithPresenter(NotificationPresenter presenter, 76 NotificationEntryManager entryManager, NotificationListContainer listContainer) { 77 mPresenter = presenter; 78 mEntryManager = entryManager; 79 mListContainer = listContainer; 80 } 81 82 /** 83 * Updates the visual representation of the notifications. 84 */ updateNotificationViews()85 public void updateNotificationViews() { 86 ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData() 87 .getActiveNotifications(); 88 ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size()); 89 final int N = activeNotifications.size(); 90 for (int i = 0; i < N; i++) { 91 NotificationData.Entry ent = activeNotifications.get(i); 92 if (ent.row.isDismissed() || ent.row.isRemoved()) { 93 // we don't want to update removed notifications because they could 94 // temporarily become children if they were isolated before. 95 continue; 96 } 97 int userId = ent.notification.getUserId(); 98 99 // Display public version of the notification if we need to redact. 100 // TODO: This area uses a lot of calls into NotificationLockscreenUserManager. 101 // We can probably move some of this code there. 102 boolean devicePublic = mLockscreenUserManager.isLockscreenPublicMode( 103 mLockscreenUserManager.getCurrentUserId()); 104 boolean userPublic = devicePublic 105 || mLockscreenUserManager.isLockscreenPublicMode(userId); 106 boolean needsRedaction = mLockscreenUserManager.needsRedaction(ent); 107 boolean sensitive = userPublic && needsRedaction; 108 boolean deviceSensitive = devicePublic 109 && !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic( 110 mLockscreenUserManager.getCurrentUserId()); 111 ent.row.setSensitive(sensitive, deviceSensitive); 112 ent.row.setNeedsRedaction(needsRedaction); 113 if (mGroupManager.isChildInGroupWithSummary(ent.row.getStatusBarNotification())) { 114 ExpandableNotificationRow summary = mGroupManager.getGroupSummary( 115 ent.row.getStatusBarNotification()); 116 List<ExpandableNotificationRow> orderedChildren = 117 mTmpChildOrderMap.get(summary); 118 if (orderedChildren == null) { 119 orderedChildren = new ArrayList<>(); 120 mTmpChildOrderMap.put(summary, orderedChildren); 121 } 122 orderedChildren.add(ent.row); 123 } else { 124 toShow.add(ent.row); 125 } 126 127 } 128 129 ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(); 130 for (int i=0; i< mListContainer.getContainerChildCount(); i++) { 131 View child = mListContainer.getContainerChildAt(i); 132 if (!toShow.contains(child) && child instanceof ExpandableNotificationRow) { 133 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 134 135 // Blocking helper is effectively a detached view. Don't bother removing it from the 136 // layout. 137 if (!row.isBlockingHelperShowing()) { 138 viewsToRemove.add((ExpandableNotificationRow) child); 139 } 140 } 141 } 142 143 for (ExpandableNotificationRow viewToRemove : viewsToRemove) { 144 if (mGroupManager.isChildInGroupWithSummary(viewToRemove.getStatusBarNotification())) { 145 // we are only transferring this notification to its parent, don't generate an 146 // animation 147 mListContainer.setChildTransferInProgress(true); 148 } 149 if (viewToRemove.isSummaryWithChildren()) { 150 viewToRemove.removeAllChildren(); 151 } 152 mListContainer.removeContainerView(viewToRemove); 153 mListContainer.setChildTransferInProgress(false); 154 } 155 156 removeNotificationChildren(); 157 158 for (int i = 0; i < toShow.size(); i++) { 159 View v = toShow.get(i); 160 if (v.getParent() == null) { 161 mVisualStabilityManager.notifyViewAddition(v); 162 mListContainer.addContainerView(v); 163 } 164 } 165 166 addNotificationChildrenAndSort(); 167 168 // So after all this work notifications still aren't sorted correctly. 169 // Let's do that now by advancing through toShow and mListContainer in 170 // lock-step, making sure mListContainer matches what we see in toShow. 171 int j = 0; 172 for (int i = 0; i < mListContainer.getContainerChildCount(); i++) { 173 View child = mListContainer.getContainerChildAt(i); 174 if (!(child instanceof ExpandableNotificationRow)) { 175 // We don't care about non-notification views. 176 continue; 177 } 178 if (((ExpandableNotificationRow) child).isBlockingHelperShowing()) { 179 // Don't count/reorder notifications that are showing the blocking helper! 180 continue; 181 } 182 183 ExpandableNotificationRow targetChild = toShow.get(j); 184 if (child != targetChild) { 185 // Oops, wrong notification at this position. Put the right one 186 // here and advance both lists. 187 if (mVisualStabilityManager.canReorderNotification(targetChild)) { 188 mListContainer.changeViewPosition(targetChild, i); 189 } else { 190 mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager); 191 } 192 } 193 j++; 194 195 } 196 197 mVisualStabilityManager.onReorderingFinished(); 198 // clear the map again for the next usage 199 mTmpChildOrderMap.clear(); 200 201 updateRowStates(); 202 203 mListContainer.onNotificationViewUpdateFinished(); 204 } 205 addNotificationChildrenAndSort()206 private void addNotificationChildrenAndSort() { 207 // Let's now add all notification children which are missing 208 boolean orderChanged = false; 209 for (int i = 0; i < mListContainer.getContainerChildCount(); i++) { 210 View view = mListContainer.getContainerChildAt(i); 211 if (!(view instanceof ExpandableNotificationRow)) { 212 // We don't care about non-notification views. 213 continue; 214 } 215 216 ExpandableNotificationRow parent = (ExpandableNotificationRow) view; 217 List<ExpandableNotificationRow> children = parent.getNotificationChildren(); 218 List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); 219 220 for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size(); 221 childIndex++) { 222 ExpandableNotificationRow childView = orderedChildren.get(childIndex); 223 if (children == null || !children.contains(childView)) { 224 if (childView.getParent() != null) { 225 Log.wtf(TAG, "trying to add a notification child that already has " + 226 "a parent. class:" + childView.getParent().getClass() + 227 "\n child: " + childView); 228 // This shouldn't happen. We can recover by removing it though. 229 ((ViewGroup) childView.getParent()).removeView(childView); 230 } 231 mVisualStabilityManager.notifyViewAddition(childView); 232 parent.addChildNotification(childView, childIndex); 233 mListContainer.notifyGroupChildAdded(childView); 234 } 235 } 236 237 // Finally after removing and adding has been performed we can apply the order. 238 orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, 239 mEntryManager); 240 } 241 if (orderChanged) { 242 mListContainer.generateChildOrderChangedEvent(); 243 } 244 } 245 removeNotificationChildren()246 private void removeNotificationChildren() { 247 // First let's remove all children which don't belong in the parents 248 ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>(); 249 for (int i = 0; i < mListContainer.getContainerChildCount(); i++) { 250 View view = mListContainer.getContainerChildAt(i); 251 if (!(view instanceof ExpandableNotificationRow)) { 252 // We don't care about non-notification views. 253 continue; 254 } 255 256 ExpandableNotificationRow parent = (ExpandableNotificationRow) view; 257 List<ExpandableNotificationRow> children = parent.getNotificationChildren(); 258 List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent); 259 260 if (children != null) { 261 toRemove.clear(); 262 for (ExpandableNotificationRow childRow : children) { 263 if ((orderedChildren == null 264 || !orderedChildren.contains(childRow)) 265 && !childRow.keepInParent()) { 266 toRemove.add(childRow); 267 } 268 } 269 for (ExpandableNotificationRow remove : toRemove) { 270 parent.removeChildNotification(remove); 271 if (mEntryManager.getNotificationData().get( 272 remove.getStatusBarNotification().getKey()) == null) { 273 // We only want to add an animation if the view is completely removed 274 // otherwise it's just a transfer 275 mListContainer.notifyGroupChildRemoved(remove, 276 parent.getChildrenContainer()); 277 } 278 } 279 } 280 } 281 } 282 283 /** 284 * Updates expanded, dimmed and locked states of notification rows. 285 */ updateRowStates()286 public void updateRowStates() { 287 Trace.beginSection("NotificationViewHierarchyManager#updateRowStates"); 288 final int N = mListContainer.getContainerChildCount(); 289 290 int visibleNotifications = 0; 291 boolean isLocked = mPresenter.isPresenterLocked(); 292 int maxNotifications = -1; 293 if (isLocked) { 294 maxNotifications = mPresenter.getMaxNotificationsWhileLocked(true /* recompute */); 295 } 296 mListContainer.setMaxDisplayedNotifications(maxNotifications); 297 Stack<ExpandableNotificationRow> stack = new Stack<>(); 298 for (int i = N - 1; i >= 0; i--) { 299 View child = mListContainer.getContainerChildAt(i); 300 if (!(child instanceof ExpandableNotificationRow)) { 301 continue; 302 } 303 stack.push((ExpandableNotificationRow) child); 304 } 305 while(!stack.isEmpty()) { 306 ExpandableNotificationRow row = stack.pop(); 307 NotificationData.Entry entry = row.getEntry(); 308 boolean isChildNotification = 309 mGroupManager.isChildInGroupWithSummary(entry.notification); 310 311 row.setOnKeyguard(isLocked); 312 313 if (!isLocked) { 314 // If mAlwaysExpandNonGroupedNotification is false, then only expand the 315 // very first notification and if it's not a child of grouped notifications. 316 row.setSystemExpanded(mAlwaysExpandNonGroupedNotification 317 || (visibleNotifications == 0 && !isChildNotification 318 && !row.isLowPriority())); 319 } 320 321 entry.row.setShowAmbient(mPresenter.isDozing()); 322 int userId = entry.notification.getUserId(); 323 boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup( 324 entry.notification) && !entry.row.isRemoved(); 325 boolean showOnKeyguard = mLockscreenUserManager.shouldShowOnKeyguard(entry 326 .notification); 327 if (suppressedSummary 328 || mLockscreenUserManager.shouldHideNotifications(userId) 329 || (isLocked && !showOnKeyguard)) { 330 entry.row.setVisibility(View.GONE); 331 } else { 332 boolean wasGone = entry.row.getVisibility() == View.GONE; 333 if (wasGone) { 334 entry.row.setVisibility(View.VISIBLE); 335 } 336 if (!isChildNotification && !entry.row.isRemoved()) { 337 if (wasGone) { 338 // notify the scroller of a child addition 339 mListContainer.generateAddAnimation(entry.row, 340 !showOnKeyguard /* fromMoreCard */); 341 } 342 visibleNotifications++; 343 } 344 } 345 if (row.isSummaryWithChildren()) { 346 List<ExpandableNotificationRow> notificationChildren = 347 row.getNotificationChildren(); 348 int size = notificationChildren.size(); 349 for (int i = size - 1; i >= 0; i--) { 350 stack.push(notificationChildren.get(i)); 351 } 352 } 353 354 row.showAppOpsIcons(entry.mActiveAppOps); 355 } 356 357 Trace.beginSection("NotificationPresenter#onUpdateRowStates"); 358 mPresenter.onUpdateRowStates(); 359 Trace.endSection(); 360 Trace.endSection(); 361 } 362 } 363