Add notification sent count to channel settings

Bug: 79607096
Test: robotests
Change-Id: I3c1d8fd1cbd9f5b8e997f1bfd50926121a5040fb
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index 5e8ef19..314a99e 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -21,6 +21,8 @@
 import android.app.INotificationManager;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageEvents;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -28,21 +30,30 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.graphics.drawable.Drawable;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.service.notification.NotifyingApp;
+import android.text.format.DateUtils;
 import android.util.IconDrawableFactory;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.R;
 import com.android.settingslib.Utils;
+import com.android.settingslib.utils.StringUtil;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class NotificationBackend {
     private static final String TAG = "NotificationBackend";
 
+    static IUsageStatsManager sUsageStatsManager = IUsageStatsManager.Stub.asInterface(
+            ServiceManager.getService(Context.USAGE_STATS_SERVICE));
+    private static final int DAYS_TO_CHECK = 7;
     static INotificationManager sINM = INotificationManager.Stub.asInterface(
             ServiceManager.getService(Context.NOTIFICATION_SERVICE));
 
@@ -62,6 +73,7 @@
         row.userId = UserHandle.getUserId(row.uid);
         row.blockedChannelCount = getBlockedChannelCount(row.pkg, row.uid);
         row.channelCount = getChannelCount(row.pkg, row.uid);
+        row.sentByChannel = getAggregatedUsageEvents(context, row.userId, row.pkg);
         return row;
     }
 
@@ -259,6 +271,87 @@
         }
     }
 
+    protected Map<String, NotificationsSentState> getAggregatedUsageEvents(
+            Context context, int userId, String pkg) {
+        long now = System.currentTimeMillis();
+        long startTime = now - (DateUtils.DAY_IN_MILLIS * DAYS_TO_CHECK);
+        UsageEvents events = null;
+        try {
+            events = sUsageStatsManager.queryEventsForPackageForUser(
+                    startTime, now, userId, pkg, context.getPackageName());
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        return getAggregatedUsageEvents(events);
+    }
+
+    protected Map<String, NotificationsSentState> getAggregatedUsageEvents(UsageEvents events) {
+        Map<String, NotificationsSentState> sentByChannel = new HashMap<>();
+        if (events != null) {
+            UsageEvents.Event event = new UsageEvents.Event();
+            while (events.hasNextEvent()) {
+                events.getNextEvent(event);
+
+                if (event.getEventType() == UsageEvents.Event.NOTIFICATION_INTERRUPTION) {
+                    String channelId = event.mNotificationChannelId;
+                    if (channelId != null) {
+                        NotificationsSentState stats = sentByChannel.get(channelId);
+                        if (stats == null) {
+                            stats = new NotificationsSentState();
+                            sentByChannel.put(channelId, stats);
+                        }
+                        if (event.getTimeStamp() > stats.lastSent) {
+                            stats.lastSent = event.getTimeStamp();
+                        }
+                        stats.sentCount++;
+                        calculateAvgSentCounts(stats);
+                    }
+                }
+
+            }
+        }
+        return sentByChannel;
+    }
+
+    public static CharSequence getSentSummary(Context context, NotificationsSentState state,
+            boolean sortByRecency) {
+        if (state == null) {
+            return null;
+        }
+        if (sortByRecency) {
+            if (state.lastSent == 0) {
+                return context.getString(R.string.notifications_sent_never);
+            }
+            return StringUtil.formatRelativeTime(
+                    context, System.currentTimeMillis() - state.lastSent, true);
+        } else {
+            if (state.avgSentWeekly > 0) {
+                return context.getString(R.string.notifications_sent_weekly, state.avgSentWeekly);
+            }
+            return context.getString(R.string.notifications_sent_daily, state.avgSentDaily);
+        }
+    }
+
+    private void calculateAvgSentCounts(NotificationsSentState stats) {
+        if (stats != null) {
+            stats.avgSentDaily = Math.round((float) stats.sentCount / DAYS_TO_CHECK);
+            if (stats.sentCount < DAYS_TO_CHECK) {
+                stats.avgSentWeekly = stats.sentCount;
+            }
+        }
+    }
+
+    /**
+     * NotificationsSentState contains how often an app sends notifications and how recently it sent
+     * one.
+     */
+    public static class NotificationsSentState {
+        public int avgSentDaily = 0;
+        public int avgSentWeekly = 0;
+        public long lastSent = 0;
+        public int sentCount = 0;
+    }
+
     static class Row {
         public String section;
     }
@@ -278,5 +371,6 @@
         public int userId;
         public int blockedChannelCount;
         public int channelCount;
+        public Map<String, NotificationsSentState> sentByChannel;
     }
 }
diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java
index 9f53334..e1c5876 100644
--- a/src/com/android/settings/notification/NotificationSettingsBase.java
+++ b/src/com/android/settings/notification/NotificationSettingsBase.java
@@ -276,6 +276,8 @@
                 && !groupBlocked);
         channelPref.setKey(channel.getId());
         channelPref.setTitle(channel.getName());
+        channelPref.setSummary(NotificationBackend.getSentSummary(
+                mContext, mAppRow.sentByChannel.get(channel.getId()), false));
         channelPref.setChecked(channel.getImportance() != IMPORTANCE_NONE);
         Bundle channelArgs = new Bundle();
         channelArgs.putInt(AppInfoBase.ARG_PACKAGE_UID, mUid);
diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java
index 9678ecb..8b1190e 100644
--- a/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppNotificationPreferenceControllerTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Activity;
+import android.app.usage.IUsageStatsManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -78,6 +79,8 @@
         final ApplicationsState.AppEntry appEntry = mock(ApplicationsState.AppEntry.class);
         appEntry.info = new ApplicationInfo();
         when(mFragment.getAppEntry()).thenReturn(appEntry);
+        NotificationBackend backend = new NotificationBackend();
+        ReflectionHelpers.setField(backend, "sUsageStatsManager", mock(IUsageStatsManager.class));
         ReflectionHelpers.setField(mController, "mBackend", new NotificationBackend());
         mController.displayPreference(mScreen);
 
diff --git a/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java b/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java
index 4d7b07c..c725962 100644
--- a/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java
+++ b/tests/robotests/src/com/android/settings/notification/NotificationBackendTest.java
@@ -16,17 +16,31 @@
 
 package com.android.settings.notification;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.app.usage.UsageEvents;
+import android.os.Parcel;
+
 import com.android.settings.notification.NotificationBackend.AppRow;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
 @RunWith(SettingsRobolectricTestRunner.class)
 public class NotificationBackendTest {
 
@@ -118,4 +132,47 @@
         assertTrue(appRow.lockedImportance);
         assertEquals("SpecificChannel", appRow.lockedChannelId);
     }
+
+    @Test
+    public void testGetAggregatedUsageEvents_multipleEventsAgg() {
+        List<UsageEvents.Event> events = new ArrayList<>();
+        UsageEvents.Event good = new UsageEvents.Event();
+        good.mEventType = UsageEvents.Event.NOTIFICATION_INTERRUPTION;
+        good.mPackage = "pkg";
+        good.mNotificationChannelId = "channel1";
+        good.mTimeStamp = 2;
+        events.add(good);
+        UsageEvents.Event good1 = new UsageEvents.Event();
+        good1.mEventType = UsageEvents.Event.NOTIFICATION_INTERRUPTION;
+        good1.mPackage = "pkg";
+        good1.mNotificationChannelId = "channel1";
+        good1.mTimeStamp = 6;
+        events.add(good1);
+        UsageEvents.Event good2 = new UsageEvents.Event();
+        good2.mEventType = UsageEvents.Event.NOTIFICATION_INTERRUPTION;
+        good2.mPackage = "pkg";
+        good2.mNotificationChannelId = "channel2";
+        good2.mTimeStamp = 3;
+        events.add(good2);
+        NotificationBackend backend = new NotificationBackend();
+
+        Map<String, NotificationBackend.NotificationsSentState> stats =
+                backend.getAggregatedUsageEvents(getUsageEvents(events));
+
+        assertThat(stats.get("channel1").sentCount).isEqualTo(2);
+        assertThat(stats.get("channel1").lastSent).isEqualTo(6);
+        assertThat(stats.get("channel1").avgSentWeekly).isEqualTo(2);
+        assertThat(stats.get("channel2").sentCount).isEqualTo(1);
+        assertThat(stats.get("channel2").lastSent).isEqualTo(3);
+        assertThat(stats.get("channel2").avgSentWeekly).isEqualTo(1);
+    }
+
+    private UsageEvents getUsageEvents(List<UsageEvents.Event> events) {
+        UsageEvents usageEvents = new UsageEvents(events, new String[] {"pkg"});
+        Parcel parcel = Parcel.obtain();
+        parcel.setDataPosition(0);
+        usageEvents.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return UsageEvents.CREATOR.createFromParcel(parcel);
+    }
 }