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

Commit a3ddc7d0 authored by Iavor-Valentin Iftime's avatar Iavor-Valentin Iftime
Browse files

Notification unbundle/rebundle on adjustment type updates

 Trigger notification unbundling/rebundling when adjustments are enabled/disabled from Settings.
 Save classification in NotificationRecord and apply when adjustments are enabled.

Flag: android.service.notification.notification_regroup_on_classification
Test: atest NotificationManagerServiceTest
Test: atest GroupHelperTest

Bug: 382047364
Bug: 377697247

Change-Id: Ic738fd9e5c0f33a1b504aef17b2ba57a68e5ae84
parent 210690bd
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -15,6 +15,10 @@
 */
package android.app;

import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
import static android.service.notification.Adjustment.TYPE_NEWS;
import static android.service.notification.Adjustment.TYPE_PROMOTION;
import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
import static android.service.notification.Flags.FLAG_NOTIFICATION_CONVERSATION_CHANNEL_MANAGEMENT;

import android.annotation.FlaggedApi;
@@ -37,6 +41,7 @@ import android.os.VibrationEffect;
import android.os.vibrator.persistence.VibrationXmlParser;
import android.os.vibrator.persistence.VibrationXmlSerializer;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
import android.util.Log;
@@ -1606,6 +1611,26 @@ public final class NotificationChannel implements Parcelable {
        return sb.toString();
    }

    /**
     * Get the reserved bundle channel ID for an Adjustment type
     * @param the Adjustment type
     * @return the channel ID, or null if type is invalid
     * @hide
     */
    public static @Nullable String getChannelIdForBundleType(@Adjustment.Types int type) {
        switch (type) {
            case TYPE_CONTENT_RECOMMENDATION:
                return RECS_ID;
            case TYPE_NEWS:
                return NEWS_ID;
            case TYPE_PROMOTION:
                return PROMOTIONS_ID;
            case TYPE_SOCIAL_MEDIA:
                return SOCIAL_MEDIA_ID;
        }
        return null;
    }

    public static final @android.annotation.NonNull Creator<NotificationChannel> CREATOR =
            new Creator<NotificationChannel>() {
        @Override
+0 −6
Original line number Diff line number Diff line
@@ -244,10 +244,4 @@ interface IStatusBarService

    /** Shows rear display educational dialog */
    void showRearDisplayDialog(int currentBaseState);

    /** Unbundle a categorized notification */
    void unbundleNotification(String key);

    /** Rebundle an (un)categorized notification */
    void rebundleNotification(String key);
}
+0 −4
Original line number Diff line number Diff line
@@ -433,10 +433,6 @@ class FakeStatusBarService : IStatusBarService.Stub() {

    override fun showRearDisplayDialog(currentBaseState: Int) {}

    override fun unbundleNotification(key: String) {}

    override fun rebundleNotification(key: String) {}

    companion object {
        const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY
        const val SECONDARY_DISPLAY_ID = 2
+0 −11
Original line number Diff line number Diff line
@@ -101,15 +101,4 @@ public interface NotificationDelegate {
    void onNotificationFeedbackReceived(String key, Bundle feedback);

    void prepareForPossibleShutdown();

    /**
     *  Called when the notification should be unbundled.
     * @param key the notification key
     */
    void unbundleNotification(String key);
    /**
     *  Called when the notification should be rebundled.
     * @param key the notification key
     */
    void rebundleNotification(String key);
}
+194 −57
Original line number Diff line number Diff line
@@ -290,6 +290,7 @@ import android.permission.PermissionManager;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.notification.Adjustment;
import android.service.notification.Adjustment.Types;
import android.service.notification.Condition;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.DeviceEffectsApplier;
@@ -417,6 +418,7 @@ import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/** {@hide} */
@@ -1864,20 +1866,62 @@ public class NotificationManagerService extends SystemService {
                mAssistants.notifyAssistantFeedbackReceived(r, feedback);
            }
        }
    };
        @Override
        public void unbundleNotification(String key) {
            if (!(notificationClassification() && notificationRegroupOnClassification())) {
    private void unclassifyNotificationsForUser(final int userId) {
        if (DBG) {
            Slog.v(TAG, "unclassifyForUser: " + userId);
        }
        unclassifyNotificationsFiltered((r) -> r.getUserId() == userId);
    }
    private void unclassifyNotificationsForUid(final int userId, @NonNull final String pkg) {
        if (DBG) {
            Slog.v(TAG, "unclassifyForUid userId: " + userId + " pkg: " + pkg);
        }
        unclassifyNotificationsFiltered((r) ->
                r.getUserId() == userId
                && Objects.equals(r.getSbn().getPackageName(), pkg));
    }
    private void unclassifyNotificationsForUserAndType(final int userId,
            final @Types int bundleType) {
        if (DBG) {
            Slog.v(TAG,
                    "unclassifyForUserAndType userId: " + userId + " bundleType: " + bundleType);
        }
        final String bundleChannelId = NotificationChannel.getChannelIdForBundleType(bundleType);
        unclassifyNotificationsFiltered((r) ->
                r.getUserId() == userId
                && r.getChannel() != null
                && Objects.equals(bundleChannelId, r.getChannel().getId()));
    }
    private void unclassifyNotificationsFiltered(Predicate<NotificationRecord> filter) {
        if (!(notificationClassificationUi() && notificationRegroupOnClassification())) {
            return;
        }
        synchronized (mNotificationLock) {
                NotificationRecord r = mNotificationsByKey.get(key);
                if (r == null) {
                    return;
            for (int i = 0; i < mEnqueuedNotifications.size(); i++) {
                final NotificationRecord r = mEnqueuedNotifications.get(i);
                if (filter.test(r)) {
                    unclassifyNotificationLocked(r);
                }
            }
            for (int i = 0; i < mNotificationList.size(); i++) {
                final NotificationRecord r = mNotificationList.get(i);
                if (filter.test(r)) {
                    unclassifyNotificationLocked(r);
                }
            }
        }
    }
    @GuardedBy("mNotificationLock")
    private void unclassifyNotificationLocked(@NonNull final NotificationRecord r) {
        if (DBG) {
                    Slog.v(TAG, "unbundleNotification: " + r);
            Slog.v(TAG, "unclassifyNotification: " + r);
        }
        boolean hasOriginalSummary = false;
@@ -1895,16 +1939,31 @@ public class NotificationManagerService extends SystemService {
        String origChannelId = r.getNotification().getChannelId();
        NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel(
                r.getSbn().getPackageName(), r.getUid(), origChannelId, false);
                if (originalChannel != null && !origChannelId.equals(r.getChannel().getId())) {
        String currChannelId = r.getChannel().getId();
        boolean isBundled = NotificationChannel.SYSTEM_RESERVED_IDS.contains(currChannelId);
        if (originalChannel != null && !origChannelId.equals(currChannelId) && isBundled) {
            r.updateNotificationChannel(originalChannel);
            mGroupHelper.onNotificationUnbundled(r, hasOriginalSummary);
        }
    }
    @VisibleForTesting
    void unclassifyNotification(final String key) {
        if (!(notificationClassificationUi() && notificationRegroupOnClassification())) {
            return;
        }
        synchronized (mNotificationLock) {
            NotificationRecord r = mNotificationsByKey.get(key);
            if (r == null) {
                return;
            }
            unclassifyNotificationLocked(r);
        }
    }
        @Override
        public void rebundleNotification(String key) {
            if (!(notificationClassification() && notificationRegroupOnClassification())) {
    @VisibleForTesting
    void reclassifyNotification(String key) {
        if (!(notificationClassificationUi() && notificationRegroupOnClassification())) {
            return;
        }
        synchronized (mNotificationLock) {
@@ -1912,26 +1971,78 @@ public class NotificationManagerService extends SystemService {
            if (r == null) {
                return;
            }
            reclassifyNotificationLocked(r, true);
        }
    }
    private void reclassifyNotificationsFiltered(Predicate<NotificationRecord> filter) {
        if (!(notificationClassificationUi() && notificationRegroupOnClassification())) {
            return;
        }
        synchronized (mNotificationLock) {
            for (int i = 0; i < mEnqueuedNotifications.size(); i++) {
                final NotificationRecord r = mEnqueuedNotifications.get(i);
                if (filter.test(r)) {
                    reclassifyNotificationLocked(r, false);
                }
            }
            for (int i = 0; i < mNotificationList.size(); i++) {
                final NotificationRecord r = mNotificationList.get(i);
                if (filter.test(r)) {
                    reclassifyNotificationLocked(r, true);
                }
            }
        }
    }
    private void reclassifyNotificationsForUserAndType(final int userId,
            final @Types int bundleType) {
        if (DBG) {
            Slog.v(TAG, "reclassifyNotificationsForUserAndType userId: " + userId + " bundleType: "
                    + bundleType);
        }
        reclassifyNotificationsFiltered(
                (r) -> r.getUserId() == userId && r.getBundleType() == bundleType);
    }
    private void reclassifyNotificationsForUid(final int userId, final String pkg) {
        if (DBG) {
            Slog.v(TAG, "reclassifyNotificationsForUid userId: " + userId + " pkg: " + pkg);
        }
        reclassifyNotificationsFiltered((r) ->
                r.getUserId() == userId && Objects.equals(r.getSbn().getPackageName(), pkg));
    }
    private void reclassifyNotificationsForUser(final int userId) {
        if (DBG) {
            Slog.v(TAG, "reclassifyAllNotificationsForUser: " + userId);
        }
        reclassifyNotificationsFiltered((r) -> r.getUserId() == userId);
    }
    @GuardedBy("mNotificationLock")
    private void reclassifyNotificationLocked(@NonNull final NotificationRecord r,
            final boolean isPosted) {
        if (DBG) {
                    Slog.v(TAG, "rebundleNotification: " + r);
            Slog.v(TAG, "reclassifyNotification: " + r);
        }
                if (r.getBundleType() != Adjustment.TYPE_OTHER) {
        boolean isBundled = NotificationChannel.SYSTEM_RESERVED_IDS.contains(
                r.getChannel().getId());
        if (r.getBundleType() != Adjustment.TYPE_OTHER && !isBundled) {
            final Bundle classifBundle = new Bundle();
            classifBundle.putInt(KEY_TYPE, r.getBundleType());
            Adjustment adj = new Adjustment(r.getSbn().getPackageName(), r.getKey(),
                            classifBundle, "rebundle", r.getUserId());
                    applyAdjustmentLocked(r, adj, /* isPosted= */ true);
                    classifBundle, "reclassify", r.getUserId());
            applyAdjustmentLocked(r, adj, isPosted);
            mRankingHandler.requestSort();
        } else {
            if (DBG) {
                        Slog.w(TAG, "Can't rebundle. No valid bundle type for: " + r);
                    }
                Slog.w(TAG, "Can't reclassify. No valid bundle type or already bundled: " + r);
            }
        }
    }
    };
    NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() {
        @Nullable
@@ -4369,7 +4480,11 @@ public class NotificationManagerService extends SystemService {
        public void allowAssistantAdjustment(String adjustmentType) {
            checkCallerIsSystemOrSystemUiOrShell();
            mAssistants.allowAdjustmentType(adjustmentType);
            if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
                if (KEY_TYPE.equals(adjustmentType)) {
                    reclassifyNotificationsForUser(UserHandle.getUserId(Binder.getCallingUid()));
                }
            }
            handleSavePolicyFile();
        }
@@ -4378,7 +4493,11 @@ public class NotificationManagerService extends SystemService {
        public void disallowAssistantAdjustment(String adjustmentType) {
            checkCallerIsSystemOrSystemUiOrShell();
            mAssistants.disallowAdjustmentType(adjustmentType);
            if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
                if (KEY_TYPE.equals(adjustmentType)) {
                    unclassifyNotificationsForUser(UserHandle.getUserId(Binder.getCallingUid()));
                }
            }
            handleSavePolicyFile();
        }
@@ -4423,7 +4542,15 @@ public class NotificationManagerService extends SystemService {
        public void setAssistantAdjustmentKeyTypeState(int type, boolean enabled) {
            checkCallerIsSystemOrSystemUiOrShell();
            mAssistants.setAssistantAdjustmentKeyTypeState(type, enabled);
            if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
                if (enabled) {
                    reclassifyNotificationsForUserAndType(
                            UserHandle.getUserId(Binder.getCallingUid()), type);
                } else {
                    unclassifyNotificationsForUserAndType(
                            UserHandle.getUserId(Binder.getCallingUid()), type);
                }
            }
            handleSavePolicyFile();
        }
@@ -4439,7 +4566,15 @@ public class NotificationManagerService extends SystemService {
        public void setTypeAdjustmentForPackageState(String pkg, boolean enabled) {
            checkCallerIsSystemOrSystemUiOrShell();
            mAssistants.setTypeAdjustmentForPackageState(pkg, enabled);
            if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
                if (enabled) {
                    reclassifyNotificationsForUid(UserHandle.getUserId(Binder.getCallingUid()),
                            pkg);
                } else {
                    unclassifyNotificationsForUid(UserHandle.getUserId(Binder.getCallingUid()),
                            pkg);
                }
            }
            handleSavePolicyFile();
        }
@@ -7244,6 +7379,10 @@ public class NotificationManagerService extends SystemService {
        if (adjustment.getSignals() != null) {
            final Bundle adjustments = adjustment.getSignals();
            Bundle.setDefusable(adjustments, true);
            // Save classification even if the adjustment is disabled, in case user enables it later
            if (notificationClassification() && adjustments.containsKey(KEY_TYPE)) {
                r.setBundleType(adjustments.getInt(KEY_TYPE));
            }
            List<String> toRemove = new ArrayList<>();
            for (String potentialKey : adjustments.keySet()) {
                if (!mAssistants.isAdjustmentAllowed(potentialKey)) {
@@ -7273,9 +7412,7 @@ public class NotificationManagerService extends SystemService {
                    int classification = adjustments.getInt(KEY_TYPE);
                    // swap app provided type with the real thing
                    adjustments.putParcelable(KEY_TYPE, newChannel);
                    logClassificationChannelAdjustmentReceived(r, isPosted, classification);
                    r.setBundleType(classification);
                }
            }
            r.addAdjustment(adjustment);
Loading