Loading services/core/java/com/android/server/notification/NotificationManagerService.java +2 −1 Original line number Diff line number Diff line Loading @@ -2514,7 +2514,8 @@ public class NotificationManagerService extends SystemService { mNotificationChannelLogger, mAppOps, mUserProfiles, mShowReviewPermissionsNotification); mShowReviewPermissionsNotification, Clock.systemUTC()); mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper, mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat); mSnoozeHelper = snoozeHelper; Loading services/core/java/com/android/server/notification/PreferencesHelper.java +108 −68 Original line number Diff line number Diff line Loading @@ -93,6 +93,8 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; import java.time.Clock; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; Loading @@ -113,6 +115,8 @@ public class PreferencesHelper implements RankingConfig { private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4; @VisibleForTesting static final int UNKNOWN_UID = UserHandle.USER_NULL; // The amount of time pacakage preferences can exist without the app being installed. private static final long PREF_GRACE_PERIOD_MS = Duration.ofDays(2).toMillis(); @VisibleForTesting static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000; Loading Loading @@ -149,6 +153,8 @@ public class PreferencesHelper implements RankingConfig { private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app"; private static final String ATT_SENT_VALID_BUBBLE = "sent_valid_bubble"; private static final String ATT_CREATION_TIME = "creation_time"; private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT; private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE; private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED; Loading Loading @@ -208,11 +214,13 @@ public class PreferencesHelper implements RankingConfig { private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS; private final boolean mShowReviewPermissionsNotification; Clock mClock; public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager, NotificationChannelLogger notificationChannelLogger, AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles, boolean showReviewPermissionsNotification) { boolean showReviewPermissionsNotification, Clock clock) { mContext = context; mZenModeHelper = zenHelper; mRankingHandler = rankingHandler; Loading @@ -225,7 +233,7 @@ public class PreferencesHelper implements RankingConfig { mShowReviewPermissionsNotification = showReviewPermissionsNotification; mIsMediaNotificationFilteringEnabled = context.getResources() .getBoolean(R.bool.config_quickSettingsShowMediaPlayer); mClock = clock; XML_VERSION = 4; updateBadgingEnabled(); Loading Loading @@ -309,7 +317,7 @@ public class PreferencesHelper implements RankingConfig { parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY), parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY), parser.getAttributeBoolean(null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE), bubblePref); bubblePref, parser.getAttributeLong(null, ATT_CREATION_TIME, mClock.millis())); r.bubblePreference = bubblePref; r.priority = parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY); r.visibility = parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY); Loading Loading @@ -463,12 +471,12 @@ public class PreferencesHelper implements RankingConfig { // TODO (b/194833441): use permissionhelper instead of DEFAULT_IMPORTANCE return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid, DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE, DEFAULT_BUBBLE_PREFERENCE); DEFAULT_BUBBLE_PREFERENCE, mClock.millis()); } private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, @UserIdInt int userId, int uid, int importance, int priority, int visibility, boolean showBadge, int bubblePreference) { boolean showBadge, int bubblePreference, long creationTime) { final String key = packagePreferencesKey(pkg, uid); PackagePreferences r = (uid == UNKNOWN_UID) Loading @@ -483,6 +491,11 @@ public class PreferencesHelper implements RankingConfig { r.visibility = visibility; r.showBadge = showBadge; r.bubblePreference = bubblePreference; if (Flags.persistIncompleteRestoreData()) { if (r.uid == UNKNOWN_UID) { r.creationTime = creationTime; } } try { createDefaultChannelIfNeededLocked(r); Loading @@ -496,6 +509,12 @@ public class PreferencesHelper implements RankingConfig { mPackagePreferences.put(key, r); } } if (r.uid == UNKNOWN_UID) { if (Flags.persistIncompleteRestoreData() && PREF_GRACE_PERIOD_MS < (mClock.millis() - r.creationTime)) { mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, userId)); } } return r; } Loading Loading @@ -590,6 +609,35 @@ public class PreferencesHelper implements RankingConfig { if (forBackup && UserHandle.getUserId(r.uid) != userId) { continue; } writePackageXml(r, out, notifPermissions, forBackup); } } if (Flags.persistIncompleteRestoreData() && !forBackup) { synchronized (mRestoredWithoutUids) { final int N = mRestoredWithoutUids.size(); for (int i = 0; i < N; i++) { final PackagePreferences r = mRestoredWithoutUids.valueAt(i); writePackageXml(r, out, notifPermissions, false); } } } // Some apps have permissions set but don't have expanded notification settings if (!notifPermissions.isEmpty()) { for (Pair<Integer, String> app : notifPermissions.keySet()) { out.startTag(null, TAG_PACKAGE); out.attribute(null, ATT_NAME, app.second); out.attributeInt(null, ATT_IMPORTANCE, notifPermissions.get(app).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); out.endTag(null, TAG_PACKAGE); } } out.endTag(null, TAG_RANKING); } public void writePackageXml(PackagePreferences r, TypedXmlSerializer out, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions, boolean forBackup) throws IOException { out.startTag(null, TAG_PACKAGE); out.attribute(null, ATT_NAME, r.pkg); if (!notifPermissions.isEmpty()) { Loading Loading @@ -624,6 +672,10 @@ public class PreferencesHelper implements RankingConfig { r.userDemotedMsgApp); out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble); if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) { out.attributeLong(null, ATT_CREATION_TIME, r.creationTime); } if (!forBackup) { out.attributeInt(null, ATT_UID, r.uid); } Loading Loading @@ -655,19 +707,6 @@ public class PreferencesHelper implements RankingConfig { out.endTag(null, TAG_PACKAGE); } } // Some apps have permissions set but don't have expanded notification settings if (!notifPermissions.isEmpty()) { for (Pair<Integer, String> app : notifPermissions.keySet()) { out.startTag(null, TAG_PACKAGE); out.attribute(null, ATT_NAME, app.second); out.attributeInt(null, ATT_IMPORTANCE, notifPermissions.get(app).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); out.endTag(null, TAG_PACKAGE); } } out.endTag(null, TAG_RANKING); } /** * Sets whether bubbles are allowed. Loading Loading @@ -2906,6 +2945,7 @@ public class PreferencesHelper implements RankingConfig { boolean hasSentValidBubble = false; boolean migrateToPm = false; long creationTime; Delegate delegate = null; ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); Loading services/core/java/com/android/server/notification/flags.aconfig +6 −0 Original line number Diff line number Diff line Loading @@ -95,3 +95,9 @@ flag { bug: "331967355" } flag { name: "persist_incomplete_restore_data" namespace: "systemui" description: "Stores restore data for not-yet-installed pkgs for 48 hours" bug: "334999659" } services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +183 −10 Original line number Diff line number Diff line Loading @@ -46,10 +46,13 @@ import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA; import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; Loading Loading @@ -110,6 +113,9 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.permission.PermissionManager; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.Secure; Loading Loading @@ -140,6 +146,9 @@ import com.android.os.AtomsProto.PackageNotificationPreferences; import com.android.server.UiServiceTestCase; import com.android.server.notification.PermissionHelper.PackagePermission; import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.protobuf.InvalidProtocolBufferException; Loading @@ -148,6 +157,7 @@ import org.json.JSONArray; import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; Loading @@ -160,6 +170,8 @@ import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.io.StringWriter; import java.time.Clock; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; Loading @@ -171,7 +183,7 @@ import java.util.Set; import java.util.concurrent.ThreadLocalRandom; @SmallTest @RunWith(AndroidJUnit4.class) @RunWith(ParameterizedAndroidJunit4.class) public class PreferencesHelperTest extends UiServiceTestCase { private static final int UID_HEADLESS = 1000000; private static final UserHandle USER = UserHandle.of(0); Loading Loading @@ -212,6 +224,22 @@ public class PreferencesHelperTest extends UiServiceTestCase { private AudioAttributes mAudioAttributes; private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @Mock Clock mClock; @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { return FlagsParameterization.allCombinationsOf( FLAG_PERSIST_INCOMPLETE_RESTORE_DATA); } public PreferencesHelperTest(FlagsParameterization flags) { mSetFlagsRule.setFlagsParameterization(flags); } @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); Loading Loading @@ -326,13 +354,14 @@ public class PreferencesHelperTest extends UiServiceTestCase { currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS)); } when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds); when(mClock.millis()).thenReturn(System.currentTimeMillis()); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false); false, mClock); mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false); false, mClock); resetZenModeHelper(); mAudioAttributes = new AudioAttributes.Builder() Loading Loading @@ -680,7 +709,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_oldXml_migrates() throws Exception { mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, /* showReviewPermissionsNotification= */ true); /* showReviewPermissionsNotification= */ true, mClock); String xml = "<ranking version=\"2\">\n" + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1 Loading Loading @@ -816,7 +845,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception { mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, /* showReviewPermissionsNotification= */ true); /* showReviewPermissionsNotification= */ true, mClock); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" Loading Loading @@ -875,7 +904,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_permissionNotificationOff() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, /* showReviewPermissionsNotification= */ false); /* showReviewPermissionsNotification= */ false, mClock); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" Loading Loading @@ -934,7 +963,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, /* showReviewPermissionsNotification= */ true); /* showReviewPermissionsNotification= */ true, mClock); String xml = "<ranking version=\"4\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" Loading Loading @@ -1010,7 +1039,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(1234); final ApplicationInfo app = new ApplicationInfo(); app.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1; when(mPm.getApplicationInfoAsUser(eq("something"), anyInt(), anyInt())).thenReturn(app); when(mPm.getApplicationInfoAsUser( eq("something"), anyInt(), eq(USER_SYSTEM))).thenReturn(app); mXmlHelper.onPackagesChanged(false, 0, new String[] {"something"}, new int[] {1234}); Loading Loading @@ -1452,6 +1482,149 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(actualChannel.isSoundRestored()); } @Test @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA) public void testRestoreXml_delayedRestore() throws Exception { // simulate package not installed when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow( new PackageManager.NameNotFoundException()); when(mClock.millis()).thenReturn(System.currentTimeMillis()); String id = "id"; String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n" + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" " + "show_badge=\"true\" />\n" + "</package>\n" + "</ranking>\n"; loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); // settings are not available with real uid because pkg is not installed assertThat(mXmlHelper.getNotificationChannel(PKG_R, UID_P, id, false)).isNull(); // but the settings are in memory with unknown_uid assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id, false)).isNotNull(); // package is "installed" when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P); // Trigger 2nd restore pass mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R}, new int[]{UID_P}); NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id, false); assertThat(channel.getImportance()).isEqualTo(2); assertThat(channel.canShowBadge()).isTrue(); assertThat(channel.canBypassDnd()).isFalse(); // removed from 'pending install' set assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id,false)).isNull(); } @Test @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA) public void testRestoreXml_delayedRestore_afterReboot() throws Exception { // load restore data ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(UID_R, PKG_R), new Pair<>(true, false)); when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM)) .thenReturn(appPermissions); // simulate package not installed when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow( new PackageManager.NameNotFoundException()); when(mClock.millis()).thenReturn(System.currentTimeMillis()); String id = "id"; String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n" + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" " + "show_badge=\"true\" />\n" + "</package>\n" + "</ranking>\n"; loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); // simulate write to disk TypedXmlSerializer serializer = Xml.newFastSerializer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); serializer.startDocument(null, true); mXmlHelper.writeXml(serializer, false, USER_SYSTEM); serializer.endDocument(); serializer.flush(); // simulate load after reboot mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false, mClock); loadByteArrayXml(baos.toByteArray(), false, USER_SYSTEM); // Trigger 2nd restore pass when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P); mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R}, new int[]{UID_P}); NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id, false); assertThat(channel.getImportance()).isEqualTo(2); assertThat(channel.canShowBadge()).isTrue(); assertThat(channel.canBypassDnd()).isFalse(); } @Test @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA) public void testRestoreXml_delayedRestore_packageMissingAfterTwoDays() throws Exception { // load restore data ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(UID_R, PKG_R), new Pair<>(true, false)); when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM)) .thenReturn(appPermissions); // simulate package not installed when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow( new PackageManager.NameNotFoundException()); String id = "id"; String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n" + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" " + "show_badge=\"true\" />\n" + "</package>\n" + "</ranking>\n"; loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); // simulate write to disk TypedXmlSerializer serializer = Xml.newFastSerializer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); serializer.startDocument(null, true); mXmlHelper.writeXml(serializer, false, USER_SYSTEM); serializer.endDocument(); serializer.flush(); // advance time by 2 days when(mClock.millis()).thenReturn( Duration.ofDays(2).toMillis() + System.currentTimeMillis()); // simulate load after reboot mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false, mClock); loadByteArrayXml(xml.getBytes(), false, USER_SYSTEM); // Trigger 2nd restore pass mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R}, new int[]{UID_P}); // verify the 2nd restore pass failed because the restore data had been removed assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id, false)).isNull(); } /** * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to Loading Loading @@ -1520,10 +1693,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false); false, mClock); mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false); false, mClock); NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); Loading Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +2 −1 Original line number Diff line number Diff line Loading @@ -2514,7 +2514,8 @@ public class NotificationManagerService extends SystemService { mNotificationChannelLogger, mAppOps, mUserProfiles, mShowReviewPermissionsNotification); mShowReviewPermissionsNotification, Clock.systemUTC()); mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper, mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat); mSnoozeHelper = snoozeHelper; Loading
services/core/java/com/android/server/notification/PreferencesHelper.java +108 −68 Original line number Diff line number Diff line Loading @@ -93,6 +93,8 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; import java.time.Clock; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; Loading @@ -113,6 +115,8 @@ public class PreferencesHelper implements RankingConfig { private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4; @VisibleForTesting static final int UNKNOWN_UID = UserHandle.USER_NULL; // The amount of time pacakage preferences can exist without the app being installed. private static final long PREF_GRACE_PERIOD_MS = Duration.ofDays(2).toMillis(); @VisibleForTesting static final int NOTIFICATION_CHANNEL_COUNT_LIMIT = 5000; Loading Loading @@ -149,6 +153,8 @@ public class PreferencesHelper implements RankingConfig { private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app"; private static final String ATT_SENT_VALID_BUBBLE = "sent_valid_bubble"; private static final String ATT_CREATION_TIME = "creation_time"; private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT; private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE; private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED; Loading Loading @@ -208,11 +214,13 @@ public class PreferencesHelper implements RankingConfig { private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS; private final boolean mShowReviewPermissionsNotification; Clock mClock; public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager, NotificationChannelLogger notificationChannelLogger, AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles, boolean showReviewPermissionsNotification) { boolean showReviewPermissionsNotification, Clock clock) { mContext = context; mZenModeHelper = zenHelper; mRankingHandler = rankingHandler; Loading @@ -225,7 +233,7 @@ public class PreferencesHelper implements RankingConfig { mShowReviewPermissionsNotification = showReviewPermissionsNotification; mIsMediaNotificationFilteringEnabled = context.getResources() .getBoolean(R.bool.config_quickSettingsShowMediaPlayer); mClock = clock; XML_VERSION = 4; updateBadgingEnabled(); Loading Loading @@ -309,7 +317,7 @@ public class PreferencesHelper implements RankingConfig { parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY), parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY), parser.getAttributeBoolean(null, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE), bubblePref); bubblePref, parser.getAttributeLong(null, ATT_CREATION_TIME, mClock.millis())); r.bubblePreference = bubblePref; r.priority = parser.getAttributeInt(null, ATT_PRIORITY, DEFAULT_PRIORITY); r.visibility = parser.getAttributeInt(null, ATT_VISIBILITY, DEFAULT_VISIBILITY); Loading Loading @@ -463,12 +471,12 @@ public class PreferencesHelper implements RankingConfig { // TODO (b/194833441): use permissionhelper instead of DEFAULT_IMPORTANCE return getOrCreatePackagePreferencesLocked(pkg, UserHandle.getUserId(uid), uid, DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE, DEFAULT_BUBBLE_PREFERENCE); DEFAULT_BUBBLE_PREFERENCE, mClock.millis()); } private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, @UserIdInt int userId, int uid, int importance, int priority, int visibility, boolean showBadge, int bubblePreference) { boolean showBadge, int bubblePreference, long creationTime) { final String key = packagePreferencesKey(pkg, uid); PackagePreferences r = (uid == UNKNOWN_UID) Loading @@ -483,6 +491,11 @@ public class PreferencesHelper implements RankingConfig { r.visibility = visibility; r.showBadge = showBadge; r.bubblePreference = bubblePreference; if (Flags.persistIncompleteRestoreData()) { if (r.uid == UNKNOWN_UID) { r.creationTime = creationTime; } } try { createDefaultChannelIfNeededLocked(r); Loading @@ -496,6 +509,12 @@ public class PreferencesHelper implements RankingConfig { mPackagePreferences.put(key, r); } } if (r.uid == UNKNOWN_UID) { if (Flags.persistIncompleteRestoreData() && PREF_GRACE_PERIOD_MS < (mClock.millis() - r.creationTime)) { mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, userId)); } } return r; } Loading Loading @@ -590,6 +609,35 @@ public class PreferencesHelper implements RankingConfig { if (forBackup && UserHandle.getUserId(r.uid) != userId) { continue; } writePackageXml(r, out, notifPermissions, forBackup); } } if (Flags.persistIncompleteRestoreData() && !forBackup) { synchronized (mRestoredWithoutUids) { final int N = mRestoredWithoutUids.size(); for (int i = 0; i < N; i++) { final PackagePreferences r = mRestoredWithoutUids.valueAt(i); writePackageXml(r, out, notifPermissions, false); } } } // Some apps have permissions set but don't have expanded notification settings if (!notifPermissions.isEmpty()) { for (Pair<Integer, String> app : notifPermissions.keySet()) { out.startTag(null, TAG_PACKAGE); out.attribute(null, ATT_NAME, app.second); out.attributeInt(null, ATT_IMPORTANCE, notifPermissions.get(app).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); out.endTag(null, TAG_PACKAGE); } } out.endTag(null, TAG_RANKING); } public void writePackageXml(PackagePreferences r, TypedXmlSerializer out, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> notifPermissions, boolean forBackup) throws IOException { out.startTag(null, TAG_PACKAGE); out.attribute(null, ATT_NAME, r.pkg); if (!notifPermissions.isEmpty()) { Loading Loading @@ -624,6 +672,10 @@ public class PreferencesHelper implements RankingConfig { r.userDemotedMsgApp); out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble); if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) { out.attributeLong(null, ATT_CREATION_TIME, r.creationTime); } if (!forBackup) { out.attributeInt(null, ATT_UID, r.uid); } Loading Loading @@ -655,19 +707,6 @@ public class PreferencesHelper implements RankingConfig { out.endTag(null, TAG_PACKAGE); } } // Some apps have permissions set but don't have expanded notification settings if (!notifPermissions.isEmpty()) { for (Pair<Integer, String> app : notifPermissions.keySet()) { out.startTag(null, TAG_PACKAGE); out.attribute(null, ATT_NAME, app.second); out.attributeInt(null, ATT_IMPORTANCE, notifPermissions.get(app).first ? IMPORTANCE_DEFAULT : IMPORTANCE_NONE); out.endTag(null, TAG_PACKAGE); } } out.endTag(null, TAG_RANKING); } /** * Sets whether bubbles are allowed. Loading Loading @@ -2906,6 +2945,7 @@ public class PreferencesHelper implements RankingConfig { boolean hasSentValidBubble = false; boolean migrateToPm = false; long creationTime; Delegate delegate = null; ArrayMap<String, NotificationChannel> channels = new ArrayMap<>(); Loading
services/core/java/com/android/server/notification/flags.aconfig +6 −0 Original line number Diff line number Diff line Loading @@ -95,3 +95,9 @@ flag { bug: "331967355" } flag { name: "persist_incomplete_restore_data" namespace: "systemui" description: "Stores restore data for not-yet-installed pkgs for 48 hours" bug: "334999659" }
services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +183 −10 Original line number Diff line number Diff line Loading @@ -46,10 +46,13 @@ import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA; import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; Loading Loading @@ -110,6 +113,9 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.permission.PermissionManager; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.Secure; Loading Loading @@ -140,6 +146,9 @@ import com.android.os.AtomsProto.PackageNotificationPreferences; import com.android.server.UiServiceTestCase; import com.android.server.notification.PermissionHelper.PackagePermission; import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.protobuf.InvalidProtocolBufferException; Loading @@ -148,6 +157,7 @@ import org.json.JSONArray; import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; Loading @@ -160,6 +170,8 @@ import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.io.StringWriter; import java.time.Clock; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; Loading @@ -171,7 +183,7 @@ import java.util.Set; import java.util.concurrent.ThreadLocalRandom; @SmallTest @RunWith(AndroidJUnit4.class) @RunWith(ParameterizedAndroidJunit4.class) public class PreferencesHelperTest extends UiServiceTestCase { private static final int UID_HEADLESS = 1000000; private static final UserHandle USER = UserHandle.of(0); Loading Loading @@ -212,6 +224,22 @@ public class PreferencesHelperTest extends UiServiceTestCase { private AudioAttributes mAudioAttributes; private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); @Mock Clock mClock; @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { return FlagsParameterization.allCombinationsOf( FLAG_PERSIST_INCOMPLETE_RESTORE_DATA); } public PreferencesHelperTest(FlagsParameterization flags) { mSetFlagsRule.setFlagsParameterization(flags); } @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); Loading Loading @@ -326,13 +354,14 @@ public class PreferencesHelperTest extends UiServiceTestCase { currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS)); } when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds); when(mClock.millis()).thenReturn(System.currentTimeMillis()); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false); false, mClock); mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false); false, mClock); resetZenModeHelper(); mAudioAttributes = new AudioAttributes.Builder() Loading Loading @@ -680,7 +709,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_oldXml_migrates() throws Exception { mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, /* showReviewPermissionsNotification= */ true); /* showReviewPermissionsNotification= */ true, mClock); String xml = "<ranking version=\"2\">\n" + "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1 Loading Loading @@ -816,7 +845,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception { mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, /* showReviewPermissionsNotification= */ true); /* showReviewPermissionsNotification= */ true, mClock); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" Loading Loading @@ -875,7 +904,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_permissionNotificationOff() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, /* showReviewPermissionsNotification= */ false); /* showReviewPermissionsNotification= */ false, mClock); String xml = "<ranking version=\"3\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" Loading Loading @@ -934,7 +963,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, /* showReviewPermissionsNotification= */ true); /* showReviewPermissionsNotification= */ true, mClock); String xml = "<ranking version=\"4\">\n" + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" Loading Loading @@ -1010,7 +1039,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(1234); final ApplicationInfo app = new ApplicationInfo(); app.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1; when(mPm.getApplicationInfoAsUser(eq("something"), anyInt(), anyInt())).thenReturn(app); when(mPm.getApplicationInfoAsUser( eq("something"), anyInt(), eq(USER_SYSTEM))).thenReturn(app); mXmlHelper.onPackagesChanged(false, 0, new String[] {"something"}, new int[] {1234}); Loading Loading @@ -1452,6 +1482,149 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(actualChannel.isSoundRestored()); } @Test @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA) public void testRestoreXml_delayedRestore() throws Exception { // simulate package not installed when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow( new PackageManager.NameNotFoundException()); when(mClock.millis()).thenReturn(System.currentTimeMillis()); String id = "id"; String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n" + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" " + "show_badge=\"true\" />\n" + "</package>\n" + "</ranking>\n"; loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); // settings are not available with real uid because pkg is not installed assertThat(mXmlHelper.getNotificationChannel(PKG_R, UID_P, id, false)).isNull(); // but the settings are in memory with unknown_uid assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id, false)).isNotNull(); // package is "installed" when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P); // Trigger 2nd restore pass mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R}, new int[]{UID_P}); NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id, false); assertThat(channel.getImportance()).isEqualTo(2); assertThat(channel.canShowBadge()).isTrue(); assertThat(channel.canBypassDnd()).isFalse(); // removed from 'pending install' set assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id,false)).isNull(); } @Test @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA) public void testRestoreXml_delayedRestore_afterReboot() throws Exception { // load restore data ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(UID_R, PKG_R), new Pair<>(true, false)); when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM)) .thenReturn(appPermissions); // simulate package not installed when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow( new PackageManager.NameNotFoundException()); when(mClock.millis()).thenReturn(System.currentTimeMillis()); String id = "id"; String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n" + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" " + "show_badge=\"true\" />\n" + "</package>\n" + "</ranking>\n"; loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); // simulate write to disk TypedXmlSerializer serializer = Xml.newFastSerializer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); serializer.startDocument(null, true); mXmlHelper.writeXml(serializer, false, USER_SYSTEM); serializer.endDocument(); serializer.flush(); // simulate load after reboot mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false, mClock); loadByteArrayXml(baos.toByteArray(), false, USER_SYSTEM); // Trigger 2nd restore pass when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UID_P); mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R}, new int[]{UID_P}); NotificationChannel channel = mXmlHelper.getNotificationChannel(PKG_R, UID_P, id, false); assertThat(channel.getImportance()).isEqualTo(2); assertThat(channel.canShowBadge()).isTrue(); assertThat(channel.canBypassDnd()).isFalse(); } @Test @EnableFlags(Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA) public void testRestoreXml_delayedRestore_packageMissingAfterTwoDays() throws Exception { // load restore data ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>(); appPermissions.put(new Pair<>(UID_R, PKG_R), new Pair<>(true, false)); when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM)) .thenReturn(appPermissions); // simulate package not installed when(mPm.getPackageUidAsUser(PKG_R, USER_SYSTEM)).thenReturn(UNKNOWN_UID); when(mPm.getApplicationInfoAsUser(eq(PKG_R), anyInt(), anyInt())).thenThrow( new PackageManager.NameNotFoundException()); String id = "id"; String xml = "<ranking version=\"1\">\n" + "<package name=\"" + PKG_R + "\" show_badge=\"true\">\n" + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" " + "show_badge=\"true\" />\n" + "</package>\n" + "</ranking>\n"; loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); // simulate write to disk TypedXmlSerializer serializer = Xml.newFastSerializer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); serializer.setOutput(new BufferedOutputStream(baos), "utf-8"); serializer.startDocument(null, true); mXmlHelper.writeXml(serializer, false, USER_SYSTEM); serializer.endDocument(); serializer.flush(); // advance time by 2 days when(mClock.millis()).thenReturn( Duration.ofDays(2).toMillis() + System.currentTimeMillis()); // simulate load after reboot mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false, mClock); loadByteArrayXml(xml.getBytes(), false, USER_SYSTEM); // Trigger 2nd restore pass mXmlHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_R}, new int[]{UID_P}); // verify the 2nd restore pass failed because the restore data had been removed assertThat(mXmlHelper.getNotificationChannel(PKG_R, UNKNOWN_UID, id, false)).isNull(); } /** * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to Loading Loading @@ -1520,10 +1693,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false); false, mClock); mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, false); false, mClock); NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); Loading