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;
2104be389c879701b5328e6a3729f2516009a015fdSelim Cinekimport android.util.Log;
2225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
2325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinekimport com.android.systemui.statusbar.ExpandableNotificationRow;
2425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinekimport com.android.systemui.statusbar.NotificationData;
259c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinekimport com.android.systemui.statusbar.StatusBarState;
26ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinekimport com.android.systemui.statusbar.policy.HeadsUpManager;
27a7d4f82c05f1c6ea5dd92a4871711d380b01100aSelim Cinekimport com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
2825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
2952941c5618d79c0fb7af655f47f558d956af28c8Selim Cinekimport java.io.FileDescriptor;
3052941c5618d79c0fb7af655f47f558d956af28c8Selim Cinekimport java.io.PrintWriter;
31c0b14b0e895d65ab428d5c05778aae37ee946e19Selim Cinekimport java.util.ArrayList;
3225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinekimport java.util.HashMap;
33967ed2a151c9b349734892a2274304acd1bd373cSelim Cinekimport java.util.Iterator;
3452941c5618d79c0fb7af655f47f558d956af28c8Selim Cinekimport java.util.Map;
3525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
3625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek/**
3725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek * A class to handle notifications and their corresponding groups.
3825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek */
39a7d4f82c05f1c6ea5dd92a4871711d380b01100aSelim Cinekpublic class NotificationGroupManager implements OnHeadsUpChangedListener {
4025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
4104be389c879701b5328e6a3729f2516009a015fdSelim Cinek    private static final String TAG = "NotificationGroupManager";
4225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
4325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    private OnGroupChangeListener mListener;
4425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    private int mBarState = -1;
45a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek    private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
46967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek    private HeadsUpManager mHeadsUpManager;
4780c44dd800d2ef03446152495b7ef3c486f1be90Selim Cinek    private boolean mIsUpdatingUnchangedGroup;
4825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
4925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    public void setOnGroupChangeListener(OnGroupChangeListener listener) {
5025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        mListener = listener;
5125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
5225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
5325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    public boolean isGroupExpanded(StatusBarNotification sbn) {
54ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
5525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        if (group == null) {
5625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            return false;
5725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        }
5825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        return group.expanded;
5925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
6025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
6125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
62ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
6325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        if (group == null) {
6425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            return;
6525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        }
6625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        setGroupExpanded(group, expanded);
6725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
6825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
6925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    private void setGroupExpanded(NotificationGroup group, boolean expanded) {
7025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        group.expanded = expanded;
7125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        if (group.summary != null) {
7225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            mListener.onGroupExpansionChanged(group.summary.row, expanded);
7325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        }
7425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
7525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
7625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    public void onEntryRemoved(NotificationData.Entry removed) {
7725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        onEntryRemovedInternal(removed, removed.notification);
7852941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        mIsolatedEntries.remove(removed.key);
7925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
8025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
8125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    /**
8225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek     * An entry was removed.
8325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek     *
8425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek     * @param removed the removed entry
8525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek     * @param sbn the notification the entry has, which doesn't need to be the same as it's internal
8625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek     *            notification
8725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek     */
8825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    private void onEntryRemovedInternal(NotificationData.Entry removed,
8925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            final StatusBarNotification sbn) {
90ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        String groupKey = getGroupKey(sbn);
9125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        final NotificationGroup group = mGroupMap.get(groupKey);
920b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek        if (group == null) {
930b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek            // When an app posts 2 different notifications as summary of the same group, then a
940b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek            // cancellation of the first notification removes this group.
950b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek            // This situation is not supported and we will not allow such notifications anymore in
960b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek            // the close future. See b/23676310 for reference.
970b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek            return;
980b4aeab281d0bd18e67f245eeccbbc468f3065f1Selim Cinek        }
99ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        if (isGroupChild(sbn)) {
10004be389c879701b5328e6a3729f2516009a015fdSelim Cinek            group.children.remove(removed.key);
101e73ad216d322d0e7002d1ce2e59caf23030dbf5bSelim Cinek        } else {
102e73ad216d322d0e7002d1ce2e59caf23030dbf5bSelim Cinek            group.summary = null;
10325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        }
1042a7393410b6390831143dca198438a4e58bdf88aSelim Cinek        updateSuppression(group);
10525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        if (group.children.isEmpty()) {
10625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            if (group.summary == null) {
10725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek                mGroupMap.remove(groupKey);
10825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            }
10925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        }
11025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
11125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
112ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    public void onEntryAdded(final NotificationData.Entry added) {
11304be389c879701b5328e6a3729f2516009a015fdSelim Cinek        if (added.row.isRemoved()) {
11404be389c879701b5328e6a3729f2516009a015fdSelim Cinek            added.setDebugThrowable(new Throwable());
11504be389c879701b5328e6a3729f2516009a015fdSelim Cinek        }
116ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        final StatusBarNotification sbn = added.notification;
117ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        boolean isGroupChild = isGroupChild(sbn);
118ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        String groupKey = getGroupKey(sbn);
11925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        NotificationGroup group = mGroupMap.get(groupKey);
12025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        if (group == null) {
12125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            group = new NotificationGroup();
12225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            mGroupMap.put(groupKey, group);
12325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        }
124ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        if (isGroupChild) {
12504be389c879701b5328e6a3729f2516009a015fdSelim Cinek            NotificationData.Entry existing = group.children.get(added.key);
12604be389c879701b5328e6a3729f2516009a015fdSelim Cinek            if (existing != null && existing != added) {
12704be389c879701b5328e6a3729f2516009a015fdSelim Cinek                Throwable existingThrowable = existing.getDebugThrowable();
12804be389c879701b5328e6a3729f2516009a015fdSelim Cinek                Log.wtf(TAG, "Inconsistent entries found with the same key " + added.key
12904be389c879701b5328e6a3729f2516009a015fdSelim Cinek                        + "existing removed: " + existing.row.isRemoved()
13004be389c879701b5328e6a3729f2516009a015fdSelim Cinek                        + (existingThrowable != null
13104be389c879701b5328e6a3729f2516009a015fdSelim Cinek                                ? Log.getStackTraceString(existingThrowable) + "\n": "")
13204be389c879701b5328e6a3729f2516009a015fdSelim Cinek                        + " added removed" + added.row.isRemoved()
13304be389c879701b5328e6a3729f2516009a015fdSelim Cinek                        , new Throwable());
13404be389c879701b5328e6a3729f2516009a015fdSelim Cinek            }
13504be389c879701b5328e6a3729f2516009a015fdSelim Cinek            group.children.put(added.key, added);
1362a7393410b6390831143dca198438a4e58bdf88aSelim Cinek            updateSuppression(group);
137e73ad216d322d0e7002d1ce2e59caf23030dbf5bSelim Cinek        } else {
13825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            group.summary = added;
139b5605e58cb8080c8c887b1885336b707596c8094Selim Cinek            group.expanded = added.row.areChildrenExpanded();
1402a7393410b6390831143dca198438a4e58bdf88aSelim Cinek            updateSuppression(group);
14125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            if (!group.children.isEmpty()) {
14204be389c879701b5328e6a3729f2516009a015fdSelim Cinek                ArrayList<NotificationData.Entry> childrenCopy
14304be389c879701b5328e6a3729f2516009a015fdSelim Cinek                        = new ArrayList<>(group.children.values());
14450e7467eb6688a37d7877aaf2c9389a7b4a62abcSelim Cinek                for (NotificationData.Entry child : childrenCopy) {
14550e7467eb6688a37d7877aaf2c9389a7b4a62abcSelim Cinek                    onEntryBecomingChild(child);
14650e7467eb6688a37d7877aaf2c9389a7b4a62abcSelim Cinek                }
14725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek                mListener.onGroupCreatedFromChildren(group);
14825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            }
14925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        }
15025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
15125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
15250e7467eb6688a37d7877aaf2c9389a7b4a62abcSelim Cinek    private void onEntryBecomingChild(NotificationData.Entry entry) {
15350e7467eb6688a37d7877aaf2c9389a7b4a62abcSelim Cinek        if (entry.row.isHeadsUp()) {
15450e7467eb6688a37d7877aaf2c9389a7b4a62abcSelim Cinek            onHeadsUpStateChanged(entry, true);
15550e7467eb6688a37d7877aaf2c9389a7b4a62abcSelim Cinek        }
15650e7467eb6688a37d7877aaf2c9389a7b4a62abcSelim Cinek    }
15750e7467eb6688a37d7877aaf2c9389a7b4a62abcSelim Cinek
1582a7393410b6390831143dca198438a4e58bdf88aSelim Cinek    private void updateSuppression(NotificationGroup group) {
15980c44dd800d2ef03446152495b7ef3c486f1be90Selim Cinek        if (group == null) {
16023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek            return;
16123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        }
1622a7393410b6390831143dca198438a4e58bdf88aSelim Cinek        boolean prevSuppressed = group.suppressed;
16323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        group.suppressed = group.summary != null && !group.expanded
16423c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                && (group.children.size() == 1
16523c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                || (group.children.size() == 0
166e46bb37acf6d3cfb9974672ace93f5381f70ad99Julia Reynolds                        && group.summary.notification.getNotification().isGroupSummary()
16723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                        && hasIsolatedChildren(group)));
1682a7393410b6390831143dca198438a4e58bdf88aSelim Cinek        if (prevSuppressed != group.suppressed) {
169967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            if (group.suppressed) {
170967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                handleSuppressedSummaryHeadsUpped(group.summary);
171967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            }
17280c44dd800d2ef03446152495b7ef3c486f1be90Selim Cinek            if (!mIsUpdatingUnchangedGroup) {
17380c44dd800d2ef03446152495b7ef3c486f1be90Selim Cinek                mListener.onGroupsChanged();
17480c44dd800d2ef03446152495b7ef3c486f1be90Selim Cinek            }
1752a7393410b6390831143dca198438a4e58bdf88aSelim Cinek        }
1762a7393410b6390831143dca198438a4e58bdf88aSelim Cinek    }
1772a7393410b6390831143dca198438a4e58bdf88aSelim Cinek
17823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    private boolean hasIsolatedChildren(NotificationGroup group) {
17923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        return getNumberOfIsolatedChildren(group.summary.notification.getGroupKey()) != 0;
18023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    }
18123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek
18223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    private int getNumberOfIsolatedChildren(String groupKey) {
18323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        int count = 0;
184a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek        for (StatusBarNotification sbn : mIsolatedEntries.values()) {
18523c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
18623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                count++;
18723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek            }
18823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        }
18923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        return count;
19023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    }
19123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek
192967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek    private NotificationData.Entry getIsolatedChild(String groupKey) {
193967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        for (StatusBarNotification sbn : mIsolatedEntries.values()) {
194967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
195967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                return mGroupMap.get(sbn.getKey()).summary;
196967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            }
197967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        }
198967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        return null;
199967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek    }
200967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek
20125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    public void onEntryUpdated(NotificationData.Entry entry,
20225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            StatusBarNotification oldNotification) {
20368bdff16a8dc3bb049c2330025395a0b981d1d18Selim Cinek        String oldKey = oldNotification.getGroupKey();
20468bdff16a8dc3bb049c2330025395a0b981d1d18Selim Cinek        String newKey = entry.notification.getGroupKey();
20568bdff16a8dc3bb049c2330025395a0b981d1d18Selim Cinek        boolean groupKeysChanged = !oldKey.equals(newKey);
20668bdff16a8dc3bb049c2330025395a0b981d1d18Selim Cinek        boolean wasGroupChild = isGroupChild(oldNotification);
20768bdff16a8dc3bb049c2330025395a0b981d1d18Selim Cinek        boolean isGroupChild = isGroupChild(entry.notification);
20880c44dd800d2ef03446152495b7ef3c486f1be90Selim Cinek        mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild;
209ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
21025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            onEntryRemovedInternal(entry, oldNotification);
21125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        }
21225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        onEntryAdded(entry);
21380c44dd800d2ef03446152495b7ef3c486f1be90Selim Cinek        mIsUpdatingUnchangedGroup = false;
214a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek        if (isIsolated(entry.notification)) {
215a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek            mIsolatedEntries.put(entry.key, entry.notification);
21668bdff16a8dc3bb049c2330025395a0b981d1d18Selim Cinek            if (groupKeysChanged) {
21723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                updateSuppression(mGroupMap.get(oldKey));
21823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                updateSuppression(mGroupMap.get(newKey));
21923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek            }
22068bdff16a8dc3bb049c2330025395a0b981d1d18Selim Cinek        } else if (!wasGroupChild && isGroupChild) {
22150e7467eb6688a37d7877aaf2c9389a7b4a62abcSelim Cinek            onEntryBecomingChild(entry);
22223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        }
22325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
22425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
2252a7393410b6390831143dca198438a4e58bdf88aSelim Cinek    public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
22623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        return isGroupSuppressed(getGroupKey(sbn)) && sbn.getNotification().isGroupSummary();
22725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
22825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
22953c4d3d23e70496c377139ffa9397aae463d2a5eSelim Cinek    private boolean isOnlyChild(StatusBarNotification sbn) {
23036b02233fa8a5121e9fabaf5ac447c7ff4bb20a8Selim Cinek        return !sbn.getNotification().isGroupSummary()
23123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                && getTotalNumberOfChildren(sbn) == 1;
2322a7393410b6390831143dca198438a4e58bdf88aSelim Cinek    }
2332a7393410b6390831143dca198438a4e58bdf88aSelim Cinek
23453c4d3d23e70496c377139ffa9397aae463d2a5eSelim Cinek    public boolean isOnlyChildInGroup(StatusBarNotification sbn) {
23588086e718340fdb869cea40b20ae1d747074bc43Selim Cinek        if (!isOnlyChild(sbn)) {
23688086e718340fdb869cea40b20ae1d747074bc43Selim Cinek            return false;
23788086e718340fdb869cea40b20ae1d747074bc43Selim Cinek        }
23888086e718340fdb869cea40b20ae1d747074bc43Selim Cinek        ExpandableNotificationRow logicalGroupSummary = getLogicalGroupSummary(sbn);
23988086e718340fdb869cea40b20ae1d747074bc43Selim Cinek        return logicalGroupSummary != null
24088086e718340fdb869cea40b20ae1d747074bc43Selim Cinek                && !logicalGroupSummary.getStatusBarNotification().equals(sbn);
24153c4d3d23e70496c377139ffa9397aae463d2a5eSelim Cinek    }
24253c4d3d23e70496c377139ffa9397aae463d2a5eSelim Cinek
24323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    private int getTotalNumberOfChildren(StatusBarNotification sbn) {
24453c4d3d23e70496c377139ffa9397aae463d2a5eSelim Cinek        int isolatedChildren = getNumberOfIsolatedChildren(sbn.getGroupKey());
24553c4d3d23e70496c377139ffa9397aae463d2a5eSelim Cinek        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
24653c4d3d23e70496c377139ffa9397aae463d2a5eSelim Cinek        int realChildren = group != null ? group.children.size() : 0;
24753c4d3d23e70496c377139ffa9397aae463d2a5eSelim Cinek        return isolatedChildren + realChildren;
24823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    }
24923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek
25023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    private boolean isGroupSuppressed(String groupKey) {
25123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        NotificationGroup group = mGroupMap.get(groupKey);
2522a7393410b6390831143dca198438a4e58bdf88aSelim Cinek        return group != null && group.suppressed;
25325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
25425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
2559c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek    public void setStatusBarState(int newState) {
2569c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek        if (mBarState == newState) {
2579c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek            return;
2589c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek        }
2599c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek        mBarState = newState;
2609c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek        if (mBarState == StatusBarState.KEYGUARD) {
2619184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek            collapseAllGroups();
2629184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek        }
2639184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek    }
2649184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek
2659184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek    public void collapseAllGroups() {
266c0b14b0e895d65ab428d5c05778aae37ee946e19Selim Cinek        // Because notifications can become isolated when the group becomes suppressed it can
267c0b14b0e895d65ab428d5c05778aae37ee946e19Selim Cinek        // lead to concurrent modifications while looping. We need to make a copy.
268c0b14b0e895d65ab428d5c05778aae37ee946e19Selim Cinek        ArrayList<NotificationGroup> groupCopy = new ArrayList<>(mGroupMap.values());
269c0b14b0e895d65ab428d5c05778aae37ee946e19Selim Cinek        int size = groupCopy.size();
270c0b14b0e895d65ab428d5c05778aae37ee946e19Selim Cinek        for (int i = 0; i < size; i++) {
271c0b14b0e895d65ab428d5c05778aae37ee946e19Selim Cinek            NotificationGroup group =  groupCopy.get(i);
2729184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek            if (group.expanded) {
2739184f9c4cdff27c6eea47c885d95bad2859b5eb3Selim Cinek                setGroupExpanded(group, false);
2749c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek            }
2752a7393410b6390831143dca198438a4e58bdf88aSelim Cinek            updateSuppression(group);
2769c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek        }
2779c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek    }
2789c4c41461d98edf622f79c0bd4b2a8af8cfa2de8Selim Cinek
27925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    /**
28025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek     * @return whether a given notification is a child in a group which has a summary
28125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek     */
28225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
283ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        if (!isGroupChild(sbn)) {
28425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            return false;
28525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        }
286ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
2872a7393410b6390831143dca198438a4e58bdf88aSelim Cinek        if (group == null || group.summary == null || group.suppressed) {
28825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek            return false;
28925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        }
2903f19f60d654421eee5b35a92129081f08c977629Selim Cinek        if (group.children.isEmpty()) {
2913f19f60d654421eee5b35a92129081f08c977629Selim Cinek            // If the suppression of a group changes because the last child was removed, this can
2923f19f60d654421eee5b35a92129081f08c977629Selim Cinek            // still be called temporarily because the child hasn't been fully removed yet. Let's
2933f19f60d654421eee5b35a92129081f08c977629Selim Cinek            // make sure we still return false in that case.
2943f19f60d654421eee5b35a92129081f08c977629Selim Cinek            return false;
2953f19f60d654421eee5b35a92129081f08c977629Selim Cinek        }
29625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        return true;
29725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
29825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
299263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek    /**
300263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek     * @return whether a given notification is a summary in a group which has children
301263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek     */
302263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek    public boolean isSummaryOfGroup(StatusBarNotification sbn) {
303ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        if (!isGroupSummary(sbn)) {
304263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek            return false;
305263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek        }
306ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
307263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek        if (group == null) {
308263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek            return false;
309263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek        }
310263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek        return !group.children.isEmpty();
311263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek    }
312263398f0175efc8bc8c965473f9565a693a0a0e0Selim Cinek
31323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    /**
31423c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek     * Get the summary of a specified status bar notification. For isolated notification this return
31523c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek     * itself.
31623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek     */
31725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
31823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        return getGroupSummary(getGroupKey(sbn));
31923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    }
32023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek
32123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    /**
32223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek     * Similar to {@link #getGroupSummary(StatusBarNotification)} but doesn't get the visual summary
32323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek     * but the logical summary, i.e when a child is isolated, it still returns the summary as if
32423c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek     * it wasn't isolated.
32523c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek     */
32623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    public ExpandableNotificationRow getLogicalGroupSummary(
32723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek            StatusBarNotification sbn) {
32823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        return getGroupSummary(sbn.getGroupKey());
32923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    }
33023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek
33123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    @Nullable
33223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek    private ExpandableNotificationRow getGroupSummary(String groupKey) {
33323c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek        NotificationGroup group = mGroupMap.get(groupKey);
33425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        return group == null ? null
33525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek                : group.summary == null ? null
33623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                        : group.summary.row;
33725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
33825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
339698b1706be196de41fc6bf03cf15971c82a11949Chris Wren    /** @return group expansion state after toggling. */
340698b1706be196de41fc6bf03cf15971c82a11949Chris Wren    public boolean toggleGroupExpansion(StatusBarNotification sbn) {
341ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        NotificationGroup group = mGroupMap.get(getGroupKey(sbn));
34283bc78338437a460076a4b5778ded38440ac3501Selim Cinek        if (group == null) {
343698b1706be196de41fc6bf03cf15971c82a11949Chris Wren            return false;
34483bc78338437a460076a4b5778ded38440ac3501Selim Cinek        }
34583bc78338437a460076a4b5778ded38440ac3501Selim Cinek        setGroupExpanded(group, !group.expanded);
346698b1706be196de41fc6bf03cf15971c82a11949Chris Wren        return group.expanded;
34783bc78338437a460076a4b5778ded38440ac3501Selim Cinek    }
34883bc78338437a460076a4b5778ded38440ac3501Selim Cinek
349ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    private boolean isIsolated(StatusBarNotification sbn) {
350a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek        return mIsolatedEntries.containsKey(sbn.getKey());
351ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    }
352ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek
353ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    private boolean isGroupSummary(StatusBarNotification sbn) {
354ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        if (isIsolated(sbn)) {
355ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek            return true;
356ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        }
357ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        return sbn.getNotification().isGroupSummary();
358ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    }
359e46bb37acf6d3cfb9974672ace93f5381f70ad99Julia Reynolds
360ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    private boolean isGroupChild(StatusBarNotification sbn) {
361ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        if (isIsolated(sbn)) {
362ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek            return false;
363ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        }
364e46bb37acf6d3cfb9974672ace93f5381f70ad99Julia Reynolds        return sbn.isGroup() && !sbn.getNotification().isGroupSummary();
365ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    }
366ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek
367ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    private String getGroupKey(StatusBarNotification sbn) {
368ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        if (isIsolated(sbn)) {
369ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek            return sbn.getKey();
370ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        }
371ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        return sbn.getGroupKey();
372ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    }
373ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek
374ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    @Override
375ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) {
376ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    }
377ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek
378ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    @Override
379ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
380ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    }
381ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek
382ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    @Override
383ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
384ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    }
385ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek
386ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    @Override
387ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) {
388ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        final StatusBarNotification sbn = entry.notification;
389ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        if (entry.row.isHeadsUp()) {
390a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek            if (shouldIsolate(sbn)) {
39123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                // We will be isolated now, so lets update the groups
39223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                onEntryRemovedInternal(entry, entry.notification);
393a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek
394a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                mIsolatedEntries.put(sbn.getKey(), sbn);
395a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek
39623c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                onEntryAdded(entry);
39723c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                // We also need to update the suppression of the old group, because this call comes
39823c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                // even before the groupManager knows about the notification at all.
39923c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                // When the notification gets added afterwards it is already isolated and therefore
40023c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                // it doesn't lead to an update.
40123c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                updateSuppression(mGroupMap.get(entry.notification.getGroupKey()));
40223c80348d29e4a28968cc8800181d088c6ca0436Selim Cinek                mListener.onGroupsChanged();
403967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            } else {
404967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                handleSuppressedSummaryHeadsUpped(entry);
405ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek            }
406ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        } else {
407a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek            if (mIsolatedEntries.containsKey(sbn.getKey())) {
408a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                // not isolated anymore, we need to update the groups
409a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                onEntryRemovedInternal(entry, entry.notification);
410a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                mIsolatedEntries.remove(sbn.getKey());
411a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                onEntryAdded(entry);
412a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                mListener.onGroupsChanged();
413ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek            }
414ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        }
415ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek    }
416ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek
417967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek    private void handleSuppressedSummaryHeadsUpped(NotificationData.Entry entry) {
418967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        StatusBarNotification sbn = entry.notification;
419967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        if (!isGroupSuppressed(sbn.getGroupKey())
420967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                || !sbn.getNotification().isGroupSummary()
421967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                || !entry.row.isHeadsUp()) {
422967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            return;
423967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        }
424967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        // The parent of a suppressed group got huned, lets hun the child!
425967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
426967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        if (notificationGroup != null) {
42704be389c879701b5328e6a3729f2516009a015fdSelim Cinek            Iterator<NotificationData.Entry> iterator
42804be389c879701b5328e6a3729f2516009a015fdSelim Cinek                    = notificationGroup.children.values().iterator();
429967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            NotificationData.Entry child = iterator.hasNext() ? iterator.next() : null;
430967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            if (child == null) {
431967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                child = getIsolatedChild(sbn.getGroupKey());
432967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            }
433967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            if (child != null) {
43440f88765156c82d6f3ceabb72f7ae72ceda0e36dSelim Cinek                if (child.row.keepInParent() || child.row.isRemoved() || child.row.isDismissed()) {
43540f88765156c82d6f3ceabb72f7ae72ceda0e36dSelim Cinek                    // the notification is actually already removed, no need to do heads-up on it.
43640f88765156c82d6f3ceabb72f7ae72ceda0e36dSelim Cinek                    return;
43740f88765156c82d6f3ceabb72f7ae72ceda0e36dSelim Cinek                }
438967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                if (mHeadsUpManager.isHeadsUp(child.key)) {
439967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                    mHeadsUpManager.updateNotification(child, true);
440967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                } else {
441967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                    mHeadsUpManager.showNotification(child);
442967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek                }
443967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek            }
444967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        }
445967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        mHeadsUpManager.releaseImmediately(entry.key);
446967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek    }
447967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek
448a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek    private boolean shouldIsolate(StatusBarNotification sbn) {
449a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek        NotificationGroup notificationGroup = mGroupMap.get(sbn.getGroupKey());
450e46bb37acf6d3cfb9974672ace93f5381f70ad99Julia Reynolds        return (sbn.isGroup() && !sbn.getNotification().isGroupSummary())
451a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                && (sbn.getNotification().fullScreenIntent != null
452a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                        || notificationGroup == null
453a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                        || !notificationGroup.expanded
454a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                        || isGroupNotFullyVisible(notificationGroup));
455a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek    }
456a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek
457a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek    private boolean isGroupNotFullyVisible(NotificationGroup notificationGroup) {
458a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek        return notificationGroup.summary == null
459a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                || notificationGroup.summary.row.getClipTopAmount() > 0
460a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek                || notificationGroup.summary.row.getTranslationY() < 0;
461a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek    }
462a6c0cef0a0cf9895d9241cb3293a7355c3e8af4aSelim Cinek
463967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
464967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek        mHeadsUpManager = headsUpManager;
465967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek    }
466967ed2a151c9b349734892a2274304acd1bd373cSelim Cinek
46752941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
46852941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        pw.println("GroupManager state:");
46952941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        pw.println("  number of groups: " +  mGroupMap.size());
47052941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        for (Map.Entry<String, NotificationGroup>  entry : mGroupMap.entrySet()) {
47152941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek            pw.println("\n    key: " + entry.getKey()); pw.println(entry.getValue());
47252941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        }
47352941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        pw.println("\n    isolated entries: " +  mIsolatedEntries.size());
47452941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        for (Map.Entry<String, StatusBarNotification> entry : mIsolatedEntries.entrySet()) {
47552941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek            pw.print("      "); pw.print(entry.getKey());
47652941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek            pw.print(", "); pw.println(entry.getValue());
47752941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        }
47852941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek    }
47952941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek
48025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    public static class NotificationGroup {
48104be389c879701b5328e6a3729f2516009a015fdSelim Cinek        public final HashMap<String, NotificationData.Entry> children = new HashMap<>();
48225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        public NotificationData.Entry summary;
48325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        public boolean expanded;
4842a7393410b6390831143dca198438a4e58bdf88aSelim Cinek        /**
4852a7393410b6390831143dca198438a4e58bdf88aSelim Cinek         * Is this notification group suppressed, i.e its summary is hidden
4862a7393410b6390831143dca198438a4e58bdf88aSelim Cinek         */
4872a7393410b6390831143dca198438a4e58bdf88aSelim Cinek        public boolean suppressed;
48852941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek
48952941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        @Override
49052941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        public String toString() {
49160ca7879ec1fa7164c6fed95413d40313423fabbSelim Cinek            String result = "    summary:\n      "
49204be389c879701b5328e6a3729f2516009a015fdSelim Cinek                    + (summary != null ? summary.notification : "null")
49304be389c879701b5328e6a3729f2516009a015fdSelim Cinek                    + (summary != null && summary.getDebugThrowable() != null
49404be389c879701b5328e6a3729f2516009a015fdSelim Cinek                            ? Log.getStackTraceString(summary.getDebugThrowable())
49504be389c879701b5328e6a3729f2516009a015fdSelim Cinek                            : "");
49652941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek            result += "\n    children size: " + children.size();
49704be389c879701b5328e6a3729f2516009a015fdSelim Cinek            for (NotificationData.Entry child : children.values()) {
49804be389c879701b5328e6a3729f2516009a015fdSelim Cinek                result += "\n      " + child.notification
49904be389c879701b5328e6a3729f2516009a015fdSelim Cinek                + (child.getDebugThrowable() != null
50004be389c879701b5328e6a3729f2516009a015fdSelim Cinek                        ? Log.getStackTraceString(child.getDebugThrowable())
50104be389c879701b5328e6a3729f2516009a015fdSelim Cinek                        : "");
50252941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek            }
50352941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek            return result;
50452941c5618d79c0fb7af655f47f558d956af28c8Selim Cinek        }
50525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
50625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
50725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    public interface OnGroupChangeListener {
50825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        /**
50925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek         * The expansion of a group has changed.
51025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek         *
51125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek         * @param changedRow the row for which the expansion has changed, which is also the summary
51225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek         * @param expanded a boolean indicating the new expanded state
51325fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek         */
51425fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded);
51525fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek
51625fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        /**
51725fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek         * A group of children just received a summary notification and should therefore become
51825fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek         * children of it.
51925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek         *
52025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek         * @param group the group created
52125fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek         */
52225fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek        void onGroupCreatedFromChildren(NotificationGroup group);
523ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek
524ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek        /**
5252a7393410b6390831143dca198438a4e58bdf88aSelim Cinek         * The groups have changed. This can happen if the isolation of a child has changes or if a
5262a7393410b6390831143dca198438a4e58bdf88aSelim Cinek         * group became suppressed / unsuppressed
527ef5127ea5f34f7a4c961021f6b691174bcb81d2eSelim Cinek         */
5282a7393410b6390831143dca198438a4e58bdf88aSelim Cinek        void onGroupsChanged();
52925fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek    }
53025fd4e2be731fe893685faa48828d8fa4526cb1aSelim Cinek}
531