Merge "Hook up notification history to NMS."

This commit is contained in:
TreeHugger Robot
2019-11-27 09:44:12 +00:00
committed by Android (Google) Code Review
7 changed files with 214 additions and 29 deletions

View File

@@ -21,6 +21,7 @@ import android.app.ITransientNotification;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationHistory;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Intent;
@@ -119,6 +120,8 @@ interface INotificationManager
@UnsupportedAppUsage
StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
NotificationHistory getNotificationHistory(String callingPkg);
void registerListener(in INotificationListener listener, in ComponentName component, int userid);
void unregisterListener(in INotificationListener listener, int userid);

View File

@@ -183,6 +183,7 @@ public class NotificationHistoryDatabase {
public NotificationHistory readNotificationHistory() {
synchronized (mLock) {
NotificationHistory notifications = new NotificationHistory();
notifications.addNotificationsToWrite(mBuffer);
for (AtomicFile file : mHistoryFiles) {
try {

View File

@@ -112,7 +112,7 @@ public class NotificationHistoryManager {
}
}
void onUserRemoved(@UserIdInt int userId) {
public void onUserRemoved(@UserIdInt int userId) {
synchronized (mLock) {
// Actual data deletion is handled by other parts of the system (the entire directory is
// removed) - we just need clean up our internal state for GC
@@ -122,7 +122,7 @@ public class NotificationHistoryManager {
}
}
void onPackageRemoved(int userId, String packageName) {
public void onPackageRemoved(int userId, String packageName) {
synchronized (mLock) {
if (!mUserUnlockedStates.get(userId, false)) {
if (mHistoryEnabled.get(userId, false)) {
@@ -142,7 +142,8 @@ public class NotificationHistoryManager {
}
}
void triggerWriteToDisk() {
// TODO: wire this up to AMS when power button is long pressed
public void triggerWriteToDisk() {
synchronized (mLock) {
final int userCount = mUserState.size();
for (int i = 0; i < userCount; i++) {
@@ -204,7 +205,7 @@ public class NotificationHistoryManager {
}
}
public boolean isHistoryEnabled(@UserIdInt int userId) {
boolean isHistoryEnabled(@UserIdInt int userId) {
synchronized (mLock) {
return mHistoryEnabled.get(userId);
}

View File

@@ -103,6 +103,8 @@ import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.WorkerThread;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
@@ -116,6 +118,8 @@ import android.app.IUriGrantsManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationHistory;
import android.app.NotificationHistory.HistoricalNotification;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
@@ -160,6 +164,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IDeviceIdleController;
@@ -477,12 +482,14 @@ public class NotificationManagerService extends SystemService {
private long mLastOverRateLogTime;
private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
private NotificationHistoryManager mHistoryManager;
private SnoozeHelper mSnoozeHelper;
private GroupHelper mGroupHelper;
private int mAutoGroupAtCount;
private boolean mIsTelevision;
private boolean mIsAutomotive;
private boolean mNotificationEffectsEnabledForAutomotive;
private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener;
private int mWarnRemoteViewsSizeBytes;
private int mStripRemoteViewsSizeBytes;
@@ -1547,6 +1554,7 @@ public class NotificationManagerService extends SystemService {
mListeners.onUserRemoved(userId);
mConditionProviders.onUserRemoved(userId);
mAssistants.onUserRemoved(userId);
mHistoryManager.onUserRemoved(userId);
handleSavePolicyFile();
} else if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
@@ -1775,8 +1783,8 @@ public class NotificationManagerService extends SystemService {
// TODO: All tests should use this init instead of the one-off setters above.
@VisibleForTesting
void init(Looper looper, RankingHandler rankingHandler, IPackageManager packageManager,
PackageManager packageManagerClient,
void init(WorkerHandler handler, RankingHandler rankingHandler,
IPackageManager packageManager, PackageManager packageManagerClient,
LightsManager lightsManager, NotificationListeners notificationListeners,
NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
@@ -1784,7 +1792,8 @@ public class NotificationManagerService extends SystemService {
ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm,
IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps,
UserManager userManager) {
UserManager userManager, NotificationHistoryManager historyManager) {
mHandler = handler;
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1810,7 +1819,6 @@ public class NotificationManagerService extends SystemService {
mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
mHandler = new WorkerHandler(looper);
mUiHandler = new Handler(UiThread.get().getLooper());
String[] extractorNames;
try {
@@ -1869,6 +1877,7 @@ public class NotificationManagerService extends SystemService {
extractorNames);
mSnoozeHelper = snoozeHelper;
mGroupHelper = groupHelper;
mHistoryManager = historyManager;
// This is a ManagedServices object that keeps track of the listeners.
mListeners = notificationListeners;
@@ -1966,7 +1975,9 @@ public class NotificationManagerService extends SystemService {
final File systemDir = new File(Environment.getDataDirectory(), "system");
mRankingThread.start();
init(Looper.myLooper(), new RankingHandlerWorker(mRankingThread.getLooper()),
WorkerHandler handler = new WorkerHandler(Looper.myLooper());
init(handler, new RankingHandlerWorker(mRankingThread.getLooper()),
AppGlobals.getPackageManager(), getContext().getPackageManager(),
getLocalService(LightsManager.class),
new NotificationListeners(AppGlobals.getPackageManager()),
@@ -1983,7 +1994,8 @@ public class NotificationManagerService extends SystemService {
UriGrantsManager.getService(),
LocalServices.getService(UriGrantsManagerInternal.class),
(AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE),
getContext().getSystemService(UserManager.class));
getContext().getSystemService(UserManager.class),
new NotificationHistoryManager(getContext(), handler));
// register for various Intents
IntentFilter filter = new IntentFilter();
@@ -2036,19 +2048,26 @@ public class NotificationManagerService extends SystemService {
}
private void registerDeviceConfigChange() {
mDeviceConfigChangedListener = properties -> {
if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())) {
return;
}
if (properties.getKeyset()
.contains(SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE)) {
mAssistants.allowAdjustmentType(Adjustment.KEY_IMPORTANCE);
mAssistants.resetDefaultAssistantsIfNecessary();
}
};
DeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_SYSTEMUI,
getContext().getMainExecutor(),
(properties) -> {
if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())) {
return;
}
if (properties.getKeyset()
.contains(SystemUiDeviceConfigFlags.NAS_DEFAULT_SERVICE)) {
mAssistants.allowAdjustmentType(Adjustment.KEY_IMPORTANCE);
mAssistants.resetDefaultAssistantsIfNecessary();
}
});
new HandlerExecutor(mHandler),
mDeviceConfigChangedListener);
}
void unregisterDeviceConfigChange() {
if (mDeviceConfigChangedListener != null) {
DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
}
}
private GroupHelper getGroupHelper() {
@@ -2134,10 +2153,21 @@ public class NotificationManagerService extends SystemService {
mListeners.onBootPhaseAppsCanStart();
mAssistants.onBootPhaseAppsCanStart();
mConditionProviders.onBootPhaseAppsCanStart();
mHistoryManager.onBootPhaseAppsCanStart();
registerDeviceConfigChange();
}
}
@Override
public void onUnlockUser(@NonNull UserInfo userInfo) {
mHandler.post(() -> mHistoryManager.onUserUnlocked(userInfo.id));
}
@Override
public void onStopUser(@NonNull UserInfo userInfo) {
mHandler.post(() -> mHistoryManager.onUserStopped(userInfo.id));
}
@GuardedBy("mNotificationLock")
private void updateListenerHintsLocked() {
final int hints = calculateHints();
@@ -2449,10 +2479,56 @@ public class NotificationManagerService extends SystemService {
mAppUsageStats.reportInterruptiveNotification(r.sbn.getPackageName(),
r.getChannel().getId(),
getRealUserId(r.sbn.getUserId()));
mHistoryManager.addNotification(new HistoricalNotification.Builder()
.setPackage(r.sbn.getPackageName())
.setUid(r.sbn.getUid())
.setChannelId(r.getChannel().getId())
.setChannelName(r.getChannel().getName().toString())
.setPostedTimeMs(r.sbn.getPostTime())
.setTitle(getHistoryTitle(r.getNotification()))
.setText(getHistoryText(
r.sbn.getPackageContext(getContext()), r.getNotification()))
.setIcon(r.getNotification().getSmallIcon())
.build());
r.setRecordedInterruption(true);
}
}
private String getHistoryTitle(Notification n) {
CharSequence title = null;
if (n.extras != null) {
title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
}
return title == null? null : String.valueOf(title);
}
/**
* Returns the appropriate substring for this notification based on the style of notification.
*/
private String getHistoryText(Context appContext, Notification n) {
CharSequence text = null;
if (n.extras != null) {
text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
Notification.Builder nb = Notification.Builder.recoverBuilder(appContext, n);
if (nb.getStyle() instanceof Notification.BigTextStyle) {
text = ((Notification.BigTextStyle) nb.getStyle()).getBigText();
} else if (nb.getStyle() instanceof Notification.MessagingStyle) {
Notification.MessagingStyle ms = (Notification.MessagingStyle) nb.getStyle();
final List<Notification.MessagingStyle.Message> messages = ms.getMessages();
if (messages != null && messages.size() > 0) {
text = messages.get(messages.size() - 1).getText();
}
}
if (TextUtils.isEmpty(text)) {
text = n.extras.getCharSequence(Notification.EXTRA_TEXT);
}
}
return text == null ? null : String.valueOf(text);
}
/**
* Report to usage stats that the user interacted with the notification.
* @param r notification record
@@ -3343,10 +3419,9 @@ public class NotificationManagerService extends SystemService {
/**
* System-only API for getting a list of recent (cleared, no longer shown) notifications.
*
* Requires ACCESS_NOTIFICATIONS which is signature|system.
*/
@Override
@RequiresPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
// enforce() will ensure the calling uid has the correct permission
getContext().enforceCallingOrSelfPermission(
@@ -3366,6 +3441,29 @@ public class NotificationManagerService extends SystemService {
return tmp;
}
/**
* System-only API for getting a list of historical notifications. May contain multiple days
* of notifications.
*/
@Override
@WorkerThread
@RequiresPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
public NotificationHistory getNotificationHistory(String callingPkg) {
// enforce() will ensure the calling uid has the correct permission
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NOTIFICATIONS,
"NotificationManagerService.getNotificationHistory");
int uid = Binder.getCallingUid();
// noteOp will check to make sure the callingPkg matches the uid
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
== AppOpsManager.MODE_ALLOWED) {
IntArray currentUserIds = mUserProfiles.getCurrentProfileIds();
return mHistoryManager.readNotificationHistory(currentUserIds.toArray());
}
return new NotificationHistory();
}
/**
* Register a listener binder directly with the notification manager.
*
@@ -6831,7 +6929,7 @@ public class NotificationManagerService extends SystemService {
}
}
private void handleOnPackageChanged(boolean removingPackage, int changeUserId,
void handleOnPackageChanged(boolean removingPackage, int changeUserId,
String[] pkgList, int[] uidList) {
boolean preferencesChanged = removingPackage;
mListeners.onPackagesChanged(removingPackage, pkgList, uidList);
@@ -6839,6 +6937,14 @@ public class NotificationManagerService extends SystemService {
mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList);
preferencesChanged |= mPreferencesHelper.onPackagesChanged(
removingPackage, changeUserId, pkgList, uidList);
if (removingPackage) {
int size = Math.min(pkgList.length, uidList.length);
for (int i = 0; i < size; i++) {
final String pkg = pkgList[i];
final int uid = uidList[i];
mHistoryManager.onPackageRemoved(UserHandle.getUserId(uid), pkg);
}
}
if (preferencesChanged) {
handleSavePolicyFile();
}

View File

@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AlarmManager;
import android.app.NotificationHistory;
import android.app.NotificationHistory.HistoricalNotification;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -198,6 +199,16 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
}
}
@Test
public void testReadNotificationHistory_readsBuffer() throws Exception {
HistoricalNotification hn = getHistoricalNotification(1);
mDataBase.addNotification(hn);
NotificationHistory nh = mDataBase.readNotificationHistory();
assertThat(nh.getNotificationsToWrite()).contains(hn);
}
@Test
public void testReadNotificationHistory_withNumFilterDoesNotReadExtraFiles() throws Exception {
AtomicFile af = mock(AtomicFile.class);

View File

@@ -218,6 +218,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Mock
RankingHandler mRankingHandler;
private static final int MAX_POST_DELAY = 1000;
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
@@ -246,6 +248,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mNotificationAssistantAccessGrantedCallback;
@Mock
UserManager mUm;
@Mock
NotificationHistoryManager mHistoryManager;
// Use a Testable subclass so we can simulate calls from the system without failing.
private static class TestableNotificationManagerService extends NotificationManagerService {
@@ -404,13 +408,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
mService.init(mTestableLooper.getLooper(), mRankingHandler,
mPackageManager, mPackageManagerClient, mockLightsManager,
mService.init(mService.new WorkerHandler(mTestableLooper.getLooper()),
mRankingHandler, mPackageManager, mPackageManagerClient, mockLightsManager,
mListeners, mAssistants, mConditionProviders,
mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager,
mGroupHelper, mAm, mAppUsageStats,
mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
mAppOpsManager, mUm);
mAppOpsManager, mUm, mHistoryManager);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mService.setAudioManager(mAudioManager);
@@ -430,6 +434,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void tearDown() throws Exception {
if (mFile != null) mFile.delete();
clearDeviceConfig();
mService.unregisterDeviceConfigChange();
InstrumentationRegistry.getInstrumentation()
.getUiAutomation().dropShellPermissionIdentity();
}
@@ -5947,4 +5952,61 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
// TODO: add tests for the rest of the non-empty cases
@Test
public void testOnUnlockUser() {
UserInfo ui = new UserInfo();
ui.id = 10;
mService.onUnlockUser(ui);
waitForIdle();
verify(mHistoryManager, timeout(MAX_POST_DELAY).times(1)).onUserUnlocked(ui.id);
}
@Test
public void testOnStopUser() {
UserInfo ui = new UserInfo();
ui.id = 10;
mService.onStopUser(ui);
waitForIdle();
verify(mHistoryManager, timeout(MAX_POST_DELAY).times(1)).onUserStopped(ui.id);
}
@Test
public void testOnBootPhase() {
mService.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
verify(mHistoryManager, never()).onBootPhaseAppsCanStart();
mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
verify(mHistoryManager, times(1)).onBootPhaseAppsCanStart();
}
@Test
public void testHandleOnPackageChanged() {
String[] pkgs = new String[] {PKG, PKG_N_MR1};
int[] uids = new int[] {mUid, UserHandle.PER_USER_RANGE + 1};
mService.handleOnPackageChanged(false, USER_SYSTEM, pkgs, uids);
verify(mHistoryManager, never()).onPackageRemoved(anyInt(), anyString());
mService.handleOnPackageChanged(true, USER_SYSTEM, pkgs, uids);
verify(mHistoryManager, times(1)).onPackageRemoved(UserHandle.getUserId(uids[0]), pkgs[0]);
verify(mHistoryManager, times(1)).onPackageRemoved(UserHandle.getUserId(uids[1]), pkgs[1]);
}
@Test
public void testNotificationHistory_addNoisyNotification() throws Exception {
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
null /* tvExtender */, false);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
verify(mHistoryManager, times(1)).addNotification(any());
}
}

View File

@@ -129,7 +129,8 @@ public class RoleObserverTest extends UiServiceTestCase {
mRoleObserver = mService.new RoleObserver(mRoleManager, mPm, mExecutor);
try {
mService.init(mock(Looper.class), mock(RankingHandler.class),
mService.init(mService.new WorkerHandler(mock(Looper.class)),
mock(RankingHandler.class),
mock(IPackageManager.class), mock(PackageManager.class),
mock(LightsManager.class),
mock(NotificationListeners.class), mock(NotificationAssistants.class),
@@ -140,7 +141,7 @@ public class RoleObserverTest extends UiServiceTestCase {
mock(UsageStatsManagerInternal.class),
mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class),
mock(UriGrantsManagerInternal.class),
mock(AppOpsManager.class), mUm);
mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class));
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
throw e;