Persist incomplete pkg restore data for longer
Store the data for packages that aren't installed yet for 2 days +
reboot instead of just until reboot.
This will make restore more reliable.
Test: PreferencesHelperTest
Test: manual restore with reboot after SUW finishes
Flag: com.android.server.notification.persist_incomplete_restore_data
DEV
Bug: 334999659
Change-Id: I4f4d9d5da51aaf788e9c8735532acbd5f42b4f2b
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b48cad2..ebea05d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2514,7 +2514,8 @@
mNotificationChannelLogger,
mAppOps,
mUserProfiles,
- mShowReviewPermissionsNotification);
+ mShowReviewPermissionsNotification,
+ Clock.systemUTC());
mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
mZenModeHelper, mUsageStats, extractorNames, mPlatformCompat);
mSnoozeHelper = snoozeHelper;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 461bd9c..1f2ad07e 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -93,6 +93,8 @@
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;
@@ -113,6 +115,8 @@
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;
@@ -149,6 +153,8 @@
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;
@@ -208,11 +214,13 @@
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;
@@ -225,7 +233,7 @@
mShowReviewPermissionsNotification = showReviewPermissionsNotification;
mIsMediaNotificationFilteringEnabled = context.getResources()
.getBoolean(R.bool.config_quickSettingsShowMediaPlayer);
-
+ mClock = clock;
XML_VERSION = 4;
updateBadgingEnabled();
@@ -309,7 +317,7 @@
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);
@@ -463,12 +471,12 @@
// 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)
@@ -483,6 +491,11 @@
r.visibility = visibility;
r.showBadge = showBadge;
r.bubblePreference = bubblePreference;
+ if (Flags.persistIncompleteRestoreData()) {
+ if (r.uid == UNKNOWN_UID) {
+ r.creationTime = creationTime;
+ }
+ }
try {
createDefaultChannelIfNeededLocked(r);
@@ -496,6 +509,12 @@
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;
}
@@ -590,70 +609,16 @@
if (forBackup && UserHandle.getUserId(r.uid) != userId) {
continue;
}
- out.startTag(null, TAG_PACKAGE);
- out.attribute(null, ATT_NAME, r.pkg);
- if (!notifPermissions.isEmpty()) {
- Pair<Integer, String> app = new Pair(r.uid, r.pkg);
- final Pair<Boolean, Boolean> permission = notifPermissions.get(app);
- out.attributeInt(null, ATT_IMPORTANCE,
- permission != null && permission.first ? IMPORTANCE_DEFAULT
- : IMPORTANCE_NONE);
- notifPermissions.remove(app);
- } else {
- if (r.importance != DEFAULT_IMPORTANCE) {
- out.attributeInt(null, ATT_IMPORTANCE, r.importance);
- }
+ 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);
}
- if (r.priority != DEFAULT_PRIORITY) {
- out.attributeInt(null, ATT_PRIORITY, r.priority);
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- out.attributeInt(null, ATT_VISIBILITY, r.visibility);
- }
- if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
- out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference);
- }
- out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge);
- out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS,
- r.lockedAppFields);
- out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
- r.hasSentInvalidMessage);
- out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE,
- r.hasSentValidMessage);
- out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
- r.userDemotedMsgApp);
- out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
-
- if (!forBackup) {
- out.attributeInt(null, ATT_UID, r.uid);
- }
-
- if (r.delegate != null) {
- out.startTag(null, TAG_DELEGATE);
-
- out.attribute(null, ATT_NAME, r.delegate.mPkg);
- out.attributeInt(null, ATT_UID, r.delegate.mUid);
- if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
- out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
- }
- out.endTag(null, TAG_DELEGATE);
- }
-
- for (NotificationChannelGroup group : r.groups.values()) {
- group.writeXml(out);
- }
-
- for (NotificationChannel channel : r.channels.values()) {
- if (forBackup) {
- if (!channel.isDeleted()) {
- channel.writeXmlForBackup(out, mContext);
- }
- } else {
- channel.writeXml(out);
- }
- }
-
- out.endTag(null, TAG_PACKAGE);
}
}
// Some apps have permissions set but don't have expanded notification settings
@@ -669,6 +634,80 @@
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()) {
+ Pair<Integer, String> app = new Pair(r.uid, r.pkg);
+ final Pair<Boolean, Boolean> permission = notifPermissions.get(app);
+ out.attributeInt(null, ATT_IMPORTANCE,
+ permission != null && permission.first ? IMPORTANCE_DEFAULT
+ : IMPORTANCE_NONE);
+ notifPermissions.remove(app);
+ } else {
+ if (r.importance != DEFAULT_IMPORTANCE) {
+ out.attributeInt(null, ATT_IMPORTANCE, r.importance);
+ }
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ out.attributeInt(null, ATT_PRIORITY, r.priority);
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ out.attributeInt(null, ATT_VISIBILITY, r.visibility);
+ }
+ if (r.bubblePreference != DEFAULT_BUBBLE_PREFERENCE) {
+ out.attributeInt(null, ATT_ALLOW_BUBBLE, r.bubblePreference);
+ }
+ out.attributeBoolean(null, ATT_SHOW_BADGE, r.showBadge);
+ out.attributeInt(null, ATT_APP_USER_LOCKED_FIELDS,
+ r.lockedAppFields);
+ out.attributeBoolean(null, ATT_SENT_INVALID_MESSAGE,
+ r.hasSentInvalidMessage);
+ out.attributeBoolean(null, ATT_SENT_VALID_MESSAGE,
+ r.hasSentValidMessage);
+ out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
+ 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);
+ }
+
+ if (r.delegate != null) {
+ out.startTag(null, TAG_DELEGATE);
+
+ out.attribute(null, ATT_NAME, r.delegate.mPkg);
+ out.attributeInt(null, ATT_UID, r.delegate.mUid);
+ if (r.delegate.mEnabled != Delegate.DEFAULT_ENABLED) {
+ out.attributeBoolean(null, ATT_ENABLED, r.delegate.mEnabled);
+ }
+ out.endTag(null, TAG_DELEGATE);
+ }
+
+ for (NotificationChannelGroup group : r.groups.values()) {
+ group.writeXml(out);
+ }
+
+ for (NotificationChannel channel : r.channels.values()) {
+ if (forBackup) {
+ if (!channel.isDeleted()) {
+ channel.writeXmlForBackup(out, mContext);
+ }
+ } else {
+ channel.writeXml(out);
+ }
+ }
+
+ out.endTag(null, TAG_PACKAGE);
+ }
+
/**
* Sets whether bubbles are allowed.
*
@@ -2906,6 +2945,7 @@
boolean hasSentValidBubble = false;
boolean migrateToPm = false;
+ long creationTime;
Delegate delegate = null;
ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 077ed5a..28598711 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -95,3 +95,9 @@
bug: "331967355"
}
+flag {
+ name: "persist_incomplete_restore_data"
+ namespace: "systemui"
+ description: "Stores restore data for not-yet-installed pkgs for 48 hours"
+ bug: "334999659"
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 5033a380..9a58594 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -46,10 +46,13 @@
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;
@@ -110,6 +113,9 @@
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;
@@ -140,6 +146,9 @@
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;
@@ -148,6 +157,7 @@
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;
@@ -160,6 +170,8 @@
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;
@@ -171,7 +183,7 @@
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);
@@ -212,6 +224,22 @@
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);
@@ -326,13 +354,14 @@
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()
@@ -680,7 +709,7 @@
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
@@ -816,7 +845,7 @@
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"
@@ -875,7 +904,7 @@
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"
@@ -934,7 +963,7 @@
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"
@@ -1010,7 +1039,8 @@
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});
@@ -1452,6 +1482,149 @@
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
@@ -1520,10 +1693,10 @@
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);