Merge "Uncache the shortcuts when Android system shuts down if the notification settings are not changed" into rvc-dev

This commit is contained in:
Danning Chen
2020-05-02 00:57:43 +00:00
committed by Android (Google) Code Review
5 changed files with 163 additions and 67 deletions

View File

@@ -62,8 +62,6 @@ public class ConversationInfo {
private static final int FLAG_DEMOTED = 1 << 6;
private static final int FLAG_NOTIFICATION_SETTING_CHANGED = 1 << 7;
@IntDef(flag = true, prefix = {"FLAG_"}, value = {
FLAG_IMPORTANT,
FLAG_NOTIFICATION_SILENCED,
@@ -72,7 +70,6 @@ public class ConversationInfo {
FLAG_PERSON_BOT,
FLAG_CONTACT_STARRED,
FLAG_DEMOTED,
FLAG_NOTIFICATION_SETTING_CHANGED,
})
@Retention(RetentionPolicy.SOURCE)
private @interface ConversationFlags {
@@ -188,11 +185,6 @@ public class ConversationInfo {
return hasConversationFlags(FLAG_CONTACT_STARRED);
}
/** Whether the conversation's notification setting has ever been changed by the user. */
boolean isNotificationSettingChanged() {
return hasConversationFlags(FLAG_NOTIFICATION_SETTING_CHANGED);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
@@ -499,10 +491,6 @@ public class ConversationInfo {
return setConversationFlag(FLAG_CONTACT_STARRED, value);
}
Builder setNotificationSettingChanged(boolean value) {
return setConversationFlag(FLAG_NOTIFICATION_SETTING_CHANGED, value);
}
private Builder setConversationFlag(@ConversationFlags int flags, boolean value) {
if (value) {
return addConversationFlags(flags);

View File

@@ -16,8 +16,6 @@
package com.android.server.people.data;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -60,9 +58,11 @@ import android.telecom.TelecomManager;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.ChooserActivity;
import com.android.internal.content.PackageMonitor;
@@ -106,8 +106,7 @@ public class DataManager {
private final SparseArray<BroadcastReceiver> mBroadcastReceivers = new SparseArray<>();
private final SparseArray<ContentObserver> mContactsContentObservers = new SparseArray<>();
private final SparseArray<ScheduledFuture<?>> mUsageStatsQueryFutures = new SparseArray<>();
private final SparseArray<NotificationListenerService> mNotificationListeners =
new SparseArray<>();
private final SparseArray<NotificationListener> mNotificationListeners = new SparseArray<>();
private final SparseArray<PackageMonitor> mPackageMonitors = new SparseArray<>();
private ContentObserver mCallLogContentObserver;
private ContentObserver mMmsSmsContentObserver;
@@ -272,6 +271,7 @@ public class DataManager {
}
pruneUninstalledPackageData(userData);
final NotificationListener notificationListener = mNotificationListeners.get(userId);
userData.forAllPackages(packageData -> {
if (signal.isCanceled()) {
return;
@@ -284,6 +284,20 @@ public class DataManager {
packageData.getEventStore().deleteEventHistories(EventStore.CATEGORY_SMS);
}
packageData.pruneOrphanEvents();
if (notificationListener != null) {
String packageName = packageData.getPackageName();
packageData.forAllConversations(conversationInfo -> {
if (conversationInfo.isShortcutCached()
&& conversationInfo.getNotificationChannelId() == null
&& !notificationListener.hasActiveNotifications(
packageName, conversationInfo.getShortcutId())) {
mShortcutServiceInternal.uncacheShortcuts(userId,
mContext.getPackageName(), packageName,
Collections.singletonList(conversationInfo.getShortcutId()),
userId);
}
});
}
});
}
@@ -337,7 +351,7 @@ public class DataManager {
Contacts.CONTENT_URI, /* notifyForDescendants= */ true,
contactsContentObserver, userId);
NotificationListener notificationListener = new NotificationListener();
NotificationListener notificationListener = new NotificationListener(userId);
mNotificationListeners.put(userId, notificationListener);
try {
notificationListener.registerAsSystemService(mContext,
@@ -753,14 +767,27 @@ public class DataManager {
/** Listener for the notifications and their settings changes. */
private class NotificationListener extends NotificationListenerService {
// Conversation shortcut ID -> Number of active notifications
private final Map<String, Integer> mActiveNotifCounts = new ArrayMap<>();
private final int mUserId;
// Conversation package name + shortcut ID -> Number of active notifications
@GuardedBy("this")
private final Map<Pair<String, String>, Integer> mActiveNotifCounts = new ArrayMap<>();
private NotificationListener(int userId) {
mUserId = userId;
}
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
if (sbn.getUser().getIdentifier() != mUserId) {
return;
}
String shortcutId = sbn.getNotification().getShortcutId();
PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> {
mActiveNotifCounts.merge(shortcutId, 1, Integer::sum);
synchronized (this) {
mActiveNotifCounts.merge(
Pair.create(sbn.getPackageName(), shortcutId), 1, Integer::sum);
}
});
if (packageData != null) {
@@ -771,26 +798,32 @@ public class DataManager {
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
int reason) {
public synchronized void onNotificationRemoved(StatusBarNotification sbn,
RankingMap rankingMap, int reason) {
if (sbn.getUser().getIdentifier() != mUserId) {
return;
}
String shortcutId = sbn.getNotification().getShortcutId();
PackageData packageData = getPackageIfConversationExists(sbn, conversationInfo -> {
int count = mActiveNotifCounts.getOrDefault(shortcutId, 0) - 1;
if (count <= 0) {
mActiveNotifCounts.remove(sbn.getNotification().getShortcutId());
// The shortcut was cached by Notification Manager synchronously when the
// associated notification was posted. Uncache it here when all the associated
// notifications are removed.
if (conversationInfo.isShortcutCached()
&& !conversationInfo.isNotificationSettingChanged()) {
int userId = sbn.getUser().getIdentifier();
mShortcutServiceInternal.uncacheShortcuts(userId,
mContext.getPackageName(), sbn.getPackageName(),
Collections.singletonList(conversationInfo.getShortcutId()),
userId);
Pair<String, String> conversationKey =
Pair.create(sbn.getPackageName(), shortcutId);
synchronized (this) {
int count = mActiveNotifCounts.getOrDefault(conversationKey, 0) - 1;
if (count <= 0) {
mActiveNotifCounts.remove(conversationKey);
// The shortcut was cached by Notification Manager synchronously when the
// associated notification was posted. Uncache it here when all the
// associated notifications are removed.
if (conversationInfo.isShortcutCached()
&& conversationInfo.getNotificationChannelId() == null) {
mShortcutServiceInternal.uncacheShortcuts(mUserId,
mContext.getPackageName(), sbn.getPackageName(),
Collections.singletonList(conversationInfo.getShortcutId()),
mUserId);
}
} else {
mActiveNotifCounts.put(conversationKey, count);
}
} else {
mActiveNotifCounts.put(shortcutId, count);
}
});
@@ -806,6 +839,9 @@ public class DataManager {
@Override
public void onNotificationChannelModified(String pkg, UserHandle user,
NotificationChannel channel, int modificationType) {
if (user.getIdentifier() != mUserId) {
return;
}
PackageData packageData = getPackage(pkg, user.getIdentifier());
String shortcutId = channel.getConversationId();
if (packageData == null || shortcutId == null) {
@@ -816,16 +852,7 @@ public class DataManager {
if (conversationInfo == null) {
return;
}
boolean isNotificationSettingChanged =
conversationInfo.isImportant() != channel.isImportantConversation()
|| conversationInfo.isDemoted() != channel.isDemoted()
|| channel.hasUserSetImportance()
|| (channel.getUserLockedFields() & USER_LOCKED_ALLOW_BUBBLE) != 0;
ConversationInfo.Builder builder = new ConversationInfo.Builder(conversationInfo);
if (modificationType == NOTIFICATION_CHANNEL_OR_GROUP_UPDATED
&& isNotificationSettingChanged) {
builder.setNotificationSettingChanged(true);
}
switch (modificationType) {
case NOTIFICATION_CHANNEL_OR_GROUP_ADDED:
case NOTIFICATION_CHANNEL_OR_GROUP_UPDATED:
@@ -848,6 +875,28 @@ public class DataManager {
}
conversationStore.addOrUpdate(builder.build());
}
synchronized void cleanupCachedShortcuts() {
for (Pair<String, String> conversationKey : mActiveNotifCounts.keySet()) {
String packageName = conversationKey.first;
String shortcutId = conversationKey.second;
PackageData packageData = getPackage(packageName, mUserId);
ConversationInfo conversationInfo =
packageData != null ? packageData.getConversationInfo(shortcutId) : null;
if (conversationInfo != null
&& conversationInfo.isShortcutCached()
&& conversationInfo.getNotificationChannelId() == null) {
mShortcutServiceInternal.uncacheShortcuts(mUserId,
mContext.getPackageName(), packageName,
Collections.singletonList(shortcutId),
mUserId);
}
}
}
synchronized boolean hasActiveNotifications(String packageName, String shortcutId) {
return mActiveNotifCounts.containsKey(Pair.create(packageName, shortcutId));
}
}
/**
@@ -917,7 +966,16 @@ public class DataManager {
@Override
public void onReceive(Context context, Intent intent) {
forAllUnlockedUsers(userData -> userData.forAllPackages(PackageData::saveToDisk));
forAllUnlockedUsers(userData -> {
NotificationListener listener = mNotificationListeners.get(userData.getUserId());
// Clean up the cached shortcuts because all the notifications are cleared after
// system shutdown. The associated shortcuts need to be uncached to keep in sync
// unless the settings are changed by the user.
if (listener != null) {
listener.cleanupCachedShortcuts();
}
userData.forAllPackages(PackageData::saveToDisk);
});
}
}

View File

@@ -54,7 +54,6 @@ public final class ConversationInfoTest {
.setPersonImportant(true)
.setPersonBot(true)
.setContactStarred(true)
.setNotificationSettingChanged(true)
.build();
assertEquals(SHORTCUT_ID, conversationInfo.getShortcutId());
@@ -71,7 +70,6 @@ public final class ConversationInfoTest {
assertTrue(conversationInfo.isPersonImportant());
assertTrue(conversationInfo.isPersonBot());
assertTrue(conversationInfo.isContactStarred());
assertTrue(conversationInfo.isNotificationSettingChanged());
}
@Test
@@ -94,7 +92,6 @@ public final class ConversationInfoTest {
assertFalse(conversationInfo.isPersonImportant());
assertFalse(conversationInfo.isPersonBot());
assertFalse(conversationInfo.isContactStarred());
assertFalse(conversationInfo.isNotificationSettingChanged());
}
@Test
@@ -112,7 +109,6 @@ public final class ConversationInfoTest {
.setPersonImportant(true)
.setPersonBot(true)
.setContactStarred(true)
.setNotificationSettingChanged(true)
.build();
ConversationInfo destination = new ConversationInfo.Builder(source)
@@ -132,6 +128,5 @@ public final class ConversationInfoTest {
assertTrue(destination.isPersonImportant());
assertTrue(destination.isPersonBot());
assertFalse(destination.isContactStarred());
assertTrue(destination.isNotificationSettingChanged());
}
}

View File

@@ -49,6 +49,7 @@ import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -95,8 +96,6 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -125,18 +124,19 @@ public final class DataManagerTest {
@Mock private TelephonyManager mTelephonyManager;
@Mock private TelecomManager mTelecomManager;
@Mock private ContentResolver mContentResolver;
@Mock private ScheduledExecutorService mExecutorService;
@Mock private JobScheduler mJobScheduler;
@Mock private ScheduledFuture mScheduledFuture;
@Mock private StatusBarNotification mStatusBarNotification;
@Mock private Notification mNotification;
@Captor private ArgumentCaptor<ShortcutChangeCallback> mShortcutChangeCallbackCaptor;
@Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
private ScheduledExecutorService mExecutorService;
private NotificationChannel mNotificationChannel;
private DataManager mDataManager;
private CancellationSignal mCancellationSignal;
private ShortcutChangeCallback mShortcutChangeCallback;
private BroadcastReceiver mShutdownBroadcastReceiver;
private TestInjector mInjector;
@Before
@@ -182,13 +182,7 @@ public final class DataManagerTest {
when(mContext.getSystemServiceName(JobScheduler.class)).thenReturn(
Context.JOB_SCHEDULER_SERVICE);
when(mExecutorService.scheduleAtFixedRate(any(Runnable.class), anyLong(), anyLong(), any(
TimeUnit.class))).thenReturn(mScheduledFuture);
doAnswer(ans -> {
Runnable runnable = (Runnable) ans.getArguments()[0];
runnable.run();
return null;
}).when(mExecutorService).execute(any(Runnable.class));
mExecutorService = new MockScheduledExecutorService();
when(mUserManager.getEnabledProfiles(USER_ID_PRIMARY))
.thenReturn(Arrays.asList(
@@ -221,6 +215,9 @@ public final class DataManagerTest {
verify(mShortcutServiceInternal).addShortcutChangeCallback(
mShortcutChangeCallbackCaptor.capture());
mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(), any());
mShutdownBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
}
@After
@@ -459,7 +456,7 @@ public final class DataManagerTest {
}
@Test
public void testShortcutNotUncachedIfSettingChanged() {
public void testShortcutNotUncachedIfNotificationChannelCreated() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
@@ -473,7 +470,6 @@ public final class DataManagerTest {
shortcut.setCached();
mDataManager.addOrUpdateConversationInfo(shortcut);
mNotificationChannel.setImportantConversation(true);
listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -530,7 +526,6 @@ public final class DataManagerTest {
assertTrue(conversationInfo.isImportant());
assertFalse(conversationInfo.isNotificationSilenced());
assertFalse(conversationInfo.isDemoted());
assertTrue(conversationInfo.isNotificationSettingChanged());
}
@Test
@@ -562,6 +557,51 @@ public final class DataManagerTest {
assertFalse(conversationInfo.isDemoted());
}
@Test
public void testUncacheShortcutWhenShutdown() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
mDataManager.addOrUpdateConversationInfo(shortcut);
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
listenerService.onNotificationPosted(mStatusBarNotification);
shortcut.setCached();
mDataManager.addOrUpdateConversationInfo(shortcut);
mShutdownBroadcastReceiver.onReceive(mContext, new Intent());
verify(mShortcutServiceInternal).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
}
@Test
public void testDoNotUncacheShortcutWhenShutdownIfNotificationChannelCreated() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
mDataManager.addOrUpdateConversationInfo(shortcut);
NotificationListenerService listenerService =
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
listenerService.onNotificationPosted(mStatusBarNotification);
shortcut.setCached();
mDataManager.addOrUpdateConversationInfo(shortcut);
listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
mNotificationChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
mShutdownBroadcastReceiver.onReceive(mContext, new Intent());
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
}
@Test
public void testShortcutAddedOrUpdated() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
@@ -721,6 +761,22 @@ public final class DataManagerTest {
assertTrue(activeTimeSlots.isEmpty());
}
@Test
public void testPruneInactiveCachedShortcuts() {
mDataManager.onUserUnlocked(USER_ID_PRIMARY);
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
shortcut.setCached();
mDataManager.addOrUpdateConversationInfo(shortcut);
mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
verify(mShortcutServiceInternal).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
}
@Test
public void testBackupAndRestoration()
throws IntentFilter.MalformedMimeTypeException {

View File

@@ -96,7 +96,6 @@ class MockScheduledExecutorService implements ScheduledExecutorService {
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,
TimeUnit unit) {
Preconditions.checkState(unit == TimeUnit.MILLISECONDS);
return new MockScheduledFuture<>(command, period, unit);
}