diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index 5e8ef19d977..314a99e6b91 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -21,6 +21,8 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; 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.PackageInfo; 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 @@ public class NotificationBackend { 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 @@ public class NotificationBackend { } } + protected Map 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 getAggregatedUsageEvents(UsageEvents events) { + Map 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 class NotificationBackend { public int userId; public int blockedChannelCount; public int channelCount; + public Map sentByChannel; } } diff --git a/src/com/android/settings/notification/NotificationSettingsBase.java b/src/com/android/settings/notification/NotificationSettingsBase.java index 9f53334574f..e1c58767f51 100644 --- a/src/com/android/settings/notification/NotificationSettingsBase.java +++ b/src/com/android/settings/notification/NotificationSettingsBase.java @@ -276,6 +276,8 @@ abstract public class NotificationSettingsBase extends DashboardFragment { && !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 9678ecb3874..8b1190ed414 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.verify; 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 @@ public class AppNotificationPreferenceControllerTest { 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 4d7b07cf3a8..c725962bd27 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 @@ public class NotificationBackendTest { assertTrue(appRow.lockedImportance); assertEquals("SpecificChannel", appRow.lockedChannelId); } + + @Test + public void testGetAggregatedUsageEvents_multipleEventsAgg() { + List 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 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 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); + } }