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

Commit 59a76049 authored by Steve Elliott's avatar Steve Elliott
Browse files

Add command to resurrect onboarding

Flag: com.android.systemui.notification_bundle_ui
Flag: android.app.nm_summarization_onboarding_ui
Test: manual
Bug: 425255569
Change-Id: Id0132c602ef55899e425f8ddb24af11dbb7729d0
parent 58c8c22f
Loading
Loading
Loading
Loading
+6 −12
Original line number Diff line number Diff line
@@ -49,13 +49,13 @@ import kotlin.reflect.KProperty
 *     onExecute: (cmd: MyCommand, pw: PrintWriter) -> ()
 * ) : ParseableCommand(name) {
 *     val flag1 by flag(
 *         shortName = "-f",
 *         longName = "--flag",
 *         shortName = "f",
 *         longName = "flag",
 *         required = false,
 *     )
 *     val param1: String by param(
 *         shortName = "-a",
 *         longName = "--args",
 *         shortName = "a",
 *         longName = "args",
 *         valueParser = Type.String
 *     ).required()
 *     val param2: Int by param(..., valueParser = Type.Int)
@@ -237,11 +237,7 @@ abstract class ParseableCommand(val name: String, val description: String? = nul
        }
    }

    fun flag(
        longName: String,
        shortName: String? = null,
        description: String = "",
    ): Flag {
    fun flag(longName: String, shortName: String? = null, description: String = ""): Flag {
        if (!checkShortName(shortName)) {
            throw IllegalArgumentException(
                "Flag short name must be one character long, or null. Got ($shortName)"
@@ -280,9 +276,7 @@ abstract class ParseableCommand(val name: String, val description: String? = nul
        return parser.param(long, short, description, valueParser)
    }

    fun <T : ParseableCommand> subCommand(
        command: T,
    ) = parser.subCommand(command)
    fun <T : ParseableCommand> subCommand(command: T) = parser.subCommand(command)

    /** For use in conjunction with [param], makes the parameter required */
    fun <T : Any> SingleArgParamOptional<T>.required(): SingleArgParam<T> = parser.require(this)
+2 −0
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ import com.android.systemui.statusbar.notification.stack.MagneticNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.OnboardingAffordanceCommands;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
@@ -128,6 +129,7 @@ import javax.inject.Provider;
                NotificationSectionHeadersModule.class,
                NotificationStatsLoggerModule.class,
                NotificationsLogModule.class,
                OnboardingAffordanceCommands.Module.class,
        }
)
public interface NotificationsModule {
+93 −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

import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.commandline.ParseableCommand
import com.android.systemui.statusbar.notification.stack.domain.interactor.BundleOnboardingInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.SummarizationOnboardingInteractor
import dagger.Binds
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import java.io.PrintWriter
import javax.inject.Inject

private const val CMD_RESTORE_ONBOARDING = "restore_onboarding"

/**
 * Restores previously-dismissed notification onboarding affordances.
 *
 * `adb shell cmd statusbar restore_onboarding --target (bundles|summaries)`
 */
@SysUISingleton
class OnboardingAffordanceCommands
@Inject
constructor(
    private val commandRegistry: CommandRegistry,
    private val bundleOnboardingInteractor: BundleOnboardingInteractor,
    private val summarizationOnboardingInteractor: SummarizationOnboardingInteractor,
) :
    ParseableCommand(
        name = CMD_RESTORE_ONBOARDING,
        description = "Restores a dismissed notification onboarding affordance.",
    ),
    CoreStartable {

    private val target: Target by
        param(
                shortName = "t",
                longName = "target",
                description =
                    """Which onboarding affordance to restore. One of "bundles" or "summaries".""",
                valueParser = { arg ->
                    when (arg) {
                        "bundles",
                        "b" -> Result.success(Target.Bundle)
                        "summaries",
                        "s" -> Result.success(Target.Summarization)
                        else -> Result.failure(IllegalArgumentException("unknown target: $arg"))
                    }
                },
            )
            .required()

    override fun start() {
        commandRegistry.registerCommand(CMD_RESTORE_ONBOARDING) { this }
    }

    override fun execute(pw: PrintWriter) {
        when (target) {
            Target.Bundle -> bundleOnboardingInteractor.resurrectOnboarding()
            Target.Summarization -> summarizationOnboardingInteractor.resurrectOnboarding()
        }
    }

    private enum class Target {
        Bundle,
        Summarization,
    }

    @dagger.Module
    interface Module {
        @Binds
        @IntoMap
        @ClassKey(OnboardingAffordanceCommands::class)
        fun bindStartable(impl: OnboardingAffordanceCommands): CoreStartable
    }
}
+11 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.statusbar.notification.stack.domain.interactor

import android.util.Log
import androidx.core.content.edit
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -53,10 +54,19 @@ constructor(
        allOf(onboardingUnseen, bundlesPresent).distinctUntilChanged().flowOn(bgDispatcher)

    fun markOnboardingDismissed() {
        Log.i(TAG, "dismissing onboarding")
        sharedPreferencesInteractor.sharedPreferences.value?.edit {
            putBoolean(KEY_SHOW_BUNDLE_ONBOARDING, false)
        } ?: Log.e(TAG, "Could not write to shared preferences")
    }

    fun resurrectOnboarding() {
        Log.i(TAG, "reviving onboarding")
        sharedPreferencesInteractor.sharedPreferences.value?.edit {
            putBoolean(KEY_SHOW_BUNDLE_ONBOARDING, true)
        } ?: Log.e(TAG, "Could not write to shared preferences")
    }
}

private const val TAG = "NotifBundles"
private const val KEY_SHOW_BUNDLE_ONBOARDING = "show_bundle_onboarding"
+3 −3
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor
import android.content.Context
import android.content.SharedPreferences
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.domain.interactor.SharedPreferencesInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -32,12 +32,12 @@ class NotificationsSharedPreferencesInteractor
@Inject
constructor(
    sharedPreferencesInteractor: SharedPreferencesInteractor,
    @Application scope: CoroutineScope,
    @Background scope: CoroutineScope,
) {
    val sharedPreferences: StateFlow<SharedPreferences?> =
        sharedPreferencesInteractor
            .sharedPreferences(FILENAME, Context.MODE_PRIVATE)
            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
            .stateIn(scope, SharingStarted.Eagerly, null)
}

private const val FILENAME = "notifs_prefs"
Loading