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

Commit a8161740 authored by Steve Elliott's avatar Steve Elliott
Browse files

Notification summaries onboarding

Bug: 391568054
Test: manual
Flag: android.app.nm_summarization_onboarding_ui
Change-Id: Id910110db48fb25145fad0999dd4c4c18ff45010
parent 1a2e3cb4
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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.
     *
+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"
+26 −1
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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(
@@ -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)
                }
            }
    }
}
+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))
+1 −0
Original line number Diff line number Diff line
@@ -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