Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b317ff7f authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Hook up notification history to NMS.

All of the lifecycle events for the history are in place now with
the exception of triggering a write when the power button is
long pressed

The new tests interacted with DeviceConfig in such a way that
callbacks were being received after tests were completed, so I
also had to add an unregistration step for the DeviceConfig
listener.

Test: atest
Bug: 137396965
Change-Id: I9ad1197105a5c2434444965a1649485e36e4b692
parent e43fac3f
Loading
Loading
Loading
Loading
+3 −0
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ import android.app.ITransientNotification;
import android.app.Notification;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationChannelGroup;
import android.app.NotificationHistory;
import android.app.NotificationManager;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.ComponentName;
import android.content.Intent;
import android.content.Intent;
@@ -119,6 +120,8 @@ interface INotificationManager
    @UnsupportedAppUsage
    @UnsupportedAppUsage
    StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
    StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);


    NotificationHistory getNotificationHistory(String callingPkg);

    void registerListener(in INotificationListener listener, in ComponentName component, int userid);
    void registerListener(in INotificationListener listener, in ComponentName component, int userid);
    void unregisterListener(in INotificationListener listener, int userid);
    void unregisterListener(in INotificationListener listener, int userid);


+1 −0
Original line number Original line Diff line number Diff line
@@ -183,6 +183,7 @@ public class NotificationHistoryDatabase {
    public NotificationHistory readNotificationHistory() {
    public NotificationHistory readNotificationHistory() {
        synchronized (mLock) {
        synchronized (mLock) {
            NotificationHistory notifications = new NotificationHistory();
            NotificationHistory notifications = new NotificationHistory();
            notifications.addNotificationsToWrite(mBuffer);


            for (AtomicFile file : mHistoryFiles) {
            for (AtomicFile file : mHistoryFiles) {
                try {
                try {
+5 −4
Original line number Original line Diff line number Diff line
@@ -112,7 +112,7 @@ public class NotificationHistoryManager {
        }
        }
    }
    }


    void onUserRemoved(@UserIdInt int userId) {
    public void onUserRemoved(@UserIdInt int userId) {
        synchronized (mLock) {
        synchronized (mLock) {
            // Actual data deletion is handled by other parts of the system (the entire directory is
            // 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
            // 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) {
        synchronized (mLock) {
            if (!mUserUnlockedStates.get(userId, false)) {
            if (!mUserUnlockedStates.get(userId, false)) {
                if (mHistoryEnabled.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) {
        synchronized (mLock) {
            final int userCount = mUserState.size();
            final int userCount = mUserState.size();
            for (int i = 0; i < userCount; i++) {
            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) {
        synchronized (mLock) {
            return mHistoryEnabled.get(userId);
            return mHistoryEnabled.get(userId);
        }
        }
+126 −20
Original line number Original line Diff line number Diff line
@@ -103,6 +103,8 @@ import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.WorkerThread;
import android.app.ActivityManager;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
import android.app.AlarmManager;
@@ -116,6 +118,8 @@ import android.app.IUriGrantsManager;
import android.app.Notification;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationChannelGroup;
import android.app.NotificationHistory;
import android.app.NotificationHistory.HistoricalNotification;
import android.app.NotificationManager;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.PendingIntent;
@@ -160,6 +164,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Bundle;
import android.os.Environment;
import android.os.Environment;
import android.os.Handler;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.IBinder;
import android.os.IDeviceIdleController;
import android.os.IDeviceIdleController;
@@ -477,12 +482,14 @@ public class NotificationManagerService extends SystemService {
    private long mLastOverRateLogTime;
    private long mLastOverRateLogTime;
    private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
    private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;


    private NotificationHistoryManager mHistoryManager;
    private SnoozeHelper mSnoozeHelper;
    private SnoozeHelper mSnoozeHelper;
    private GroupHelper mGroupHelper;
    private GroupHelper mGroupHelper;
    private int mAutoGroupAtCount;
    private int mAutoGroupAtCount;
    private boolean mIsTelevision;
    private boolean mIsTelevision;
    private boolean mIsAutomotive;
    private boolean mIsAutomotive;
    private boolean mNotificationEffectsEnabledForAutomotive;
    private boolean mNotificationEffectsEnabledForAutomotive;
    private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener;


    private int mWarnRemoteViewsSizeBytes;
    private int mWarnRemoteViewsSizeBytes;
    private int mStripRemoteViewsSizeBytes;
    private int mStripRemoteViewsSizeBytes;
@@ -1547,6 +1554,7 @@ public class NotificationManagerService extends SystemService {
                mListeners.onUserRemoved(userId);
                mListeners.onUserRemoved(userId);
                mConditionProviders.onUserRemoved(userId);
                mConditionProviders.onUserRemoved(userId);
                mAssistants.onUserRemoved(userId);
                mAssistants.onUserRemoved(userId);
                mHistoryManager.onUserRemoved(userId);
                handleSavePolicyFile();
                handleSavePolicyFile();
            } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
            } else if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
                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.
    // TODO: All tests should use this init instead of the one-off setters above.
    @VisibleForTesting
    @VisibleForTesting
    void init(Looper looper, RankingHandler rankingHandler, IPackageManager packageManager,
    void init(WorkerHandler handler, RankingHandler rankingHandler,
            PackageManager packageManagerClient,
            IPackageManager packageManager, PackageManager packageManagerClient,
            LightsManager lightsManager, NotificationListeners notificationListeners,
            LightsManager lightsManager, NotificationListeners notificationListeners,
            NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
            NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
            ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
            ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
@@ -1784,7 +1792,8 @@ public class NotificationManagerService extends SystemService {
            ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
            ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
            UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm,
            UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm,
            IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps,
            IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps,
            UserManager userManager) {
            UserManager userManager, NotificationHistoryManager historyManager) {
        mHandler = handler;
        Resources resources = getContext().getResources();
        Resources resources = getContext().getResources();
        mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
        mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
                Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
                Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
@@ -1810,7 +1819,6 @@ public class NotificationManagerService extends SystemService {
        mPlatformCompat = IPlatformCompat.Stub.asInterface(
        mPlatformCompat = IPlatformCompat.Stub.asInterface(
                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));


        mHandler = new WorkerHandler(looper);
        mUiHandler = new Handler(UiThread.get().getLooper());
        mUiHandler = new Handler(UiThread.get().getLooper());
        String[] extractorNames;
        String[] extractorNames;
        try {
        try {
@@ -1869,6 +1877,7 @@ public class NotificationManagerService extends SystemService {
                extractorNames);
                extractorNames);
        mSnoozeHelper = snoozeHelper;
        mSnoozeHelper = snoozeHelper;
        mGroupHelper = groupHelper;
        mGroupHelper = groupHelper;
        mHistoryManager = historyManager;


        // This is a ManagedServices object that keeps track of the listeners.
        // This is a ManagedServices object that keeps track of the listeners.
        mListeners = notificationListeners;
        mListeners = notificationListeners;
@@ -1966,7 +1975,9 @@ public class NotificationManagerService extends SystemService {
        final File systemDir = new File(Environment.getDataDirectory(), "system");
        final File systemDir = new File(Environment.getDataDirectory(), "system");
        mRankingThread.start();
        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(),
                AppGlobals.getPackageManager(), getContext().getPackageManager(),
                getLocalService(LightsManager.class),
                getLocalService(LightsManager.class),
                new NotificationListeners(AppGlobals.getPackageManager()),
                new NotificationListeners(AppGlobals.getPackageManager()),
@@ -1983,7 +1994,8 @@ public class NotificationManagerService extends SystemService {
                UriGrantsManager.getService(),
                UriGrantsManager.getService(),
                LocalServices.getService(UriGrantsManagerInternal.class),
                LocalServices.getService(UriGrantsManagerInternal.class),
                (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE),
                (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE),
                getContext().getSystemService(UserManager.class));
                getContext().getSystemService(UserManager.class),
                new NotificationHistoryManager(getContext(), handler));


        // register for various Intents
        // register for various Intents
        IntentFilter filter = new IntentFilter();
        IntentFilter filter = new IntentFilter();
@@ -2036,10 +2048,7 @@ public class NotificationManagerService extends SystemService {
    }
    }


    private void registerDeviceConfigChange() {
    private void registerDeviceConfigChange() {
        DeviceConfig.addOnPropertiesChangedListener(
        mDeviceConfigChangedListener = properties -> {
                DeviceConfig.NAMESPACE_SYSTEMUI,
                getContext().getMainExecutor(),
                (properties) -> {
            if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())) {
            if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())) {
                return;
                return;
            }
            }
@@ -2048,7 +2057,17 @@ public class NotificationManagerService extends SystemService {
                mAssistants.allowAdjustmentType(Adjustment.KEY_IMPORTANCE);
                mAssistants.allowAdjustmentType(Adjustment.KEY_IMPORTANCE);
                mAssistants.resetDefaultAssistantsIfNecessary();
                mAssistants.resetDefaultAssistantsIfNecessary();
            }
            }
                });
        };
        DeviceConfig.addOnPropertiesChangedListener(
                DeviceConfig.NAMESPACE_SYSTEMUI,
                new HandlerExecutor(mHandler),
                mDeviceConfigChangedListener);
    }

    void unregisterDeviceConfigChange() {
        if (mDeviceConfigChangedListener != null) {
            DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
        }
    }
    }


    private GroupHelper getGroupHelper() {
    private GroupHelper getGroupHelper() {
@@ -2134,10 +2153,21 @@ public class NotificationManagerService extends SystemService {
            mListeners.onBootPhaseAppsCanStart();
            mListeners.onBootPhaseAppsCanStart();
            mAssistants.onBootPhaseAppsCanStart();
            mAssistants.onBootPhaseAppsCanStart();
            mConditionProviders.onBootPhaseAppsCanStart();
            mConditionProviders.onBootPhaseAppsCanStart();
            mHistoryManager.onBootPhaseAppsCanStart();
            registerDeviceConfigChange();
            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")
    @GuardedBy("mNotificationLock")
    private void updateListenerHintsLocked() {
    private void updateListenerHintsLocked() {
        final int hints = calculateHints();
        final int hints = calculateHints();
@@ -2449,10 +2479,56 @@ public class NotificationManagerService extends SystemService {
            mAppUsageStats.reportInterruptiveNotification(r.sbn.getPackageName(),
            mAppUsageStats.reportInterruptiveNotification(r.sbn.getPackageName(),
                    r.getChannel().getId(),
                    r.getChannel().getId(),
                    getRealUserId(r.sbn.getUserId()));
                    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);
            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.
     * Report to usage stats that the user interacted with the notification.
     * @param r notification record
     * @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.
         * System-only API for getting a list of recent (cleared, no longer shown) notifications.
         *
         * Requires ACCESS_NOTIFICATIONS which is signature|system.
         */
         */
        @Override
        @Override
        @RequiresPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
        public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
        public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
            // enforce() will ensure the calling uid has the correct permission
            // enforce() will ensure the calling uid has the correct permission
            getContext().enforceCallingOrSelfPermission(
            getContext().enforceCallingOrSelfPermission(
@@ -3366,6 +3441,29 @@ public class NotificationManagerService extends SystemService {
            return tmp;
            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.
         * 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) {
            String[] pkgList, int[] uidList) {
        boolean preferencesChanged = removingPackage;
        boolean preferencesChanged = removingPackage;
        mListeners.onPackagesChanged(removingPackage, pkgList, uidList);
        mListeners.onPackagesChanged(removingPackage, pkgList, uidList);
@@ -6839,6 +6937,14 @@ public class NotificationManagerService extends SystemService {
        mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList);
        mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList);
        preferencesChanged |= mPreferencesHelper.onPackagesChanged(
        preferencesChanged |= mPreferencesHelper.onPackagesChanged(
                removingPackage, changeUserId, pkgList, uidList);
                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) {
        if (preferencesChanged) {
            handleSavePolicyFile();
            handleSavePolicyFile();
        }
        }
+11 −0
Original line number Original line Diff line number Diff line
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;


import android.app.AlarmManager;
import android.app.AlarmManager;
import android.app.NotificationHistory;
import android.app.NotificationHistory.HistoricalNotification;
import android.app.NotificationHistory.HistoricalNotification;
import android.content.Context;
import android.content.Context;
import android.graphics.drawable.Icon;
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
    @Test
    public void testReadNotificationHistory_withNumFilterDoesNotReadExtraFiles() throws Exception {
    public void testReadNotificationHistory_withNumFilterDoesNotReadExtraFiles() throws Exception {
        AtomicFile af = mock(AtomicFile.class);
        AtomicFile af = mock(AtomicFile.class);
Loading