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

Commit 14c68466 authored by Steve Elliott's avatar Steve Elliott Committed by Android (Google) Code Review
Browse files

Merge "Notification summaries onboarding" into main

parents 20ff11d0 a8161740
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