• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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