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

Commit e2b77a55 authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Move autogrouping into framework."

parents b9df32d1 8f488d3f
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -37556,8 +37556,6 @@ package android.service.notification {
    method public int getUser();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
    field public static final java.lang.String GROUP_KEY_OVERRIDE_KEY = "group_key_override";
    field public static final java.lang.String NEEDS_AUTOGROUPING_KEY = "autogroup_needed";
  }
  public final class Condition implements android.os.Parcelable {
+4 −4
Original line number Diff line number Diff line
@@ -134,11 +134,11 @@ public final class NotificationChannel implements Parcelable {
        this.mLockscreenVisibility = lockscreenVisibility;
    }

    // Modifiable by apps.
    // Modifiable by apps on channel creation.

    /**
     * Sets the ringtone that should be played for notifications posted to this channel if
     * the notifications don't supply a ringtone.
     * the notifications don't supply a ringtone. Only modifiable on channel creation.
     */
    public void setDefaultRingtone(Uri defaultRingtone) {
        this.mRingtone = defaultRingtone;
@@ -146,7 +146,7 @@ public final class NotificationChannel implements Parcelable {

    /**
     * Sets whether notifications posted to this channel should display notification lights,
     * on devices that support that feature.
     * on devices that support that feature. Only modifiable on channel creation.
     */
    public void setLights(boolean lights) {
        this.mLights = lights;
@@ -154,7 +154,7 @@ public final class NotificationChannel implements Parcelable {

    /**
     * Sets whether notification posted to this channel should vibrate, even if individual
     * notifications are marked as having vibration.
     * notifications are marked as having vibration only modifiable on channel creation.
     */
    public void setVibration(boolean vibration) {
        this.mVibration = vibration;
+0 −3
Original line number Diff line number Diff line
@@ -36,9 +36,6 @@ public final class Adjustment implements Parcelable {
    private final Bundle mSignals;
    private final int mUser;

    public static final String GROUP_KEY_OVERRIDE_KEY = "group_key_override";
    public static final String NEEDS_AUTOGROUPING_KEY = "autogroup_needed";

    /**
     * Create a notification adjustment.
     *
+1 −166
Original line number Diff line number Diff line
@@ -35,180 +35,15 @@ import java.util.Map;
import android.ext.services.R;

/**
 * Class that provides an updatable ranker module for the notification manager..
 * Class that provides an updatable ranker module for the notification manager.
 */
public final class Ranker extends NotificationRankerService {
    private static final String TAG = "RocketRanker";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private static final int AUTOBUNDLE_AT_COUNT = 4;
    private static final String AUTOBUNDLE_KEY = "ranker_bundle";

    // Map of user : <Map of package : notification keys>. Only contains notifications that are not
    // bundled by the app (aka no group or sort key).
    Map<Integer, Map<String, LinkedHashSet<String>>> mUnbundledNotifications;

    @Override
    public Adjustment onNotificationEnqueued(StatusBarNotification sbn, int importance,
            boolean user) {
        if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey());
        return null;
    }

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
        try {
            List<String> notificationsToBundle = new ArrayList<>();
            if (!sbn.isAppGroup()) {
                // Not grouped by the app, add to the list of notifications for the app;
                // send bundling update if app exceeds the autobundling limit.
                synchronized (mUnbundledNotifications) {
                    Map<String, LinkedHashSet<String>> unbundledNotificationsByUser
                            = mUnbundledNotifications.get(sbn.getUserId());
                    if (unbundledNotificationsByUser == null) {
                        unbundledNotificationsByUser = new HashMap<>();
                    }
                    mUnbundledNotifications.put(sbn.getUserId(), unbundledNotificationsByUser);
                    LinkedHashSet<String> notificationsForPackage
                            = unbundledNotificationsByUser.get(sbn.getPackageName());
                    if (notificationsForPackage == null) {
                        notificationsForPackage = new LinkedHashSet<>();
                    }

                    notificationsForPackage.add(sbn.getKey());
                    unbundledNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);

                    if (notificationsForPackage.size() >= AUTOBUNDLE_AT_COUNT) {
                        for (String key : notificationsForPackage) {
                            notificationsToBundle.add(key);
                        }
                    }
                }
                if (notificationsToBundle.size() > 0) {
                    adjustAutobundlingSummary(sbn.getPackageName(), notificationsToBundle.get(0),
                            true, sbn.getUserId());
                    adjustNotificationBundling(sbn.getPackageName(), notificationsToBundle, true,
                            sbn.getUserId());
                }
            } else {
                // Grouped, but not by us. Send updates to unautobundle, if we bundled it.
                maybeUnbundle(sbn, false, sbn.getUserId());
            }
        } catch (Exception e) {
            Slog.e(TAG, "Failure processing new notification", e);
        }
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        try {
            maybeUnbundle(sbn, true, sbn.getUserId());
        } catch (Exception e) {
            Slog.e(TAG, "Error processing canceled notification", e);
        }
    }

    /**
     * Un-autobundles notifications that are now grouped by the app. Additionally cancels
     * autobundling if the status change of this notification resulted in the loose notification
     * count being under the limit.
     */
    private void maybeUnbundle(StatusBarNotification sbn, boolean notificationGone, int user) {
        List<String> notificationsToUnAutobundle = new ArrayList<>();
        boolean removeSummary = false;
        synchronized (mUnbundledNotifications) {
            Map<String, LinkedHashSet<String>> unbundledNotificationsByUser
                    = mUnbundledNotifications.get(sbn.getUserId());
            if (unbundledNotificationsByUser == null || unbundledNotificationsByUser.size() == 0) {
                return;
            }
            LinkedHashSet<String> notificationsForPackage
                    = unbundledNotificationsByUser.get(sbn.getPackageName());
            if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
                return;
            }
            if (notificationsForPackage.remove(sbn.getKey())) {
                if (!notificationGone) {
                    // Add the current notification to the unbundling list if it still exists.
                    notificationsToUnAutobundle.add(sbn.getKey());
                }
                // If the status change of this notification has brought the number of loose
                // notifications back below the limit, remove the summary and un-autobundle.
                if (notificationsForPackage.size() == AUTOBUNDLE_AT_COUNT - 1) {
                    removeSummary = true;
                    for (String key : notificationsForPackage) {
                        notificationsToUnAutobundle.add(key);
                    }
                }
            }
        }
        if (notificationsToUnAutobundle.size() > 0) {
            if (removeSummary) {
                adjustAutobundlingSummary(sbn.getPackageName(), null, false, user);
            }
            adjustNotificationBundling(sbn.getPackageName(), notificationsToUnAutobundle, false,
                    user);
        }
    }

    @Override
    public void onListenerConnected() {
        if (DEBUG) Log.i(TAG, "CONNECTED");
        mUnbundledNotifications = new HashMap<>();
        for (StatusBarNotification sbn : getActiveNotifications()) {
            onNotificationPosted(sbn);
        }
    }

    private void adjustAutobundlingSummary(String packageName, String key, boolean summaryNeeded,
            int user) {
        Bundle signals = new Bundle();
        if (summaryNeeded) {
            signals.putBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, true);
            signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, AUTOBUNDLE_KEY);
        } else {
            signals.putBoolean(Adjustment.NEEDS_AUTOGROUPING_KEY, false);
        }
        Adjustment adjustment = new Adjustment(packageName, key, IMPORTANCE_UNSPECIFIED, signals,
                getContext().getString(R.string.notification_ranker_autobundle_explanation), null,
                user);
        if (DEBUG) {
            Log.i(TAG, "Summary update for: " + packageName + " "
                    + (summaryNeeded ? "adding" : "removing"));
        }
        try {
            adjustNotification(adjustment);
        } catch (Exception e) {
            Slog.e(TAG, "Adjustment failed", e);
        }

    }
    private void adjustNotificationBundling(String packageName, List<String> keys, boolean bundle,
            int user) {
        List<Adjustment> adjustments = new ArrayList<>();
        for (String key : keys) {
            adjustments.add(createBundlingAdjustment(packageName, key, bundle, user));
            if (DEBUG) Log.i(TAG, "Sending bundling adjustment for: " + key);
        }
        try {
            adjustNotifications(adjustments);
        } catch (Exception e) {
            Slog.e(TAG, "Adjustments failed", e);
        }
    }

    private Adjustment createBundlingAdjustment(String packageName, String key, boolean bundle,
            int user) {
        Bundle signals = new Bundle();
        if (bundle) {
            signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, AUTOBUNDLE_KEY);
        } else {
            signals.putString(Adjustment.GROUP_KEY_OVERRIDE_KEY, null);
        }
        return new Adjustment(packageName, key, IMPORTANCE_UNSPECIFIED, signals,
                getContext().getString(R.string.notification_ranker_autobundle_explanation),
                null, user);
    }

}
 No newline at end of file
+165 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 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.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Slog;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

/**
 * NotificationManagerService helper for auto-grouping notifications.
 */
public class GroupHelper {
    private static final String TAG = "GroupHelper";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    protected static final int AUTOGROUP_AT_COUNT = 4;
    protected static final String AUTOGROUP_KEY = "ranker_group";

    private final Callback mCallback;

    // Map of user : <Map of package : notification keys>. Only contains notifications that are not
    // groupd by the app (aka no group or sort key).
    Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>();

    public GroupHelper(Callback callback) {;
        mCallback = callback;
    }

    public void onNotificationPosted(StatusBarNotification sbn) {
        if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey());
        try {
            List<String> notificationsToGroup = new ArrayList<>();
            if (!sbn.isAppGroup()) {
                // Not grouped by the app, add to the list of notifications for the app;
                // send grouping update if app exceeds the autogrouping limit.
                synchronized (mUngroupedNotifications) {
                    Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
                            = mUngroupedNotifications.get(sbn.getUserId());
                    if (ungroupedNotificationsByUser == null) {
                        ungroupedNotificationsByUser = new HashMap<>();
                    }
                    mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser);
                    LinkedHashSet<String> notificationsForPackage
                            = ungroupedNotificationsByUser.get(sbn.getPackageName());
                    if (notificationsForPackage == null) {
                        notificationsForPackage = new LinkedHashSet<>();
                    }

                    notificationsForPackage.add(sbn.getKey());
                    ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);

                    if (notificationsForPackage.size() >= AUTOGROUP_AT_COUNT) {
                        notificationsToGroup.addAll(notificationsForPackage);
                    }
                }
                if (notificationsToGroup.size() > 0) {
                    adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(),
                            notificationsToGroup.get(0), true);
                    adjustNotificationBundling(notificationsToGroup, true);
                }
            } else {
                // Grouped, but not by us. Send updates to un-autogroup, if we grouped it.
                maybeUngroup(sbn, false, sbn.getUserId());
            }
        } catch (Exception e) {
            Slog.e(TAG, "Failure processing new notification", e);
        }
    }

    public void onNotificationRemoved(StatusBarNotification sbn) {
        try {
            maybeUngroup(sbn, true, sbn.getUserId());
        } catch (Exception e) {
            Slog.e(TAG, "Error processing canceled notification", e);
        }
    }

    /**
     * Un-autogroups notifications that are now grouped by the app. Additionally cancels
     * autogrouping if the status change of this notification resulted in the loose notification
     * count being under the limit.
     */
    private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
        List<String> notificationsToUnAutogroup = new ArrayList<>();
        boolean removeSummary = false;
        synchronized (mUngroupedNotifications) {
            Map<String, LinkedHashSet<String>> ungroupdNotificationsByUser
                    = mUngroupedNotifications.get(sbn.getUserId());
            if (ungroupdNotificationsByUser == null || ungroupdNotificationsByUser.size() == 0) {
                return;
            }
            LinkedHashSet<String> notificationsForPackage
                    = ungroupdNotificationsByUser.get(sbn.getPackageName());
            if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
                return;
            }
            if (notificationsForPackage.remove(sbn.getKey())) {
                if (!notificationGone) {
                    // Add the current notification to the ungrouping list if it still exists.
                    notificationsToUnAutogroup.add(sbn.getKey());
                }
                // If the status change of this notification has brought the number of loose
                // notifications back below the limit, remove the summary and un-autogroup.
                if (notificationsForPackage.size() == AUTOGROUP_AT_COUNT - 1) {
                    removeSummary = true;
                    for (String key : notificationsForPackage) {
                        notificationsToUnAutogroup.add(key);
                    }
                }
            }
        }
        if (notificationsToUnAutogroup.size() > 0) {
            if (removeSummary) {
                adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false);
            }
            adjustNotificationBundling(notificationsToUnAutogroup, false);
        }
    }

    private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey,
            boolean summaryNeeded) {
        if (summaryNeeded) {
            mCallback.addAutoGroupSummary(userId, packageName, triggeringKey);
        } else {
            mCallback.removeAutoGroupSummary(userId, packageName);
        }
    }

    private void adjustNotificationBundling(List<String> keys, boolean group) {
        for (String key : keys) {
            if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group);
            if (group) {
                mCallback.addAutoGroup(key);
            } else {
                mCallback.removeAutoGroup(key);
            }
        }
    }

    protected interface Callback {
        void addAutoGroup(String key);
        void removeAutoGroup(String key);
        void addAutoGroupSummary(int userId, String pkg, String triggeringKey);
        void removeAutoGroupSummary(int user, String pkg);
    }
}
Loading