Merge "Add ability to delete individual history items"

This commit is contained in:
Julia Reynolds
2020-02-08 01:15:39 +00:00
committed by Android (Google) Code Review
8 changed files with 193 additions and 7 deletions

View File

@@ -113,6 +113,7 @@ interface INotificationManager
int getAppsBypassingDndCount(int uid);
ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId);
boolean isPackagePaused(String pkg);
void deleteNotificationHistoryItem(String pkg, int uid, long postedTime);
void silenceNotificationSound();

View File

@@ -345,6 +345,26 @@ public final class NotificationHistory implements Parcelable {
poolStringsFromNotifications();
}
/**
* Removes an individual historical notification and regenerates the string pool
*/
public boolean removeNotificationFromWrite(String packageName, long postedTime) {
boolean removed = false;
for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) {
HistoricalNotification hn = mNotificationsToWrite.get(i);
if (packageName.equals(hn.getPackage())
&& postedTime == hn.getPostedTimeMs()) {
removed = true;
mNotificationsToWrite.remove(i);
}
}
if (removed) {
poolStringsFromNotifications();
}
return removed;
}
/**
* Gets pooled strings in order to write them to disk
*/

View File

@@ -233,6 +233,40 @@ public class NotificationHistoryTest {
.containsExactlyElementsIn(postRemoveExpectedEntries);
}
@Test
public void testRemoveNotificationFromWrite() {
NotificationHistory history = new NotificationHistory();
List<HistoricalNotification> postRemoveExpectedEntries = new ArrayList<>();
List<String> postRemoveExpectedStrings = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
HistoricalNotification n = getHistoricalNotification("pkg", i);
if (987654323 != n.getPostedTimeMs()) {
postRemoveExpectedStrings.add(n.getPackage());
postRemoveExpectedStrings.add(n.getChannelName());
postRemoveExpectedStrings.add(n.getChannelId());
postRemoveExpectedEntries.add(n);
}
history.addNotificationToWrite(n);
}
history.poolStringsFromNotifications();
assertThat(history.getNotificationsToWrite().size()).isEqualTo(10);
// 1 package name and 10 unique channel names and ids
assertThat(history.getPooledStringsToWrite().length).isEqualTo(21);
history.removeNotificationFromWrite("pkg", 987654323);
// 1 package names and 9 * 2 unique channel names and ids
assertThat(history.getPooledStringsToWrite().length).isEqualTo(19);
assertThat(history.getNotificationsToWrite())
.containsExactlyElementsIn(postRemoveExpectedEntries);
}
@Test
public void testParceling() {
NotificationHistory history = new NotificationHistory();

View File

@@ -170,6 +170,11 @@ public class NotificationHistoryDatabase {
mFileWriteHandler.post(rpr);
}
public void deleteNotificationHistoryItem(String pkg, long postedTime) {
RemoveNotificationRunnable rnr = new RemoveNotificationRunnable(pkg, postedTime);
mFileWriteHandler.post(rnr);
}
public void addNotification(final HistoricalNotification notification) {
synchronized (mLock) {
mBuffer.addNewNotificationToWrite(notification);
@@ -367,6 +372,49 @@ public class NotificationHistoryDatabase {
}
}
final class RemoveNotificationRunnable implements Runnable {
private String mPkg;
private long mPostedTime;
private NotificationHistory mNotificationHistory;
public RemoveNotificationRunnable(String pkg, long postedTime) {
mPkg = pkg;
mPostedTime = postedTime;
}
@VisibleForTesting
void setNotificationHistory(NotificationHistory nh) {
mNotificationHistory = nh;
}
@Override
public void run() {
if (DEBUG) Slog.d(TAG, "RemovePackageRunnable");
synchronized (mLock) {
// Remove from pending history
mBuffer.removeNotificationFromWrite(mPkg, mPostedTime);
Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator();
while (historyFileItr.hasNext()) {
final AtomicFile af = historyFileItr.next();
try {
NotificationHistory notificationHistory = mNotificationHistory != null
? mNotificationHistory
: new NotificationHistory();
readLocked(af, notificationHistory,
new NotificationHistoryFilter.Builder().build());
if(notificationHistory.removeNotificationFromWrite(mPkg, mPostedTime)) {
writeLocked(af, notificationHistory);
}
} catch (Exception e) {
Slog.e(TAG, "Cannot clean up file on notification removal "
+ af.getBaseFile().getName(), e);
}
}
}
}
}
public static final class NotificationHistoryFileAttrProvider implements
NotificationHistoryDatabase.FileAttrProvider {
final static String TAG = "NotifHistoryFileDate";

View File

@@ -130,7 +130,7 @@ public class NotificationHistoryManager {
}
}
public void onPackageRemoved(int userId, String packageName) {
public void onPackageRemoved(@UserIdInt int userId, String packageName) {
synchronized (mLock) {
if (!mUserUnlockedStates.get(userId, false)) {
if (mHistoryEnabled.get(userId, false)) {
@@ -150,6 +150,22 @@ public class NotificationHistoryManager {
}
}
public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) {
synchronized (mLock) {
int userId = UserHandle.getUserId(uid);
final NotificationHistoryDatabase userHistory =
getUserHistoryAndInitializeIfNeededLocked(userId);
// TODO: it shouldn't be possible to delete a notification entry while the user is
// locked but we should handle it
if (userHistory == null) {
Slog.w(TAG, "Attempted to remove notif for locked/gone/disabled user "
+ userId);
return;
}
userHistory.deleteNotificationHistoryItem(pkg, postedTime);
}
}
// TODO: wire this up to AMS when power button is long pressed
public void triggerWriteToDisk() {
synchronized (mLock) {

View File

@@ -2780,7 +2780,7 @@ public class NotificationManagerService extends SystemService {
if (!isSystemToast) {
int count = 0;
final int N = mToastQueue.size();
for (int i=0; i<N; i++) {
for (int i = 0; i < N; i++) {
final ToastRecord r = mToastQueue.get(i);
if (r.pkg.equals(pkg)) {
count++;
@@ -2820,7 +2820,7 @@ public class NotificationManagerService extends SystemService {
if (pkg == null || token == null) {
Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " token=" + token);
return ;
return;
}
synchronized (mToastQueue) {
@@ -2933,14 +2933,14 @@ public class NotificationManagerService extends SystemService {
/**
* Updates the enabled state for notifications for the given package (and uid).
* Additionally, this method marks the app importance as locked by the user, which means
* Additionally, this method marks the app importance as locked by the user, which
* means
* that notifications from the app will <b>not</b> be considered for showing a
* blocking helper.
*
* @param pkg package that owns the notifications to update
* @param uid uid of the app providing notifications
* @param pkg package that owns the notifications to update
* @param uid uid of the app providing notifications
* @param enabled whether notifications should be enabled for the app
*
* @see #setNotificationsEnabledForPackage(String, int, boolean)
*/
@Override
@@ -3030,6 +3030,12 @@ public class NotificationManagerService extends SystemService {
mListeners.onStatusBarIconsBehaviorChanged(hide);
}
@Override
public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) {
checkCallerIsSystem();
mHistoryManager.deleteNotificationHistoryItem(pkg, uid, postedTime);
}
@Override
public int getPackageImportance(String pkg) {
checkCallerIsSystemOrSameApp(pkg);

View File

@@ -44,6 +44,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.internal.matchers.Not;
import java.io.File;
import java.util.ArrayList;
@@ -239,6 +240,52 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
verify(af2, never()).openRead();
}
@Test
public void testRemoveNotificationRunnable() throws Exception {
NotificationHistory nh = mock(NotificationHistory.class);
NotificationHistoryDatabase.RemoveNotificationRunnable rnr =
mDataBase.new RemoveNotificationRunnable("pkg", 123);
rnr.setNotificationHistory(nh);
AtomicFile af = mock(AtomicFile.class);
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
mDataBase.mHistoryFiles.addLast(af);
when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(true);
mDataBase.mBuffer = mock(NotificationHistory.class);
rnr.run();
verify(mDataBase.mBuffer).removeNotificationFromWrite("pkg", 123);
verify(af).openRead();
verify(nh).removeNotificationFromWrite("pkg", 123);
verify(af).startWrite();
}
@Test
public void testRemoveNotificationRunnable_noChanges() throws Exception {
NotificationHistory nh = mock(NotificationHistory.class);
NotificationHistoryDatabase.RemoveNotificationRunnable rnr =
mDataBase.new RemoveNotificationRunnable("pkg", 123);
rnr.setNotificationHistory(nh);
AtomicFile af = mock(AtomicFile.class);
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
mDataBase.mHistoryFiles.addLast(af);
when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(false);
mDataBase.mBuffer = mock(NotificationHistory.class);
rnr.run();
verify(mDataBase.mBuffer).removeNotificationFromWrite("pkg", 123);
verify(af).openRead();
verify(nh).removeNotificationFromWrite("pkg", 123);
verify(af, never()).startWrite();
}
private class TestFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider {
public Map<File, Long> creationDates = new HashMap<>();

View File

@@ -288,6 +288,20 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase {
verify(userHistoryAll, never()).onPackageRemoved(pkg);
}
@Test
public void testDeleteNotificationHistoryItem_userUnlocked() {
String pkg = "pkg";
long time = 235;
NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class);
mHistoryManager.onUserUnlocked(USER_SYSTEM);
mHistoryManager.replaceNotificationHistoryDatabase(USER_SYSTEM, userHistory);
mHistoryManager.deleteNotificationHistoryItem(pkg, 1, time);
verify(userHistory, times(1)).deleteNotificationHistoryItem(pkg, time);
}
@Test
public void testTriggerWriteToDisk() {
NotificationHistoryDatabase userHistorySystem = mock(NotificationHistoryDatabase.class);