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

Commit b219c623 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Add retention policy to delete channels

- On initial upgrade, all currently deleted channels will be
cleaned up
- Moving forward, deleted channels will be retained for 30
days before being permanently removed

Test: atest PreferencesHelperTest, NotificationChannelTest
Bug: 177246841
Change-Id: Ibfd33d38e9c7cef51e00ac0b63f295c8734009a6
parent 9032a61e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -254,6 +254,7 @@ package android.app {
    method public boolean isImportanceLockedByOEM();
    method public void lockFields(int);
    method public void setDeleted(boolean);
    method public void setDeletedTimeMs(long);
    method public void setDemoted(boolean);
    method public void setFgServiceShown(boolean);
    method public void setImportanceLockedByCriticalDeviceFunction(boolean);
+31 −2
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.os.Parcelable;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.proto.ProtoOutputStream;
@@ -111,6 +112,7 @@ public final class NotificationChannel implements Parcelable {
    private static final String ATT_CONVERSATION_ID = "conv_id";
    private static final String ATT_IMP_CONVERSATION = "imp_conv";
    private static final String ATT_DEMOTE = "dem";
    private static final String ATT_DELETED_TIME_MS = "del_time";
    private static final String DELIMITER = ",";

    /**
@@ -183,6 +185,7 @@ public final class NotificationChannel implements Parcelable {
            NotificationManager.IMPORTANCE_UNSPECIFIED;
    private static final boolean DEFAULT_DELETED = false;
    private static final boolean DEFAULT_SHOW_BADGE = true;
    private static final long DEFAULT_DELETION_TIME_MS = -1;

    @UnsupportedAppUsage
    private String mId;
@@ -214,6 +217,7 @@ public final class NotificationChannel implements Parcelable {
    private String mConversationId = null;
    private boolean mDemoted = false;
    private boolean mImportantConvo = false;
    private long mDeletedTime = DEFAULT_DELETION_TIME_MS;

    /**
     * Creates a notification channel.
@@ -282,6 +286,7 @@ public final class NotificationChannel implements Parcelable {
        mConversationId = in.readString();
        mDemoted = in.readBoolean();
        mImportantConvo = in.readBoolean();
        mDeletedTime = in.readLong();
    }

    @Override
@@ -341,6 +346,7 @@ public final class NotificationChannel implements Parcelable {
        dest.writeString(mConversationId);
        dest.writeBoolean(mDemoted);
        dest.writeBoolean(mImportantConvo);
        dest.writeLong(mDeletedTime);
    }

    /**
@@ -374,6 +380,14 @@ public final class NotificationChannel implements Parcelable {
        mDeleted = deleted;
    }

    /**
     * @hide
     */
    @TestApi
    public void setDeletedTimeMs(long time) {
        mDeletedTime = time;
    }

    /**
     * @hide
     */
@@ -763,6 +777,13 @@ public final class NotificationChannel implements Parcelable {
        return mDeleted;
    }

    /**
     * @hide
     */
    public long getDeletedTimeMs() {
        return mDeletedTime;
    }

    /**
     * @hide
     */
@@ -906,6 +927,8 @@ public final class NotificationChannel implements Parcelable {
        enableVibration(safeBool(parser, ATT_VIBRATION_ENABLED, false));
        setShowBadge(safeBool(parser, ATT_SHOW_BADGE, false));
        setDeleted(safeBool(parser, ATT_DELETED, false));
        setDeletedTimeMs(XmlUtils.readLongAttribute(
                parser, ATT_DELETED_TIME_MS, DEFAULT_DELETION_TIME_MS));
        setGroup(parser.getAttributeValue(null, ATT_GROUP));
        lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
        setFgServiceShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
@@ -1024,6 +1047,9 @@ public final class NotificationChannel implements Parcelable {
        if (isDeleted()) {
            out.attributeBoolean(null, ATT_DELETED, isDeleted());
        }
        if (getDeletedTimeMs() >= 0) {
            out.attributeLong(null, ATT_DELETED_TIME_MS, getDeletedTimeMs());
        }
        if (getGroup() != null) {
            out.attribute(null, ATT_GROUP, getGroup());
        }
@@ -1091,6 +1117,7 @@ public final class NotificationChannel implements Parcelable {
        record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
        record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
        record.put(ATT_DELETED, Boolean.toString(isDeleted()));
        record.put(ATT_DELETED_TIME_MS, Long.toString(getDeletedTimeMs()));
        record.put(ATT_GROUP, getGroup());
        record.put(ATT_BLOCKABLE_SYSTEM, isBlockable());
        record.put(ATT_ALLOW_BUBBLE, getAllowBubbles());
@@ -1182,6 +1209,7 @@ public final class NotificationChannel implements Parcelable {
                && mVibrationEnabled == that.mVibrationEnabled
                && mShowBadge == that.mShowBadge
                && isDeleted() == that.isDeleted()
                && getDeletedTimeMs() == that.getDeletedTimeMs()
                && isBlockable() == that.isBlockable()
                && mAllowBubbles == that.mAllowBubbles
                && Objects.equals(getId(), that.getId())
@@ -1205,8 +1233,8 @@ public final class NotificationChannel implements Parcelable {
        int result = Objects.hash(getId(), getName(), mDesc, getImportance(), mBypassDnd,
                getLockscreenVisibility(), getSound(), mLights, getLightColor(),
                getUserLockedFields(),
                isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
                getAudioAttributes(), isBlockable(), mAllowBubbles,
                isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(),
                getGroup(), getAudioAttributes(), isBlockable(), mAllowBubbles,
                mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance,
                mParentId, mConversationId, mDemoted, mImportantConvo);
        result = 31 * result + Arrays.hashCode(mVibration);
@@ -1247,6 +1275,7 @@ public final class NotificationChannel implements Parcelable {
                + ", mVibrationEnabled=" + mVibrationEnabled
                + ", mShowBadge=" + mShowBadge
                + ", mDeleted=" + mDeleted
                + ", mDeletedTimeMs=" + mDeletedTime
                + ", mGroup='" + mGroup + '\''
                + ", mAudioAttributes=" + mAudioAttributes
                + ", mBlockableSystem=" + mBlockableSystem
+27 −6
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.service.notification.ConversationChannelWrapper;
import android.service.notification.NotificationListenerService;
import android.service.notification.RankingHelperProto;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
@@ -101,6 +102,7 @@ public class PreferencesHelper implements RankingConfig {
    private static final int NOTIFICATION_PREFERENCES_PULL_LIMIT = 1000;
    private static final int NOTIFICATION_CHANNEL_PULL_LIMIT = 2000;
    private static final int NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT = 1000;
    private static final int NOTIFICATION_CHANNEL_DELETION_RETENTION_DAYS = 30;

    @VisibleForTesting
    static final String TAG_RANKING = "ranking";
@@ -324,12 +326,8 @@ public class PreferencesHelper implements RankingConfig {
                                                channel.setImportanceLockedByOEM(true);
                                            }
                                        }
                                        boolean isInvalidShortcutChannel =
                                                channel.getConversationId() != null &&
                                                        channel.getConversationId().contains(
                                                                PLACEHOLDER_CONVERSATION_ID);
                                        if (mAllowInvalidShortcuts || (!mAllowInvalidShortcuts
                                                && !isInvalidShortcutChannel)) {

                                        if (isShortcutOk(channel) && isDeletionOk(channel)) {
                                            r.channels.put(id, channel);
                                        }
                                    }
@@ -369,6 +367,26 @@ public class PreferencesHelper implements RankingConfig {
        throw new IllegalStateException("Failed to reach END_DOCUMENT");
    }

    private boolean isShortcutOk(NotificationChannel channel) {
        boolean isInvalidShortcutChannel =
                channel.getConversationId() != null &&
                        channel.getConversationId().contains(
                                PLACEHOLDER_CONVERSATION_ID);
        return mAllowInvalidShortcuts || (!mAllowInvalidShortcuts && !isInvalidShortcutChannel);
    }

    private boolean isDeletionOk(NotificationChannel nc) {
        if (!nc.isDeleted()) {
            return true;
        }
        long boundary = System.currentTimeMillis() - (
                DateUtils.DAY_IN_MILLIS * NOTIFICATION_CHANNEL_DELETION_RETENTION_DAYS);
        if (nc.getDeletedTimeMs() <= boundary) {
            return false;
        }
        return true;
    }

    private PackagePreferences getPackagePreferencesLocked(String pkg, int uid) {
        final String key = packagePreferencesKey(pkg, uid);
        return mPackagePreferences.get(key);
@@ -828,6 +846,7 @@ public class PreferencesHelper implements RankingConfig {
                if (existing.isDeleted()) {
                    // The existing channel was deleted - undelete it.
                    existing.setDeleted(false);
                    existing.setDeletedTimeMs(-1);
                    needsPolicyFileChange = true;
                    wasUndeleted = true;

@@ -1119,6 +1138,7 @@ public class PreferencesHelper implements RankingConfig {
    private void deleteNotificationChannelLocked(NotificationChannel channel, String pkg, int uid) {
        if (!channel.isDeleted()) {
            channel.setDeleted(true);
            channel.setDeletedTimeMs(System.currentTimeMillis());
            LogMaker lm = getChannelLog(channel, pkg);
            lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
            MetricsLogger.action(lm);
@@ -1479,6 +1499,7 @@ public class PreferencesHelper implements RankingConfig {
                if (nc.getConversationId() != null
                        && conversationIds.contains(nc.getConversationId())) {
                    nc.setDeleted(true);
                    nc.setDeletedTimeMs(System.currentTimeMillis());
                    LogMaker lm = getChannelLog(nc, pkg);
                    lm.setType(
                            com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
+90 −0
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ import android.provider.Settings.Secure;
import android.service.notification.ConversationChannelWrapper;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableContentResolver;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
@@ -3292,6 +3293,95 @@ public class PreferencesHelperTest extends UiServiceTestCase {
        assertNotNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true));
    }

    @Test
    public void testDeleted_noTime() throws Exception {
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
                mAppOpsManager, mStatsEventBuilderFactory);

        final String xml = "<ranking version=\"1\">\n"
                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
                + "<channel id=\"id\" name=\"hi\" importance=\"3\" deleted=\"true\"/>"
                + "</package>"
                + "</ranking>";
        TypedXmlPullParser parser = Xml.newFastPullParser();
        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
                null);
        parser.nextTag();
        mHelper.readXml(parser, false, UserHandle.USER_ALL);

        assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, "id", true));
    }

    @Test
    public void testDeleted_recentTime() throws Exception {
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
                mAppOpsManager, mStatsEventBuilderFactory);

        mHelper.createNotificationChannel(
                PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
        mHelper.deleteNotificationChannel(PKG_P, UID_P, "id");
        NotificationChannel nc1 = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
        assertTrue(DateUtils.isToday(nc1.getDeletedTimeMs()));
        assertTrue(nc1.isDeleted());

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_P, UID_P, false,
                UserHandle.USER_SYSTEM, "id", NotificationChannel.DEFAULT_CHANNEL_ID);

        TypedXmlPullParser parser = Xml.newFastPullParser();
        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
                null);
        parser.nextTag();
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
                mAppOpsManager, mStatsEventBuilderFactory);
        mHelper.readXml(parser, true, UserHandle.USER_SYSTEM);

        NotificationChannel nc = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
        assertTrue(DateUtils.isToday(nc.getDeletedTimeMs()));
        assertTrue(nc.isDeleted());
    }

    @Test
    public void testUnDelete_time() throws Exception {
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
                mAppOpsManager, mStatsEventBuilderFactory);

        mHelper.createNotificationChannel(
                PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
        mHelper.deleteNotificationChannel(PKG_P, UID_P, "id");
        NotificationChannel nc1 = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
        assertTrue(DateUtils.isToday(nc1.getDeletedTimeMs()));
        assertTrue(nc1.isDeleted());

        mHelper.createNotificationChannel(
                PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
        nc1 = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
        assertEquals(-1, nc1.getDeletedTimeMs());
        assertFalse(nc1.isDeleted());
    }

    @Test
    public void testDeleted_longTime() throws Exception {
        mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger,
                mAppOpsManager, mStatsEventBuilderFactory);

        long time = System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 30);

        final String xml = "<ranking version=\"1\">\n"
                + "<package name=\"" + PKG_O + "\" uid=\"" + UID_O + "\" >\n"
                + "<channel id=\"id\" name=\"hi\" importance=\"3\" deleted=\"true\" del_time=\""
                + time + "\"/>"
                + "</package>"
                + "</ranking>";
        TypedXmlPullParser parser = Xml.newFastPullParser();
        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())),
                null);
        parser.nextTag();
        mHelper.readXml(parser, false, UserHandle.USER_ALL);

        NotificationChannel nc = mHelper.getNotificationChannel(PKG_O, UID_O, "id", true);
        assertNull(nc);
    }

    @Test
    public void testGetConversations_all() {
        String convoId = "convo";