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

Commit 4a24fd3c authored by Chris Wren's avatar Chris Wren
Browse files

Honor the sort and group keys for notification ranking.

Sort notifications naturally, then move group childen to be next their proxy.
The group proxy is the summary, or if no summary exists, the lowest-ranked
member of the group is chosen as the proxy.

Notifications with a sortKey but no group and placed into a synthetic
group that consists of all notifications from that package and user in
the same priority bucket that also have sortKeys.

Bug: 15190903
Change-Id: I377ed65b9592079446da4a4189f5ab49d28630ec
parent 0c434aed
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ public class StatusBarNotification implements Parcelable {
    private final int id;
    private final String tag;
    private final String key;
    private final String groupKey;

    private final int uid;
    private final String opPkg;
@@ -65,6 +66,7 @@ public class StatusBarNotification implements Parcelable {
        this.notification.setUser(user);
        this.postTime = postTime;
        this.key = key();
        this.groupKey = groupKey();
    }

    public StatusBarNotification(Parcel in) {
@@ -84,12 +86,26 @@ public class StatusBarNotification implements Parcelable {
        this.notification.setUser(this.user);
        this.postTime = in.readLong();
        this.key = key();
        this.groupKey = groupKey();
    }

    private String key() {
        return user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid;
    }

    private String groupKey() {
        final String group = getNotification().getGroup();
        final String sortKey = getNotification().getSortKey();
        if (group == null && sortKey == null) {
            // a group of one
            return key;
        }
        return user.getIdentifier() + "|" + pkg + "|" +
                (group == null
                        ? "p:" + notification.priority
                        : "g:" + group);
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeString(this.pkg);
        out.writeString(this.opPkg);
@@ -240,4 +256,11 @@ public class StatusBarNotification implements Parcelable {
    public String getKey() {
        return key;
    }

    /**
     * A key that indicates the group with which this message ranks.
     */
    public String getGroupKey() {
        return groupKey;
    }
}
+57 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 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.server.notification;

import android.text.TextUtils;
import android.util.Log;

/**
 * Sorts notifications, accounting for groups and sort keys.
 */
public class GroupedNotificationComparator extends NotificationComparator {
    private static final String TAG = "GroupedNotificationComparator";

    @Override
    public int compare(NotificationRecord left, NotificationRecord right) {
        // "recently intrusive" is an ad hoc group that temporarily claims noisy notifications
        if (left.isRecentlyIntrusive() != right.isRecentlyIntrusive()) {
            return left.isRecentlyIntrusive() ? -1 : 1;
        }

        final NotificationRecord leftProxy = left.getRankingProxy();
        if (leftProxy == null) {
            throw new RuntimeException("left proxy cannot be null: " + left.getKey());
        }
        final NotificationRecord rightProxy = right.getRankingProxy();
        if (rightProxy == null) {
            throw new RuntimeException("right proxy cannot be null: " + right.getKey());
        }
        final String leftSortKey = left.getNotification().getSortKey();
        final String rightSortKey = right.getNotification().getSortKey();
        if (leftProxy != rightProxy) {
            // between groups, compare proxies
            return Integer.compare(leftProxy.getAuthoritativeRank(),
                    rightProxy.getAuthoritativeRank());
        } else if (TextUtils.isEmpty(leftSortKey) || TextUtils.isEmpty(rightSortKey)) {
            // missing sort keys, use prior rank
            return Integer.compare(left.getAuthoritativeRank(),
                    right.getAuthoritativeRank());
        } else {
            // use sort keys within group
            return leftSortKey.compareTo(rightSortKey);
        }
    }
}
+14 −14
Original line number Diff line number Diff line
@@ -18,35 +18,35 @@ package com.android.server.notification;
import java.util.Comparator;

/**
 * Sorts notificaitons into attention-relelvant order.
 * Sorts notifications individually into attention-relelvant order.
 */
public class NotificationComparator
        implements Comparator<NotificationRecord> {

    @Override
    public int compare(NotificationRecord lhs, NotificationRecord rhs) {
        if (lhs.isRecentlyIntrusive() != rhs.isRecentlyIntrusive()) {
            return lhs.isRecentlyIntrusive() ? -1 : 1;
        }
        final int leftPackagePriority = lhs.getPackagePriority();
        final int rightPackagePriority = rhs.getPackagePriority();
    public int compare(NotificationRecord left, NotificationRecord right) {
        final int leftPackagePriority = left.getPackagePriority();
        final int rightPackagePriority = right.getPackagePriority();
        if (leftPackagePriority != rightPackagePriority) {
            // by priority, high to low
            return -1 * Integer.compare(leftPackagePriority, rightPackagePriority);
        }
        final int leftScore = lhs.sbn.getScore();
        final int rightScore = rhs.sbn.getScore();

        final int leftScore = left.sbn.getScore();
        final int rightScore = right.sbn.getScore();
        if (leftScore != rightScore) {
            // by priority, high to low
            return -1 * Integer.compare(leftScore, rightScore);
        }
        final float leftPeple = lhs.getContactAffinity();
        final float rightPeople = rhs.getContactAffinity();
        if (leftPeple != rightPeople) {

        final float leftPeople = left.getContactAffinity();
        final float rightPeople = right.getContactAffinity();
        if (leftPeople != rightPeople) {
            // by contact proximity, close to far
            return -1 * Float.compare(leftPeple, rightPeople);
            return -1 * Float.compare(leftPeople, rightPeople);
        }

        // then break ties by time, most recent first
        return -1 * Long.compare(lhs.getRankingTimeMs(), rhs.getRankingTimeMs());
        return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
    }
}
+31 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.service.notification.StatusBarNotification;
import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.lang.reflect.Array;
@@ -60,7 +61,12 @@ public final class NotificationRecord {
    public boolean isUpdate;
    private int mPackagePriority;

    NotificationRecord(StatusBarNotification sbn, int score)
    // The record that ranking should use for comparisons outside the group.
    private NotificationRecord mRankingProxy;
    private int mAuthoritativeRank;

    @VisibleForTesting
    public NotificationRecord(StatusBarNotification sbn, int score)
    {
        this.sbn = sbn;
        this.score = score;
@@ -73,7 +79,9 @@ public final class NotificationRecord {
        mRecentlyIntrusive = previous.mRecentlyIntrusive;
        mPackagePriority = previous.mPackagePriority;
        mIntercept = previous.mIntercept;
        mRankingProxy = previous.mRankingProxy;
        mRankingTimeMs = calculateRankingTimeMs(previous.getRankingTimeMs());
        // Don't copy mGroupKey, recompute it, in case it has changed
    }

    public Notification getNotification() { return sbn.getNotification(); }
@@ -89,6 +97,7 @@ public final class NotificationRecord {
                + " / " + idDebugString(baseContext, sbn.getPackageName(), notification.icon));
        pw.println(prefix + "  pri=" + notification.priority + " score=" + sbn.getScore());
        pw.println(prefix + "  key=" + sbn.getKey());
        pw.println(prefix + "  groupKey=" + getGroupKey());
        pw.println(prefix + "  contentIntent=" + notification.contentIntent);
        pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
        pw.println(prefix + "  tickerText=" + notification.tickerText);
@@ -145,6 +154,7 @@ public final class NotificationRecord {
        pw.println(prefix + "  mRecentlyIntrusive=" + mRecentlyIntrusive);
        pw.println(prefix + "  mPackagePriority=" + mPackagePriority);
        pw.println(prefix + "  mIntercept=" + mIntercept);
        pw.println(prefix + "  mRankingProxy=" + getRankingProxy().getKey());
        pw.println(prefix + "  mRankingTimeMs=" + mRankingTimeMs);
    }

@@ -241,4 +251,24 @@ public final class NotificationRecord {
        }
        return sbn.getPostTime();
    }

    public NotificationRecord getRankingProxy() {
        return mRankingProxy;
    }

    public void setRankingProxy(NotificationRecord proxy) {
        mRankingProxy = proxy;
    }

    public void setAuthoritativeRank(int authoritativeRank) {
        mAuthoritativeRank = authoritativeRank;
    }

    public int getAuthoritativeRank() {
        return mAuthoritativeRank;
    }

    public String getGroupKey() {
        return sbn.getGroupKey();
    }
}
+35 −6
Original line number Diff line number Diff line
@@ -17,12 +17,12 @@ package com.android.server.notification;

import android.app.Notification;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.SparseIntArray;
import org.xmlpull.v1.XmlPullParser;
@@ -49,13 +49,13 @@ public class RankingHelper implements RankingConfig {
    private static final String ATT_UID = "uid";
    private static final String ATT_PRIORITY = "priority";

    private static final String VALUE_HIGH = "high";

    private final NotificationSignalExtractor[] mSignalExtractors;
    private final NotificationComparator mRankingComparator = new NotificationComparator();
    private final NotificationComparator mPreliminaryComparator = new NotificationComparator();
    private final NotificationComparator mFinalComparator = new GroupedNotificationComparator();

    // Package name to uid, to priority. Would be better as Table<String, Int, Int>
    private final ArrayMap<String, SparseIntArray> mPackagePriorities;
    private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp;

    private final Context mContext;
    private final Handler mRankingHandler;
@@ -83,6 +83,7 @@ public class RankingHelper implements RankingConfig {
                Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
            }
        }
        mProxyByGroupTmp = new ArrayMap<String, NotificationRecord>();
    }

    public void extractSignals(NotificationRecord r) {
@@ -166,11 +167,39 @@ public class RankingHelper implements RankingConfig {
    }

    public void sort(ArrayList<NotificationRecord> notificationList) {
        Collections.sort(notificationList, mRankingComparator);
        final int N = notificationList.size();
        // clear group proxies
        for (int i = N - 1; i >= 0; i--) {
            notificationList.get(i).setRankingProxy(null);
        }

        // rank each record individually
        Collections.sort(notificationList, mPreliminaryComparator);

        // record inidivdual ranking result and nominate proxies for each group
        for (int i = N - 1; i >= 0; i--) {
            final NotificationRecord record = notificationList.get(i);
            record.setAuthoritativeRank(i);
            final String groupKey = record.getGroupKey();
            boolean isGroupSummary = record.getNotification().getGroup() != null
                    && (record.getNotification().flags & Notification.FLAG_GROUP_SUMMARY) != 0;
            if (isGroupSummary || mProxyByGroupTmp.get(groupKey) == null) {
                mProxyByGroupTmp.put(groupKey, record);
            }
        }
        // assign nominated proxies to each notification
        for (int i = 0; i < N; i++) {
            final NotificationRecord record = notificationList.get(i);
            record.setRankingProxy(mProxyByGroupTmp.get(record.getGroupKey()));
        }
        // Do a second ranking pass, using group proxies
        Collections.sort(notificationList, mFinalComparator);

        mProxyByGroupTmp.clear();
    }

    public int indexOf(ArrayList<NotificationRecord> notificationList, NotificationRecord target) {
        return Collections.binarySearch(notificationList, target, mRankingComparator);
        return Collections.binarySearch(notificationList, target, mFinalComparator);
    }

    private static int safeInt(XmlPullParser parser, String att, int defValue) {
Loading