Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 25fd4e2b authored by Selim Cinek's avatar Selim Cinek
Browse files

Introduced a group manager to manage group notifications

Bug: 15869874
Change-Id: I1bbcd9e5a2b8dae62bd8d93908dacc5d8fc08887
parent abf60bb2
Loading
Loading
Loading
Loading
+12 −2
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ import com.android.systemui.SystemUI;
import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
import com.android.systemui.statusbar.policy.PreviewInflater;
@@ -156,6 +157,8 @@ public abstract class BaseStatusBar extends SystemUI implements
    protected NotificationData mNotificationData;
    protected NotificationStackScrollLayout mStackScroller;

    protected NotificationGroupManager mGroupManager = new NotificationGroupManager();

    // for heads up notifications
    protected HeadsUpNotificationView mHeadsUpNotificationView;
    protected int mHeadsUpNotificationDecay;
@@ -438,8 +441,7 @@ public abstract class BaseStatusBar extends SystemUI implements
                        // Ignore children of notifications that have a summary, since we're not
                        // going to show them anyway. This is true also when the summary is canceled,
                        // because children are automatically canceled by NoMan in that case.
                        if (n.isGroupChild() &&
                                mNotificationData.isGroupWithSummary(sbn.getGroupKey())) {
                        if (mGroupManager.isChildInGroupWithSummary(sbn)) {
                            if (DEBUG) {
                                Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
                            }
@@ -707,6 +709,11 @@ public abstract class BaseStatusBar extends SystemUI implements
        return null;
    }

    @Override
    public NotificationGroupManager getGroupManager() {
        return mGroupManager;
    }

    /**
     * Takes the necessary steps to prepare the status bar for starting an activity, then starts it.
     * @param action A dismiss action that is called if it's safe to start the activity.
@@ -2016,6 +2023,7 @@ public abstract class BaseStatusBar extends SystemUI implements
                && publicUnchanged) {
            if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
            oldEntry.notification = notification;
            mGroupManager.onEntryUpdated(oldEntry, oldNotification);
            try {
                if (oldEntry.icon != null) {
                    // Update the icon
@@ -2074,6 +2082,7 @@ public abstract class BaseStatusBar extends SystemUI implements
                if (!shouldInterrupt) {
                    if (DEBUG) Log.d(TAG, "releasing heads up for key: " + key);
                    oldEntry.notification = notification;
                    mGroupManager.onEntryUpdated(oldEntry, oldNotification);
                    mHeadsUpNotificationView.release();
                    return;
                }
@@ -2087,6 +2096,7 @@ public abstract class BaseStatusBar extends SystemUI implements
                } else {
                    if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key);
                    oldEntry.notification = notification;
                    mGroupManager.onEntryUpdated(oldEntry, oldNotification);
                    final StatusBarIcon ic = new StatusBarIcon(notification.getPackageName(),
                            notification.getUser(),
                            n.icon,
+13 −23
Original line number Diff line number Diff line
@@ -22,9 +22,10 @@ import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.View;

import com.android.systemui.statusbar.phone.NotificationGroupManager;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -91,10 +92,12 @@ public class NotificationData {

    private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
    private final ArrayList<Entry> mSortedAndFiltered = new ArrayList<>();
    private ArraySet<String> mGroupsWithSummaries = new ArraySet<>();

    private NotificationGroupManager mGroupManager;

    private RankingMap mRankingMap;
    private final Ranking mTmpRanking = new Ranking();

    private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() {
        private final Ranking mRankingA = new Ranking();
        private final Ranking mRankingB = new Ranking();
@@ -141,6 +144,7 @@ public class NotificationData {

    public NotificationData(Environment environment) {
        mEnvironment = environment;
        mGroupManager = environment.getGroupManager();
    }

    /**
@@ -163,12 +167,14 @@ public class NotificationData {
    public void add(Entry entry, RankingMap ranking) {
        mEntries.put(entry.notification.getKey(), entry);
        updateRankingAndSort(ranking);
        mGroupManager.onEntryAdded(entry);
    }

    public Entry remove(String key, RankingMap ranking) {
        Entry removed = mEntries.remove(key);
        if (removed == null) return null;
        updateRankingAndSort(ranking);
        mGroupManager.onEntryRemoved(removed);
        return removed;
    }

@@ -203,7 +209,6 @@ public class NotificationData {
    // anything changed, and this class should call back the UI so it updates itself.
    public void filterAndSort() {
        mSortedAndFiltered.clear();
        mGroupsWithSummaries.clear();

        final int N = mEntries.size();
        for (int i = 0; i < N; i++) {
@@ -214,32 +219,12 @@ public class NotificationData {
                continue;
            }

            if (sbn.getNotification().isGroupSummary()) {
                mGroupsWithSummaries.add(sbn.getGroupKey());
            }
            mSortedAndFiltered.add(entry);
        }

        // Second pass: Filter out group children with summary.
        if (!mGroupsWithSummaries.isEmpty()) {
            final int M = mSortedAndFiltered.size();
            for (int i = M - 1; i >= 0; i--) {
                Entry ent = mSortedAndFiltered.get(i);
                StatusBarNotification sbn = ent.notification;
                if (sbn.getNotification().isGroupChild() &&
                        mGroupsWithSummaries.contains(sbn.getGroupKey())) {
                    mSortedAndFiltered.remove(i);
                }
            }
        }

        Collections.sort(mSortedAndFiltered, mRankingComparator);
    }

    public boolean isGroupWithSummary(String groupKey) {
        return mGroupsWithSummaries.contains(groupKey);
    }

    boolean shouldFilterOut(StatusBarNotification sbn) {
        if (!(mEnvironment.isDeviceProvisioned() ||
                showNotificationEvenIfUnprovisioned(sbn))) {
@@ -254,6 +239,10 @@ public class NotificationData {
                mEnvironment.shouldHideSensitiveContents(sbn.getUserId())) {
            return true;
        }

        if (mGroupManager.isChildInGroupWithSummary(sbn)) {
            return true;
        }
        return false;
    }

@@ -328,5 +317,6 @@ public class NotificationData {
        public boolean isDeviceProvisioned();
        public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
        public String getCurrentMediaNotificationKey();
        public NotificationGroupManager getGroupManager();
    }
}
+232 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.systemui.statusbar.phone;

import android.app.Notification;
import android.service.notification.StatusBarNotification;

import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * A class to handle notifications and their corresponding groups.
 */
public class NotificationGroupManager {

    private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
    private OnGroupChangeListener mListener;
    private int mBarState = -1;

    public void setOnGroupChangeListener(OnGroupChangeListener listener) {
        mListener = listener;
    }

    public boolean isGroupExpanded(StatusBarNotification sbn) {
        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
        if (group == null) {
            return false;
        }
        return group.expanded;
    }

    public void setGroupExpanded(StatusBarNotification sbn, boolean expanded) {
        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
        if (group == null) {
            return;
        }
        setGroupExpanded(group, expanded);
    }

    private void setGroupExpanded(NotificationGroup group, boolean expanded) {
        group.expanded = expanded;
        if (group.summary != null) {
            mListener.onGroupExpansionChanged(group.summary.row, expanded);
        }
    }

    public void onEntryRemoved(NotificationData.Entry removed) {
        onEntryRemovedInternal(removed, removed.notification);
    }

    /**
     * An entry was removed.
     *
     * @param removed the removed entry
     * @param sbn the notification the entry has, which doesn't need to be the same as it's internal
     *            notification
     */
    private void onEntryRemovedInternal(NotificationData.Entry removed,
            final StatusBarNotification sbn) {
        Notification notif = sbn.getNotification();
        String groupKey = sbn.getGroupKey();
        final NotificationGroup group = mGroupMap.get(groupKey);
        if (notif.isGroupSummary()) {
            group.summary = null;
        } else {
            group.children.remove(removed);
        }
        if (group.children.isEmpty()) {
            if (group.summary == null) {
                mGroupMap.remove(groupKey);
            } else {
                if (group.expanded) {
                    // only the summary is left. Change it to unexpanded in a few ms. We do this to
                    // avoid raceconditions
                    removed.row.post(new Runnable() {
                        @Override
                        public void run() {
                            if (group.children.isEmpty()) {
                                setGroupExpanded(sbn, false);
                            }
                        }
                    });
                }
            }
        }
    }

    public void onEntryAdded(NotificationData.Entry added) {
        StatusBarNotification sbn = added.notification;
        Notification notif = sbn.getNotification();
        String groupKey = sbn.getGroupKey();
        NotificationGroup group = mGroupMap.get(groupKey);
        if (group == null) {
            group = new NotificationGroup();
            mGroupMap.put(groupKey, group);
        }
        if (notif.isGroupSummary()) {
            group.summary = added;
            if (!group.children.isEmpty()) {
                mListener.onGroupCreatedFromChildren(group);
            }
        } else {
            group.children.add(added);
        }
    }

    public void onEntryUpdated(NotificationData.Entry entry,
            StatusBarNotification oldNotification) {
        if (mGroupMap.get(oldNotification.getGroupKey()) != null) {
            onEntryRemovedInternal(entry, oldNotification);
        }
        onEntryAdded(entry);
    }

    public boolean isVisible(StatusBarNotification sbn) {
        if (!sbn.getNotification().isGroupChild()) {
            return true;
        }
        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
        if (group != null && group.expanded) {
            return true;
        }
        return false;
    }

    public boolean hasGroupChildren(StatusBarNotification sbn) {
        if (areGroupsProhibited()) {
            return false;
        }
        if (!sbn.getNotification().isGroupSummary()) {
            return false;
        }
        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
        if (group == null) {
            return false;
        }
        return !group.children.isEmpty();
    }

    public void setStatusBarState(int newState) {
        if (mBarState == newState) {
            return;
        }
        boolean prohibitedBefore = areGroupsProhibited();
        mBarState = newState;
        boolean nowProhibited = areGroupsProhibited();
        if (nowProhibited != prohibitedBefore) {
            if (nowProhibited) {
                for (NotificationGroup group : mGroupMap.values()) {
                    if (group.expanded) {
                        setGroupExpanded(group, false);
                    }
                }
            }
            mListener.onGroupsProhibitedChanged();
        }
    }

    private boolean areGroupsProhibited() {
        return mBarState == StatusBarState.KEYGUARD;
    }

    /**
     * @return whether a given notification is a child in a group which has a summary
     */
    public boolean isChildInGroupWithSummary(StatusBarNotification sbn) {
        if (!sbn.getNotification().isGroupChild()) {
            return false;
        }
        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
        if (group == null || group.summary == null) {
            return false;
        }
        return true;
    }

    public ExpandableNotificationRow getGroupSummary(StatusBarNotification sbn) {
        NotificationGroup group = mGroupMap.get(sbn.getGroupKey());
        return group == null ? null
                : group.summary == null ? null
                : group.summary.row;
    }

    public static class NotificationGroup {
        public final HashSet<NotificationData.Entry> children = new HashSet<>();
        public NotificationData.Entry summary;
        public boolean expanded;
    }

    public interface OnGroupChangeListener {
        /**
         * The expansion of a group has changed.
         *
         * @param changedRow the row for which the expansion has changed, which is also the summary
         * @param expanded a boolean indicating the new expanded state
         */
        void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded);

        /**
         * Children group policy has changed and children may no be prohibited or allowed.
         */
        void onGroupsProhibitedChanged();

        /**
         * A group of children just received a summary notification and should therefore become
         * children of it.
         *
         * @param group the group created
         */
        void onGroupCreatedFromChildren(NotificationGroup group);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -3379,6 +3379,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
            }
        }
        mState = state;
        mGroupManager.setStatusBarState(state);
        mStatusBarWindowManager.setStatusBarState(state);
    }