NotificationGroupManager.java revision a6c0cef0a0cf9895d9241cb3293a7355c3e8af4a
125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek/* 225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * Copyright (C) 2015 The Android Open Source Project 325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * 425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * Licensed under the Apache License, Version 2.0 (the "License"); 525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * you may not use this file except in compliance with the License. 625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * You may obtain a copy of the License at 725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * 825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * http://www.apache.org/licenses/LICENSE-2.0 925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * 1025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * Unless required by applicable law or agreed to in writing, software 1125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * distributed under the License is distributed on an "AS IS" BASIS, 1225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * See the License for the specific language governing permissions and 1425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * limitations under the License 1525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek */ 1625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 1725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinekpackage com.android.systemui.statusbar.phone; 1825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 1925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinekimport android.service.notification.StatusBarNotification; 2023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinekimport android.support.annotation.Nullable; 2125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 2225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinekimport com.android.systemui.statusbar.ExpandableNotificationRow; 2325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinekimport com.android.systemui.statusbar.NotificationData; 249c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinekimport com.android.systemui.statusbar.StatusBarState; 25ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinekimport com.android.systemui.statusbar.policy.HeadsUpManager; 2625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 2725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinekimport java.util.HashMap; 2825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinekimport java.util.HashSet; 2925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 3025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek/** 3125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * A class to handle notifications and their corresponding groups. 3225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek */ 33ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinekpublic class NotificationGroupManager implements HeadsUpManager.OnHeadsUpChangedListener { 3425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 3525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>(); 3625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek private OnGroupChangeListener mListener; 3725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek private int mBarState = -1; 38a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>(); 3925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 4025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public void setOnGroupChangeListener(OnGroupChangeListener listener) { 4125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek mListener = listener; 4225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 4325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 4425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public boolean isGroupExpanded(StatusBarNotification sbn) { 45ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek NotificationGroup group = mGroupMap.get(getGroupKey(sbn)); 4625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek if (group == null) { 4725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek return false; 4825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 4925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek return group.expanded; 5025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 5125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 5225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) { 53ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek NotificationGroup group = mGroupMap.get(getGroupKey(sbn)); 5425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek if (group == null) { 5525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek return; 5625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 5725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek setGroupExpanded(group, expanded); 5825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 5925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 6025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek private void setGroupExpanded(NotificationGroup group, boolean expanded) { 6125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek group.expanded = expanded; 6225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek if (group.summary != null) { 6325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek mListener.onGroupExpansionChanged(group.summary.row, expanded); 6425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 6525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 6625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 6725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public void onEntryRemoved(NotificationData.Entry removed) { 6825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek onEntryRemovedInternal(removed, removed.notification); 6925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 7025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 7125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek /** 7225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * An entry was removed. 7325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * 7425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * @param removed the removed entry 7525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * @param sbn the notification the entry has, which doesn't need to be the same as it's internal 7625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * notification 7725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek */ 7825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek private void onEntryRemovedInternal(NotificationData.Entry removed, 7925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek final StatusBarNotification sbn) { 80ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek String groupKey = getGroupKey(sbn); 8125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek final NotificationGroup group = mGroupMap.get(groupKey); 820b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek if (group == null) { 830b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek // When an app posts 2 different notifications as summary of the same group, then a 840b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek // cancellation of the first notification removes this group. 850b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek // This situation is not supported and we will not allow such notifications anymore in 860b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek // the close future. See b/23676310 for reference. 870b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek return; 880b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek } 89ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek if (isGroupChild(sbn)) { 9025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek group.children.remove(removed); 91e73ad216d322d0e7002d1ce2e59caf23030dbf5bSelim Cinek } else { 92e73ad216d322d0e7002d1ce2e59caf23030dbf5bSelim Cinek group.summary = null; 9325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 942a7393410b6390831143dca198438a4e58bdf88aSelim Cinek updateSuppression(group); 9525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek if (group.children.isEmpty()) { 9625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek if (group.summary == null) { 9725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek mGroupMap.remove(groupKey); 9825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 9925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 10025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 10125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 102ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek public void onEntryAdded(final NotificationData.Entry added) { 103ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek final StatusBarNotification sbn = added.notification; 104ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek boolean isGroupChild = isGroupChild(sbn); 105ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek String groupKey = getGroupKey(sbn); 10625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek NotificationGroup group = mGroupMap.get(groupKey); 10725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek if (group == null) { 10825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek group = new NotificationGroup(); 10925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek mGroupMap.put(groupKey, group); 11025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 111ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek if (isGroupChild) { 112e73ad216d322d0e7002d1ce2e59caf23030dbf5bSelim Cinek group.children.add(added); 1132a7393410b6390831143dca198438a4e58bdf88aSelim Cinek updateSuppression(group); 114e73ad216d322d0e7002d1ce2e59caf23030dbf5bSelim Cinek } else { 11525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek group.summary = added; 116b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek group.expanded = added.row.areChildrenExpanded(); 1172a7393410b6390831143dca198438a4e58bdf88aSelim Cinek updateSuppression(group); 11825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek if (!group.children.isEmpty()) { 11925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek mListener.onGroupCreatedFromChildren(group); 12025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 12125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 12225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 12325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 1242a7393410b6390831143dca198438a4e58bdf88aSelim Cinek private void updateSuppression(NotificationGroup group) { 12523c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek if (group == null) { 12623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek return; 12723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek } 1282a7393410b6390831143dca198438a4e58bdf88aSelim Cinek boolean prevSuppressed = group.suppressed; 12923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek group.suppressed = group.summary != null && !group.expanded 13023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek && (group.children.size() == 1 13123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek || (group.children.size() == 0 13223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek && !group.summary.notification.getNotification().isGroupChild() 13323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek && hasIsolatedChildren(group))); 1342a7393410b6390831143dca198438a4e58bdf88aSelim Cinek if (prevSuppressed != group.suppressed) { 1352a7393410b6390831143dca198438a4e58bdf88aSelim Cinek mListener.onGroupsChanged(); 1362a7393410b6390831143dca198438a4e58bdf88aSelim Cinek } 1372a7393410b6390831143dca198438a4e58bdf88aSelim Cinek } 1382a7393410b6390831143dca198438a4e58bdf88aSelim Cinek 13923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek private boolean hasIsolatedChildren(NotificationGroup group) { 14023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek return getNumberOfIsolatedChildren(group.summary.notification.getGroupKey()) != 0; 14123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek } 14223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek 14323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek private int getNumberOfIsolatedChildren(String groupKey) { 14423c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek int count = 0; 145a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek for (StatusBarNotification sbn : mIsolatedEntries.values()) { 14623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) { 14723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek count++; 14823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek } 14923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek } 15023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek return count; 15123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek } 15223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek 15325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public void onEntryUpdated(NotificationData.Entry entry, 15425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek StatusBarNotification oldNotification) { 155ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek if (mGroupMap.get(getGroupKey(oldNotification)) != null) { 15625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek onEntryRemovedInternal(entry, oldNotification); 15725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 15825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek onEntryAdded(entry); 159a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek if (isIsolated(entry.notification)) { 160a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek mIsolatedEntries.put(entry.key, entry.notification); 16123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek String oldKey = oldNotification.getGroupKey(); 16223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek String newKey = entry.notification.getGroupKey(); 16323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek if (!oldKey.equals(newKey)) { 16423c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek updateSuppression(mGroupMap.get(oldKey)); 16523c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek updateSuppression(mGroupMap.get(newKey)); 16623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek } 16723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek } 16825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 16925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 1702a7393410b6390831143dca198438a4e58bdf88aSelim Cinek public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) { 17123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek return isGroupSuppressed(getGroupKey(sbn)) && sbn.getNotification().isGroupSummary(); 17225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 17325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 17423c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek public boolean isOnlyChildInSuppressedGroup(StatusBarNotification sbn) { 17523c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek return isGroupSuppressed(sbn.getGroupKey()) 17623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek && sbn.getNotification().isGroupChild() 17723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek && getTotalNumberOfChildren(sbn) == 1; 1782a7393410b6390831143dca198438a4e58bdf88aSelim Cinek } 1792a7393410b6390831143dca198438a4e58bdf88aSelim Cinek 18023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek private int getTotalNumberOfChildren(StatusBarNotification sbn) { 18123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek return getNumberOfIsolatedChildren(sbn.getGroupKey()) 18223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek + mGroupMap.get(sbn.getGroupKey()).children.size(); 18323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek } 18423c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek 18523c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek private boolean isGroupSuppressed(String groupKey) { 18623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek NotificationGroup group = mGroupMap.get(groupKey); 1872a7393410b6390831143dca198438a4e58bdf88aSelim Cinek return group != null && group.suppressed; 18825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 18925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 1909c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek public void setStatusBarState(int newState) { 1919c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek if (mBarState == newState) { 1929c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek return; 1939c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek } 1949c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek mBarState = newState; 1959c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek if (mBarState == StatusBarState.KEYGUARD) { 1969184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek collapseAllGroups(); 1979184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek } 1989184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek } 1999184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek 2009184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek public void collapseAllGroups() { 2019184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek for (NotificationGroup group : mGroupMap.values()) { 2029184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek if (group.expanded) { 2039184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek setGroupExpanded(group, false); 2049c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek } 2052a7393410b6390831143dca198438a4e58bdf88aSelim Cinek updateSuppression(group); 2069c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek } 2079c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek } 2089c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek 20925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek /** 21025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * @return whether a given notification is a child in a group which has a summary 21125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek */ 21225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public boolean isChildInGroupWithSummary(StatusBarNotification sbn) { 213ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek if (!isGroupChild(sbn)) { 21425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek return false; 21525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 216ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek NotificationGroup group = mGroupMap.get(getGroupKey(sbn)); 2172a7393410b6390831143dca198438a4e58bdf88aSelim Cinek if (group == null || group.summary == null || group.suppressed) { 21825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek return false; 21925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 22025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek return true; 22125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 22225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 223263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek /** 224263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek * @return whether a given notification is a summary in a group which has children 225263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek */ 226263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek public boolean isSummaryOfGroup(StatusBarNotification sbn) { 227ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek if (!isGroupSummary(sbn)) { 228263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek return false; 229263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek } 230ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek NotificationGroup group = mGroupMap.get(getGroupKey(sbn)); 231263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek if (group == null) { 232263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek return false; 233263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek } 234263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek return !group.children.isEmpty(); 235263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek } 236263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek 23723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek /** 23823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek * Get the summary of a specified status bar notification. For isolated notification this return 23923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek * itself. 24023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek */ 24125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) { 24223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek return getGroupSummary(getGroupKey(sbn)); 24323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek } 24423c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek 24523c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek /** 24623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek * Similar to {@link #getGroupSummary(StatusBarNotification)} but doesn't get the visual summary 24723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek * but the logical summary, i.e when a child is isolated, it still returns the summary as if 24823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek * it wasn't isolated. 24923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek */ 25023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek public ExpandableNotificationRow getLogicalGroupSummary( 25123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek StatusBarNotification sbn) { 25223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek return getGroupSummary(sbn.getGroupKey()); 25323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek } 25423c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek 25523c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek @Nullable 25623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek private ExpandableNotificationRow getGroupSummary(String groupKey) { 25723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek NotificationGroup group = mGroupMap.get(groupKey); 25825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek return group == null ? null 25925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek : group.summary == null ? null 26023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek : group.summary.row; 26125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 26225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 26383bc78338437a460076a4b5778ded38440ac3501Selim Cinek public void toggleGroupExpansion(StatusBarNotification sbn) { 264ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek NotificationGroup group = mGroupMap.get(getGroupKey(sbn)); 26583bc78338437a460076a4b5778ded38440ac3501Selim Cinek if (group == null) { 26683bc78338437a460076a4b5778ded38440ac3501Selim Cinek return; 26783bc78338437a460076a4b5778ded38440ac3501Selim Cinek } 26883bc78338437a460076a4b5778ded38440ac3501Selim Cinek setGroupExpanded(group, !group.expanded); 26983bc78338437a460076a4b5778ded38440ac3501Selim Cinek } 27083bc78338437a460076a4b5778ded38440ac3501Selim Cinek 271ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek private boolean isIsolated(StatusBarNotification sbn) { 272a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek return mIsolatedEntries.containsKey(sbn.getKey()); 273ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 274ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek 275ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek private boolean isGroupSummary(StatusBarNotification sbn) { 276ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek if (isIsolated(sbn)) { 277ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek return true; 278ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 279ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek return sbn.getNotification().isGroupSummary(); 280ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 281ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek private boolean isGroupChild(StatusBarNotification sbn) { 282ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek if (isIsolated(sbn)) { 283ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek return false; 284ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 285ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek return sbn.getNotification().isGroupChild(); 286ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 287ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek 288ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek private String getGroupKey(StatusBarNotification sbn) { 289ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek if (isIsolated(sbn)) { 290ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek return sbn.getKey(); 291ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 292ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek return sbn.getGroupKey(); 293ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 294ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek 295ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek @Override 296ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { 297ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 298ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek 299ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek @Override 300ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { 301ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 302ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek 303ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek @Override 304ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { 305ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 306ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek 307ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek @Override 308ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { 309ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek final StatusBarNotification sbn = entry.notification; 310ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek if (entry.row.isHeadsUp()) { 311a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek if (shouldIsolate(sbn)) { 31223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek // We will be isolated now, so lets update the groups 31323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek onEntryRemovedInternal(entry, entry.notification); 314a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek 315a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek mIsolatedEntries.put(sbn.getKey(), sbn); 316a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek 31723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek onEntryAdded(entry); 31823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek // We also need to update the suppression of the old group, because this call comes 31923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek // even before the groupManager knows about the notification at all. 32023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek // When the notification gets added afterwards it is already isolated and therefore 32123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek // it doesn't lead to an update. 32223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek updateSuppression(mGroupMap.get(entry.notification.getGroupKey())); 32323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek mListener.onGroupsChanged(); 324ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 325ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } else { 326a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek if (mIsolatedEntries.containsKey(sbn.getKey())) { 327a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek // not isolated anymore, we need to update the groups 328a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek onEntryRemovedInternal(entry, entry.notification); 329a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek mIsolatedEntries.remove(sbn.getKey()); 330a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek onEntryAdded(entry); 331a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek mListener.onGroupsChanged(); 332ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 333ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 334ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek } 335ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek 336a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek private boolean shouldIsolate(StatusBarNotification sbn) { 337a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey()); 338a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek return sbn.getNotification().isGroupChild() 339a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek && (sbn.getNotification().fullScreenIntent != null 340a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek || notificationGroup == null 341a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek || !notificationGroup.expanded 342a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek || isGroupNotFullyVisible(notificationGroup)); 343a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek } 344a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek 345a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek private boolean isGroupNotFullyVisible(NotificationGroup notificationGroup) { 346a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek return notificationGroup.summary == null 347a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek || notificationGroup.summary.row.getClipTopOptimization() > 0 348a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek || notificationGroup.summary.row.getClipTopAmount() > 0 349a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek || notificationGroup.summary.row.getTranslationY() < 0; 350a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek } 351a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek 35225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public static class NotificationGroup { 35325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public final HashSet<NotificationData.Entry> children = new HashSet<>(); 35425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public NotificationData.Entry summary; 35525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public boolean expanded; 3562a7393410b6390831143dca198438a4e58bdf88aSelim Cinek /** 3572a7393410b6390831143dca198438a4e58bdf88aSelim Cinek * Is this notification group suppressed, i.e its summary is hidden 3582a7393410b6390831143dca198438a4e58bdf88aSelim Cinek */ 3592a7393410b6390831143dca198438a4e58bdf88aSelim Cinek public boolean suppressed; 36025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 36125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 36225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek public interface OnGroupChangeListener { 36325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek /** 36425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * The expansion of a group has changed. 36525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * 36625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * @param changedRow the row for which the expansion has changed, which is also the summary 36725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * @param expanded a boolean indicating the new expanded state 36825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek */ 36925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded); 37025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek 37125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek /** 37225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * A group of children just received a summary notification and should therefore become 37325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * children of it. 37425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * 37525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * @param group the group created 37625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek */ 37725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek void onGroupCreatedFromChildren(NotificationGroup group); 378ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek 379ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek /** 3802a7393410b6390831143dca198438a4e58bdf88aSelim Cinek * The groups have changed. This can happen if the isolation of a child has changes or if a 3812a7393410b6390831143dca198438a4e58bdf88aSelim Cinek * group became suppressed / unsuppressed 382ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek */ 3832a7393410b6390831143dca198438a4e58bdf88aSelim Cinek void onGroupsChanged(); 38425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek } 38525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek} 386