Loading core/java/android/provider/Settings.java +10 −0 Original line number Diff line number Diff line Loading @@ -2418,6 +2418,16 @@ public final class Settings { public static final String ACTION_NOTIFICATION_BUNDLES = "android.settings.NOTIFICATION_BUNDLES"; /** * Activity Action: Show notification summarization settings screen * * @hide */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_NOTIFICATION_SUMMARIZATION = "android.settings.NOTIFICATION_SUMMARIZATION"; /** * Activity Action: Show app listing settings, filtered by those that send notifications. * Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SummarizationOnboardingInteractor.kt 0 → 100644 +79 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.statusbar.notification.stack.domain.interactor import android.app.NotificationManager import android.service.notification.Adjustment.KEY_SUMMARIZATION import androidx.core.content.edit import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.SharedPreferencesExt.observeBoolean import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext class SummarizationOnboardingInteractor @Inject constructor( notifListRepo: ActiveNotificationListRepository, private val sharedPreferencesInteractor: NotificationsSharedPreferencesInteractor, private val notificationManager: NotificationManager, @Background private val bgDispatcher: CoroutineDispatcher, ) { private val notifsPresent: Flow<Boolean> = notifListRepo.activeNotifications .map { store -> store.renderList.isNotEmpty() } .distinctUntilChanged() private val onboardingUnseen: Flow<Boolean> = sharedPreferencesInteractor.sharedPreferences .flatMapLatestConflated { prefs -> prefs?.observeBoolean(KEY_SHOW_SUMMARIZATION_ONBOARDING, true) ?: flowOf(false) } .distinctUntilChanged() private val summarizationAvailableAndDisabled: Flow<Boolean> = flow { emit(isAvailableAndDisabled()) } val onboardingNeeded: Flow<Boolean> = allOf(onboardingUnseen, summarizationAvailableAndDisabled, notifsPresent) .distinctUntilChanged() .flowOn(bgDispatcher) fun markOnboardingDismissed() { sharedPreferencesInteractor.sharedPreferences.value?.edit { putBoolean(KEY_SHOW_SUMMARIZATION_ONBOARDING, false) } } private suspend fun isAvailableAndDisabled(): Boolean = withContext(bgDispatcher) { KEY_SUMMARIZATION !in notificationManager.unsupportedAdjustmentTypes && KEY_SUMMARIZATION !in notificationManager.allowedAssistantAdjustments } } private const val KEY_SHOW_SUMMARIZATION_ONBOARDING = "show_summarization_onboarding" packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +26 −1 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.Bundles import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.OnboardingAffordanceManager import com.android.systemui.statusbar.notification.Summarization import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController import com.android.systemui.statusbar.notification.dagger.SilentHeader import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView Loading @@ -58,6 +59,7 @@ import com.android.systemui.statusbar.notification.stack.ui.view.NotificationSta import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList import com.android.systemui.statusbar.notification.stack.ui.viewmodel.BundleOnboardingViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SummarizationOnboardingViewModel import com.android.systemui.statusbar.notification.ui.viewbinder.HeadsUpNotificationViewBinder import com.android.systemui.util.kotlin.awaitCancellationThenDispose import com.android.systemui.util.kotlin.getOrNull Loading Loading @@ -97,7 +99,9 @@ constructor( private val viewModel: NotificationListViewModel, private val systemClock: SystemClock, private val bundleOnboardingBinder: Provider<BundleOnboardingViewBinder>, private val summarizationOnboardingBinder: Provider<SummarizationOnboardingViewBinder>, @Bundles private val bundleOnboardingMgr: OnboardingAffordanceManager, @Summarization private val summarizationOnboardingMgr: OnboardingAffordanceManager, ) { fun bindWhileAttached( Loading Loading @@ -356,6 +360,27 @@ constructor( private suspend fun bindSummarizationOnboarding(parentView: NotificationStackScrollLayout) { if (NotificationSummarizationOnboardingUi.isUnexpectedlyInLegacyMode()) return // TODO(b/391568054): not yet implemented val summarizationViewModel: SummarizationOnboardingViewModel = viewModel.summarizationOnboarding summarizationViewModel.showAffordance .flatMapLatestConflated { show -> if (show) { configuration .inflateLayout<OnboardingAffordanceView>( R.layout.onboarding_summaries_affordance, parentView, attachToRoot = false, ) .flowOn(inflationDispatcher) } else { flowOf(null) } } .collectLatest { summariesView -> summarizationOnboardingMgr.view.value = summariesView summariesView?.let { summarizationOnboardingBinder.get().bind(summarizationViewModel, summariesView) } } } } packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SummarizationOnboardingViewBinder.kt 0 → 100644 +50 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.statusbar.notification.stack.ui.viewbinder import android.content.Intent import android.provider.Settings import com.android.systemui.lifecycle.repeatWhenAttachedToWindow import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.stack.OnboardingAffordanceView import com.android.systemui.statusbar.notification.stack.activityStarterScope import com.android.systemui.statusbar.notification.stack.onDismissClicked import com.android.systemui.statusbar.notification.stack.onTurnOnClicked import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SummarizationOnboardingViewModel import javax.inject.Inject import kotlinx.coroutines.launch class SummarizationOnboardingViewBinder @Inject constructor(private val activityStarter: NotificationActivityStarter) { suspend fun bind(viewModel: SummarizationOnboardingViewModel, view: OnboardingAffordanceView) { view.repeatWhenAttachedToWindow { launch { view.onDismissClicked.collect { viewModel.dismissAffordance() } } launch { view.onTurnOnClicked.collect { view.activityStarterScope(activityStarter) { startSettingsIntent(settingsIntent) viewModel.dismissAffordance() } } } } } } private val settingsIntent = NotificationActivityStarter.SettingsIntent(Intent(Settings.ACTION_NOTIFICATION_SUMMARIZATION)) packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +1 −0 Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ constructor( val footerViewModelFactory: FooterViewModel.Factory, val emptyShadeViewModelFactory: EmptyShadeViewModel.Factory, val bundleOnboarding: BundleOnboardingViewModel, val summarizationOnboarding: SummarizationOnboardingViewModel, val logger: Optional<NotificationLoggerViewModel>, activeNotificationsInteractor: ActiveNotificationsInteractor, notificationStackInteractor: NotificationStackInteractor, Loading Loading
core/java/android/provider/Settings.java +10 −0 Original line number Diff line number Diff line Loading @@ -2418,6 +2418,16 @@ public final class Settings { public static final String ACTION_NOTIFICATION_BUNDLES = "android.settings.NOTIFICATION_BUNDLES"; /** * Activity Action: Show notification summarization settings screen * * @hide */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_NOTIFICATION_SUMMARIZATION = "android.settings.NOTIFICATION_SUMMARIZATION"; /** * Activity Action: Show app listing settings, filtered by those that send notifications. * Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SummarizationOnboardingInteractor.kt 0 → 100644 +79 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.statusbar.notification.stack.domain.interactor import android.app.NotificationManager import android.service.notification.Adjustment.KEY_SUMMARIZATION import androidx.core.content.edit import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.SharedPreferencesExt.observeBoolean import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext class SummarizationOnboardingInteractor @Inject constructor( notifListRepo: ActiveNotificationListRepository, private val sharedPreferencesInteractor: NotificationsSharedPreferencesInteractor, private val notificationManager: NotificationManager, @Background private val bgDispatcher: CoroutineDispatcher, ) { private val notifsPresent: Flow<Boolean> = notifListRepo.activeNotifications .map { store -> store.renderList.isNotEmpty() } .distinctUntilChanged() private val onboardingUnseen: Flow<Boolean> = sharedPreferencesInteractor.sharedPreferences .flatMapLatestConflated { prefs -> prefs?.observeBoolean(KEY_SHOW_SUMMARIZATION_ONBOARDING, true) ?: flowOf(false) } .distinctUntilChanged() private val summarizationAvailableAndDisabled: Flow<Boolean> = flow { emit(isAvailableAndDisabled()) } val onboardingNeeded: Flow<Boolean> = allOf(onboardingUnseen, summarizationAvailableAndDisabled, notifsPresent) .distinctUntilChanged() .flowOn(bgDispatcher) fun markOnboardingDismissed() { sharedPreferencesInteractor.sharedPreferences.value?.edit { putBoolean(KEY_SHOW_SUMMARIZATION_ONBOARDING, false) } } private suspend fun isAvailableAndDisabled(): Boolean = withContext(bgDispatcher) { KEY_SUMMARIZATION !in notificationManager.unsupportedAdjustmentTypes && KEY_SUMMARIZATION !in notificationManager.allowedAssistantAdjustments } } private const val KEY_SHOW_SUMMARIZATION_ONBOARDING = "show_summarization_onboarding"
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +26 −1 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.notification.Bundles import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.OnboardingAffordanceManager import com.android.systemui.statusbar.notification.Summarization import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController import com.android.systemui.statusbar.notification.dagger.SilentHeader import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView Loading @@ -58,6 +59,7 @@ import com.android.systemui.statusbar.notification.stack.ui.view.NotificationSta import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList import com.android.systemui.statusbar.notification.stack.ui.viewmodel.BundleOnboardingViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SummarizationOnboardingViewModel import com.android.systemui.statusbar.notification.ui.viewbinder.HeadsUpNotificationViewBinder import com.android.systemui.util.kotlin.awaitCancellationThenDispose import com.android.systemui.util.kotlin.getOrNull Loading Loading @@ -97,7 +99,9 @@ constructor( private val viewModel: NotificationListViewModel, private val systemClock: SystemClock, private val bundleOnboardingBinder: Provider<BundleOnboardingViewBinder>, private val summarizationOnboardingBinder: Provider<SummarizationOnboardingViewBinder>, @Bundles private val bundleOnboardingMgr: OnboardingAffordanceManager, @Summarization private val summarizationOnboardingMgr: OnboardingAffordanceManager, ) { fun bindWhileAttached( Loading Loading @@ -356,6 +360,27 @@ constructor( private suspend fun bindSummarizationOnboarding(parentView: NotificationStackScrollLayout) { if (NotificationSummarizationOnboardingUi.isUnexpectedlyInLegacyMode()) return // TODO(b/391568054): not yet implemented val summarizationViewModel: SummarizationOnboardingViewModel = viewModel.summarizationOnboarding summarizationViewModel.showAffordance .flatMapLatestConflated { show -> if (show) { configuration .inflateLayout<OnboardingAffordanceView>( R.layout.onboarding_summaries_affordance, parentView, attachToRoot = false, ) .flowOn(inflationDispatcher) } else { flowOf(null) } } .collectLatest { summariesView -> summarizationOnboardingMgr.view.value = summariesView summariesView?.let { summarizationOnboardingBinder.get().bind(summarizationViewModel, summariesView) } } } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SummarizationOnboardingViewBinder.kt 0 → 100644 +50 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.systemui.statusbar.notification.stack.ui.viewbinder import android.content.Intent import android.provider.Settings import com.android.systemui.lifecycle.repeatWhenAttachedToWindow import com.android.systemui.statusbar.notification.NotificationActivityStarter import com.android.systemui.statusbar.notification.stack.OnboardingAffordanceView import com.android.systemui.statusbar.notification.stack.activityStarterScope import com.android.systemui.statusbar.notification.stack.onDismissClicked import com.android.systemui.statusbar.notification.stack.onTurnOnClicked import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SummarizationOnboardingViewModel import javax.inject.Inject import kotlinx.coroutines.launch class SummarizationOnboardingViewBinder @Inject constructor(private val activityStarter: NotificationActivityStarter) { suspend fun bind(viewModel: SummarizationOnboardingViewModel, view: OnboardingAffordanceView) { view.repeatWhenAttachedToWindow { launch { view.onDismissClicked.collect { viewModel.dismissAffordance() } } launch { view.onTurnOnClicked.collect { view.activityStarterScope(activityStarter) { startSettingsIntent(settingsIntent) viewModel.dismissAffordance() } } } } } } private val settingsIntent = NotificationActivityStarter.SettingsIntent(Intent(Settings.ACTION_NOTIFICATION_SUMMARIZATION))
packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +1 −0 Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ constructor( val footerViewModelFactory: FooterViewModel.Factory, val emptyShadeViewModelFactory: EmptyShadeViewModel.Factory, val bundleOnboarding: BundleOnboardingViewModel, val summarizationOnboarding: SummarizationOnboardingViewModel, val logger: Optional<NotificationLoggerViewModel>, activeNotificationsInteractor: ActiveNotificationsInteractor, notificationStackInteractor: NotificationStackInteractor, Loading