Loading src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt +1 −1 Original line number Diff line number Diff line Loading @@ -95,7 +95,7 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) { AppSettingsPreference(app) AppAllServicesPreference(app) // TODO: notification_settings AppNotificationPreference(app) AppPermissionPreference(app) AppStoragePreference(app) InstantAppDomainsPreference(app) Loading src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appinfo import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settings.R import com.android.settings.applications.appinfo.AppInfoDashboardFragment import com.android.settings.notification.app.AppNotificationSettings import com.android.settings.spa.notification.AppNotificationRepository import com.android.settings.spa.notification.IAppNotificationRepository import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn @OptIn(ExperimentalLifecycleComposeApi::class) @Composable fun AppNotificationPreference( app: ApplicationInfo, repository: IAppNotificationRepository = rememberContext(::AppNotificationRepository), ) { val context = LocalContext.current val summaryFlow = remember(app) { flow { emit(repository.getNotificationSummary(app)) }.flowOn(Dispatchers.IO) } Preference(object : PreferenceModel { override val title = stringResource(R.string.notifications_label) override val summary = summaryFlow.collectAsStateWithLifecycle( initialValue = stringResource(R.string.summary_placeholder) ) override val onClick = { navigateToAppNotificationSettings(context, app) } }) } private fun navigateToAppNotificationSettings(context: Context, app: ApplicationInfo) { AppInfoDashboardFragment.startAppInfoFragment( AppNotificationSettings::class.java, app, context, AppInfoSettingsProvider.METRICS_CATEGORY, ) } No newline at end of file src/com/android/settings/spa/notification/AppNotificationRepository.kt +60 −1 Original line number Diff line number Diff line Loading @@ -31,8 +31,10 @@ import android.os.RemoteException import android.os.ServiceManager import android.util.Log import com.android.settings.R import com.android.settingslib.spa.framework.util.formatString import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.PackageManagers import com.android.settingslib.spaprivileged.model.app.userId import java.util.concurrent.TimeUnit import kotlin.math.max import kotlin.math.roundToInt Loading @@ -50,6 +52,11 @@ data class NotificationSentState( var sentCount: Int = 0, ) interface IAppNotificationRepository { /** Gets the notification summary for the given application. */ fun getNotificationSummary(app: ApplicationInfo): String } class AppNotificationRepository( private val context: Context, private val packageManagers: IPackageManagers = PackageManagers, Loading @@ -59,7 +66,7 @@ class AppNotificationRepository( private val notificationManager: INotificationManager = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE) ), ) { ) : IAppNotificationRepository { fun getAggregatedUsageEvents(userIdFlow: Flow<Int>): Flow<Map<String, NotificationSentState>> = userIdFlow.map { userId -> val aggregatedStats = mutableMapOf<String, NotificationSentState>() Loading Loading @@ -115,6 +122,58 @@ class AppNotificationRepository( } } override fun getNotificationSummary(app: ApplicationInfo): String { if (!isEnabled(app)) return context.getString(R.string.off) val channelCount = getChannelCount(app) if (channelCount == 0) { return calculateFrequencySummary(getSentCount(app)) } val blockedChannelCount = getBlockedChannelCount(app) if (channelCount == blockedChannelCount) return context.getString(R.string.off) val frequencySummary = calculateFrequencySummary(getSentCount(app)) if (blockedChannelCount == 0) return frequencySummary return context.getString( R.string.notifications_enabled_with_info, frequencySummary, context.formatString( R.string.notifications_categories_off, "count" to blockedChannelCount ) ) } private fun getSentCount(app: ApplicationInfo): Int { var sentCount = 0 queryEventsForPackageForUser(app).forEachNotificationEvent { sentCount++ } return sentCount } private fun queryEventsForPackageForUser(app: ApplicationInfo): UsageEvents? { val now = System.currentTimeMillis() val startTime = now - TimeUnit.DAYS.toMillis(DAYS_TO_CHECK) return try { usageStatsManager.queryEventsForPackageForUser( startTime, now, app.userId, app.packageName, context.packageName ) } catch (e: RemoteException) { Log.e(TAG, "Failed IUsageStatsManager.queryEventsForPackageForUser(): ", e) null } } private fun getChannelCount(app: ApplicationInfo): Int = try { notificationManager.getNumNotificationChannelsForPackage(app.packageName, app.uid, false) } catch (e: Exception) { Log.w(TAG, "Error calling INotificationManager", e) 0 } private fun getBlockedChannelCount(app: ApplicationInfo): Int = try { notificationManager.getBlockedChannelCount(app.packageName, app.uid) } catch (e: Exception) { Log.w(TAG, "Error calling INotificationManager", e) 0 } fun calculateFrequencySummary(sentCount: Int): String { val dailyFrequency = (sentCount.toFloat() / DAYS_TO_CHECK).roundToInt() return if (dailyFrequency > 0) { Loading tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppNotificationPreferenceTest.kt 0 → 100644 +122 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appinfo import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performClick import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.settings.R import com.android.settings.applications.appinfo.AppInfoDashboardFragment import com.android.settings.notification.app.AppNotificationSettings import com.android.settings.spa.notification.IAppNotificationRepository import com.android.settingslib.spa.testutils.delay import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoSession import org.mockito.Spy import org.mockito.quality.Strictness @RunWith(AndroidJUnit4::class) class AppNotificationPreferenceTest { @get:Rule val composeTestRule = createComposeRule() private lateinit var mockSession: MockitoSession @Spy private val context: Context = ApplicationProvider.getApplicationContext() private val repository = object : IAppNotificationRepository { override fun getNotificationSummary(app: ApplicationInfo) = SUMMARY } @Before fun setUp() { mockSession = ExtendedMockito.mockitoSession() .initMocks(this) .mockStatic(AppInfoDashboardFragment::class.java) .strictness(Strictness.LENIENT) .startMocking() } @After fun tearDown() { mockSession.finishMocking() } @Test fun title_displayed() { setContent() composeTestRule.onNodeWithText(context.getString(R.string.notifications_label)) .assertIsDisplayed() } @Test fun summary_displayed() { setContent() composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed() } @Test fun onClick_startActivity() { setContent() composeTestRule.onRoot().performClick() composeTestRule.delay() ExtendedMockito.verify { AppInfoDashboardFragment.startAppInfoFragment( AppNotificationSettings::class.java, APP, context, AppInfoSettingsProvider.METRICS_CATEGORY, ) } } private fun setContent() { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { AppNotificationPreference(app = APP, repository = repository) } } } private companion object { const val PACKAGE_NAME = "package.name" const val UID = 123 val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME uid = UID } const val SUMMARY = "Summary" } } No newline at end of file tests/spa_unit/src/com/android/settings/spa/notification/AppNotificationRepositoryTest.kt +91 −2 Original line number Diff line number Diff line Loading @@ -29,8 +29,10 @@ import android.content.pm.ApplicationInfo import android.os.Build import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R import com.android.settingslib.spa.testutils.any import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.userId import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first Loading Loading @@ -89,6 +91,39 @@ class AppNotificationRepositoryTest { return channel } private fun mockIsEnabled(app: ApplicationInfo, enabled: Boolean) { whenever(notificationManager.areNotificationsEnabledForPackage(app.packageName, app.uid)) .thenReturn(enabled) } private fun mockChannelCount(app: ApplicationInfo, count: Int) { whenever( notificationManager.getNumNotificationChannelsForPackage( app.packageName, app.uid, false, ) ).thenReturn(count) } private fun mockBlockedChannelCount(app: ApplicationInfo, count: Int) { whenever(notificationManager.getBlockedChannelCount(app.packageName, app.uid)) .thenReturn(count) } private fun mockSentCount(app: ApplicationInfo, sentCount: Int) { val events = (1..sentCount).map { UsageEvents.Event().apply { mEventType = UsageEvents.Event.NOTIFICATION_INTERRUPTION } } whenever( usageStatsManager.queryEventsForPackageForUser( any(), any(), eq(app.userId), eq(app.packageName), any() ) ).thenReturn(UsageEvents(events, arrayOf())) } @Test fun getAggregatedUsageEvents() = runTest { val events = listOf( Loading Loading @@ -120,8 +155,7 @@ class AppNotificationRepositoryTest { @Test fun isEnabled() { whenever(notificationManager.areNotificationsEnabledForPackage(APP.packageName, APP.uid)) .thenReturn(true) mockIsEnabled(app = APP, enabled = true) val isEnabled = repository.isEnabled(APP) Loading Loading @@ -211,6 +245,61 @@ class AppNotificationRepositoryTest { .setNotificationsEnabledForPackage(APP.packageName, APP.uid, true) } @Test fun getNotificationSummary_notEnabled() { mockIsEnabled(app = APP, enabled = false) val summary = repository.getNotificationSummary(APP) assertThat(summary).isEqualTo(context.getString(R.string.off)) } @Test fun getNotificationSummary_noChannel() { mockIsEnabled(app = APP, enabled = true) mockChannelCount(app = APP, count = 0) mockSentCount(app = APP, sentCount = 1) val summary = repository.getNotificationSummary(APP) assertThat(summary).isEqualTo("About 1 notification per week") } @Test fun getNotificationSummary_allChannelsBlocked() { mockIsEnabled(app = APP, enabled = true) mockChannelCount(app = APP, count = 2) mockBlockedChannelCount(app = APP, count = 2) val summary = repository.getNotificationSummary(APP) assertThat(summary).isEqualTo(context.getString(R.string.off)) } @Test fun getNotificationSummary_noChannelBlocked() { mockIsEnabled(app = APP, enabled = true) mockChannelCount(app = APP, count = 2) mockSentCount(app = APP, sentCount = 2) mockBlockedChannelCount(app = APP, count = 0) val summary = repository.getNotificationSummary(APP) assertThat(summary).isEqualTo("About 2 notifications per week") } @Test fun getNotificationSummary_someChannelsBlocked() { mockIsEnabled(app = APP, enabled = true) mockChannelCount(app = APP, count = 2) mockSentCount(app = APP, sentCount = 3) mockBlockedChannelCount(app = APP, count = 1) val summary = repository.getNotificationSummary(APP) assertThat(summary).isEqualTo("About 3 notifications per week / 1 category turned off") } @Test fun calculateFrequencySummary_daily() { val summary = repository.calculateFrequencySummary(4) Loading Loading
src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt +1 −1 Original line number Diff line number Diff line Loading @@ -95,7 +95,7 @@ private fun AppInfoSettings(packageInfoPresenter: PackageInfoPresenter) { AppSettingsPreference(app) AppAllServicesPreference(app) // TODO: notification_settings AppNotificationPreference(app) AppPermissionPreference(app) AppStoragePreference(app) InstantAppDomainsPreference(app) Loading
src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appinfo import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settings.R import com.android.settings.applications.appinfo.AppInfoDashboardFragment import com.android.settings.notification.app.AppNotificationSettings import com.android.settings.spa.notification.AppNotificationRepository import com.android.settings.spa.notification.IAppNotificationRepository import com.android.settingslib.spa.framework.compose.rememberContext import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn @OptIn(ExperimentalLifecycleComposeApi::class) @Composable fun AppNotificationPreference( app: ApplicationInfo, repository: IAppNotificationRepository = rememberContext(::AppNotificationRepository), ) { val context = LocalContext.current val summaryFlow = remember(app) { flow { emit(repository.getNotificationSummary(app)) }.flowOn(Dispatchers.IO) } Preference(object : PreferenceModel { override val title = stringResource(R.string.notifications_label) override val summary = summaryFlow.collectAsStateWithLifecycle( initialValue = stringResource(R.string.summary_placeholder) ) override val onClick = { navigateToAppNotificationSettings(context, app) } }) } private fun navigateToAppNotificationSettings(context: Context, app: ApplicationInfo) { AppInfoDashboardFragment.startAppInfoFragment( AppNotificationSettings::class.java, app, context, AppInfoSettingsProvider.METRICS_CATEGORY, ) } No newline at end of file
src/com/android/settings/spa/notification/AppNotificationRepository.kt +60 −1 Original line number Diff line number Diff line Loading @@ -31,8 +31,10 @@ import android.os.RemoteException import android.os.ServiceManager import android.util.Log import com.android.settings.R import com.android.settingslib.spa.framework.util.formatString import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.PackageManagers import com.android.settingslib.spaprivileged.model.app.userId import java.util.concurrent.TimeUnit import kotlin.math.max import kotlin.math.roundToInt Loading @@ -50,6 +52,11 @@ data class NotificationSentState( var sentCount: Int = 0, ) interface IAppNotificationRepository { /** Gets the notification summary for the given application. */ fun getNotificationSummary(app: ApplicationInfo): String } class AppNotificationRepository( private val context: Context, private val packageManagers: IPackageManagers = PackageManagers, Loading @@ -59,7 +66,7 @@ class AppNotificationRepository( private val notificationManager: INotificationManager = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE) ), ) { ) : IAppNotificationRepository { fun getAggregatedUsageEvents(userIdFlow: Flow<Int>): Flow<Map<String, NotificationSentState>> = userIdFlow.map { userId -> val aggregatedStats = mutableMapOf<String, NotificationSentState>() Loading Loading @@ -115,6 +122,58 @@ class AppNotificationRepository( } } override fun getNotificationSummary(app: ApplicationInfo): String { if (!isEnabled(app)) return context.getString(R.string.off) val channelCount = getChannelCount(app) if (channelCount == 0) { return calculateFrequencySummary(getSentCount(app)) } val blockedChannelCount = getBlockedChannelCount(app) if (channelCount == blockedChannelCount) return context.getString(R.string.off) val frequencySummary = calculateFrequencySummary(getSentCount(app)) if (blockedChannelCount == 0) return frequencySummary return context.getString( R.string.notifications_enabled_with_info, frequencySummary, context.formatString( R.string.notifications_categories_off, "count" to blockedChannelCount ) ) } private fun getSentCount(app: ApplicationInfo): Int { var sentCount = 0 queryEventsForPackageForUser(app).forEachNotificationEvent { sentCount++ } return sentCount } private fun queryEventsForPackageForUser(app: ApplicationInfo): UsageEvents? { val now = System.currentTimeMillis() val startTime = now - TimeUnit.DAYS.toMillis(DAYS_TO_CHECK) return try { usageStatsManager.queryEventsForPackageForUser( startTime, now, app.userId, app.packageName, context.packageName ) } catch (e: RemoteException) { Log.e(TAG, "Failed IUsageStatsManager.queryEventsForPackageForUser(): ", e) null } } private fun getChannelCount(app: ApplicationInfo): Int = try { notificationManager.getNumNotificationChannelsForPackage(app.packageName, app.uid, false) } catch (e: Exception) { Log.w(TAG, "Error calling INotificationManager", e) 0 } private fun getBlockedChannelCount(app: ApplicationInfo): Int = try { notificationManager.getBlockedChannelCount(app.packageName, app.uid) } catch (e: Exception) { Log.w(TAG, "Error calling INotificationManager", e) 0 } fun calculateFrequencySummary(sentCount: Int): String { val dailyFrequency = (sentCount.toFloat() / DAYS_TO_CHECK).roundToInt() return if (dailyFrequency > 0) { Loading
tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppNotificationPreferenceTest.kt 0 → 100644 +122 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.spa.app.appinfo import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performClick import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.settings.R import com.android.settings.applications.appinfo.AppInfoDashboardFragment import com.android.settings.notification.app.AppNotificationSettings import com.android.settings.spa.notification.IAppNotificationRepository import com.android.settingslib.spa.testutils.delay import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoSession import org.mockito.Spy import org.mockito.quality.Strictness @RunWith(AndroidJUnit4::class) class AppNotificationPreferenceTest { @get:Rule val composeTestRule = createComposeRule() private lateinit var mockSession: MockitoSession @Spy private val context: Context = ApplicationProvider.getApplicationContext() private val repository = object : IAppNotificationRepository { override fun getNotificationSummary(app: ApplicationInfo) = SUMMARY } @Before fun setUp() { mockSession = ExtendedMockito.mockitoSession() .initMocks(this) .mockStatic(AppInfoDashboardFragment::class.java) .strictness(Strictness.LENIENT) .startMocking() } @After fun tearDown() { mockSession.finishMocking() } @Test fun title_displayed() { setContent() composeTestRule.onNodeWithText(context.getString(R.string.notifications_label)) .assertIsDisplayed() } @Test fun summary_displayed() { setContent() composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed() } @Test fun onClick_startActivity() { setContent() composeTestRule.onRoot().performClick() composeTestRule.delay() ExtendedMockito.verify { AppInfoDashboardFragment.startAppInfoFragment( AppNotificationSettings::class.java, APP, context, AppInfoSettingsProvider.METRICS_CATEGORY, ) } } private fun setContent() { composeTestRule.setContent { CompositionLocalProvider(LocalContext provides context) { AppNotificationPreference(app = APP, repository = repository) } } } private companion object { const val PACKAGE_NAME = "package.name" const val UID = 123 val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME uid = UID } const val SUMMARY = "Summary" } } No newline at end of file
tests/spa_unit/src/com/android/settings/spa/notification/AppNotificationRepositoryTest.kt +91 −2 Original line number Diff line number Diff line Loading @@ -29,8 +29,10 @@ import android.content.pm.ApplicationInfo import android.os.Build import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R import com.android.settingslib.spa.testutils.any import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.userId import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first Loading Loading @@ -89,6 +91,39 @@ class AppNotificationRepositoryTest { return channel } private fun mockIsEnabled(app: ApplicationInfo, enabled: Boolean) { whenever(notificationManager.areNotificationsEnabledForPackage(app.packageName, app.uid)) .thenReturn(enabled) } private fun mockChannelCount(app: ApplicationInfo, count: Int) { whenever( notificationManager.getNumNotificationChannelsForPackage( app.packageName, app.uid, false, ) ).thenReturn(count) } private fun mockBlockedChannelCount(app: ApplicationInfo, count: Int) { whenever(notificationManager.getBlockedChannelCount(app.packageName, app.uid)) .thenReturn(count) } private fun mockSentCount(app: ApplicationInfo, sentCount: Int) { val events = (1..sentCount).map { UsageEvents.Event().apply { mEventType = UsageEvents.Event.NOTIFICATION_INTERRUPTION } } whenever( usageStatsManager.queryEventsForPackageForUser( any(), any(), eq(app.userId), eq(app.packageName), any() ) ).thenReturn(UsageEvents(events, arrayOf())) } @Test fun getAggregatedUsageEvents() = runTest { val events = listOf( Loading Loading @@ -120,8 +155,7 @@ class AppNotificationRepositoryTest { @Test fun isEnabled() { whenever(notificationManager.areNotificationsEnabledForPackage(APP.packageName, APP.uid)) .thenReturn(true) mockIsEnabled(app = APP, enabled = true) val isEnabled = repository.isEnabled(APP) Loading Loading @@ -211,6 +245,61 @@ class AppNotificationRepositoryTest { .setNotificationsEnabledForPackage(APP.packageName, APP.uid, true) } @Test fun getNotificationSummary_notEnabled() { mockIsEnabled(app = APP, enabled = false) val summary = repository.getNotificationSummary(APP) assertThat(summary).isEqualTo(context.getString(R.string.off)) } @Test fun getNotificationSummary_noChannel() { mockIsEnabled(app = APP, enabled = true) mockChannelCount(app = APP, count = 0) mockSentCount(app = APP, sentCount = 1) val summary = repository.getNotificationSummary(APP) assertThat(summary).isEqualTo("About 1 notification per week") } @Test fun getNotificationSummary_allChannelsBlocked() { mockIsEnabled(app = APP, enabled = true) mockChannelCount(app = APP, count = 2) mockBlockedChannelCount(app = APP, count = 2) val summary = repository.getNotificationSummary(APP) assertThat(summary).isEqualTo(context.getString(R.string.off)) } @Test fun getNotificationSummary_noChannelBlocked() { mockIsEnabled(app = APP, enabled = true) mockChannelCount(app = APP, count = 2) mockSentCount(app = APP, sentCount = 2) mockBlockedChannelCount(app = APP, count = 0) val summary = repository.getNotificationSummary(APP) assertThat(summary).isEqualTo("About 2 notifications per week") } @Test fun getNotificationSummary_someChannelsBlocked() { mockIsEnabled(app = APP, enabled = true) mockChannelCount(app = APP, count = 2) mockSentCount(app = APP, sentCount = 3) mockBlockedChannelCount(app = APP, count = 1) val summary = repository.getNotificationSummary(APP) assertThat(summary).isEqualTo("About 3 notifications per week / 1 category turned off") } @Test fun calculateFrequencySummary_daily() { val summary = repository.calculateFrequencySummary(4) Loading