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

Commit 8535c8b3 authored by Caitlin Shkuratov's avatar Caitlin Shkuratov
Browse files

[SB][Notif] Remove demo commands for notification chips.

Now that we can automatically promote notifications from certain apps,
we don't need the adb demo commands.

Bug: 361346412
Flag: com.android.systemui.status_bar_notification_chips
Test: SysUI builds, atest SystemUITests
Change-Id: I484a78c5a667fe4b7427de7b6452e6153f652b50
parent 3bdb41a1
Loading
Loading
Loading
Loading
+0 −146
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.chips.notification.demo.ui.viewmodel

import android.content.packageManager
import android.graphics.drawable.BitmapDrawable
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.commandline.commandRegistry
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever

@SmallTest
class DemoNotifChipViewModelTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val commandRegistry = kosmos.commandRegistry
    private val pw = PrintWriter(StringWriter())

    private val underTest = kosmos.demoNotifChipViewModel

    @Before
    fun setUp() {
        underTest.start()
        whenever(kosmos.packageManager.getApplicationIcon(any<String>()))
            .thenReturn(BitmapDrawable())
    }

    @Test
    @DisableFlags(StatusBarNotifChips.FLAG_NAME)
    fun chip_flagOff_hidden() =
        testScope.runTest {
            val latest by collectLastValue(underTest.chip)

            addDemoNotifChip()

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun chip_noPackage_hidden() =
        testScope.runTest {
            val latest by collectLastValue(underTest.chip)

            commandRegistry.onShellCommand(pw, arrayOf("demo-notif"))

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun chip_hasPackage_shown() =
        testScope.runTest {
            val latest by collectLastValue(underTest.chip)

            commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "-p", "com.android.systemui"))

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun chip_hasText_shownWithText() =
        testScope.runTest {
            val latest by collectLastValue(underTest.chip)

            commandRegistry.onShellCommand(
                pw,
                arrayOf("demo-notif", "-p", "com.android.systemui", "-t", "test"),
            )

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Text::class.java)
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun chip_supportsColor() =
        testScope.runTest {
            val latest by collectLastValue(underTest.chip)

            commandRegistry.onShellCommand(
                pw,
                arrayOf("demo-notif", "-p", "com.android.systemui", "-c", "#434343"),
            )

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
            assertThat((latest as OngoingActivityChipModel.Shown).colors)
                .isInstanceOf(ColorsModel.Custom::class.java)
        }

    @Test
    @EnableFlags(StatusBarNotifChips.FLAG_NAME)
    fun chip_hasHideArg_hidden() =
        testScope.runTest {
            val latest by collectLastValue(underTest.chip)

            // First, show a chip
            addDemoNotifChip()
            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)

            // Then, hide the chip
            commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "--hide"))

            assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
        }

    private fun addDemoNotifChip() {
        addDemoNotifChip(commandRegistry, pw)
    }

    companion object {
        fun addDemoNotifChip(commandRegistry: CommandRegistry, pw: PrintWriter) {
            commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "-p", "com.android.systemui"))
        }
    }
}
+0 −2
Original line number Diff line number Diff line
@@ -38,7 +38,6 @@ import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
@@ -97,7 +96,6 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
    @Before
    fun setUp() {
        setUpPackageManagerForMediaProjection(kosmos)
        kosmos.demoNotifChipViewModel.start()
        val icon =
            BitmapDrawable(
                context.resources,
+41 −37
Original line number Diff line number Diff line
@@ -37,8 +37,6 @@ import com.android.systemui.screenrecord.data.repository.screenRecordRepository
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModelTest.Companion.addDemoNotifChip
import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip
@@ -112,7 +110,6 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
    @Before
    fun setUp() {
        setUpPackageManagerForMediaProjection(kosmos)
        kosmos.demoNotifChipViewModel.start()
        kosmos.statusBarNotificationChipsInteractor.start()
        val icon =
            BitmapDrawable(
@@ -254,20 +251,6 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
            assertIsCallChip(latest!!.secondary)
        }

    @Test
    fun chips_threeActiveChips_topTwoShown() =
        testScope.runTest {
            screenRecordState.value = ScreenRecordModel.Recording
            callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
            addDemoNotifChip(commandRegistry, pw)

            val latest by collectLastValue(underTest.chips)

            assertIsScreenRecordChip(latest!!.primary)
            assertIsCallChip(latest!!.secondary)
            // Demo notif chip is dropped
        }

    @Test
    fun primaryChip_onlyCallShown_callShown() =
        testScope.runTest {
@@ -433,7 +416,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
    fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
        testScope.runTest {
            // Start with just the lowest priority chip shown
            addDemoNotifChip(commandRegistry, pw)
            val notifIcon = mock<StatusBarIconView>()
            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = notifIcon,
                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
                    )
                )
            )
            // And everything else hidden
            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
            mediaProjectionState.value = MediaProjectionState.NotProjecting
@@ -441,7 +433,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {

            val latest by collectLastValue(underTest.primaryChip)

            assertIsDemoNotifChip(latest)
            assertIsNotifChip(latest, notifIcon)

            // WHEN the higher priority call chip is added
            callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
@@ -475,7 +467,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
            mediaProjectionState.value =
                MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
            callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
            addDemoNotifChip(commandRegistry, pw)
            val notifIcon = mock<StatusBarIconView>()
            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = notifIcon,
                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
                    )
                )
            )

            val latest by collectLastValue(underTest.primaryChip)

@@ -497,15 +498,24 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
            // WHEN the higher priority call is removed
            callRepo.setOngoingCallState(OngoingCallModel.NoCall)

            // THEN the lower priority demo notif is used
            assertIsDemoNotifChip(latest)
            // THEN the lower priority notif is used
            assertIsNotifChip(latest, notifIcon)
        }

    @Test
    fun chips_movesChipsAroundAccordingToPriority() =
        testScope.runTest {
            // Start with just the lowest priority chip shown
            addDemoNotifChip(commandRegistry, pw)
            val notifIcon = mock<StatusBarIconView>()
            setNotifs(
                listOf(
                    activeNotificationModel(
                        key = "notif",
                        statusBarChipIcon = notifIcon,
                        promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
                    )
                )
            )
            // And everything else hidden
            callRepo.setOngoingCallState(OngoingCallModel.NoCall)
            mediaProjectionState.value = MediaProjectionState.NotProjecting
@@ -513,16 +523,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {

            val latest by collectLastValue(underTest.chips)

            assertIsDemoNotifChip(latest!!.primary)
            assertIsNotifChip(latest!!.primary, notifIcon)
            assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)

            // WHEN the higher priority call chip is added
            callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))

            // THEN the higher priority call chip is used as primary and demo notif is demoted to
            // THEN the higher priority call chip is used as primary and notif is demoted to
            // secondary
            assertIsCallChip(latest!!.primary)
            assertIsDemoNotifChip(latest!!.secondary)
            assertIsNotifChip(latest!!.secondary, notifIcon)

            // WHEN the higher priority media projection chip is added
            mediaProjectionState.value =
@@ -533,7 +543,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
                )

            // THEN the higher priority media projection chip is used as primary and call is demoted
            // to secondary (and demo notif is dropped altogether)
            // to secondary (and notif is dropped altogether)
            assertIsShareToAppChip(latest!!.primary)
            assertIsCallChip(latest!!.secondary)

@@ -547,15 +557,15 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
            screenRecordState.value = ScreenRecordModel.DoingNothing
            callRepo.setOngoingCallState(OngoingCallModel.NoCall)

            // THEN media projection and demo notif remain
            // THEN media projection and notif remain
            assertIsShareToAppChip(latest!!.primary)
            assertIsDemoNotifChip(latest!!.secondary)
            assertIsNotifChip(latest!!.secondary, notifIcon)

            // WHEN media projection is dropped
            mediaProjectionState.value = MediaProjectionState.NotProjecting

            // THEN demo notif is promoted to primary
            assertIsDemoNotifChip(latest!!.primary)
            // THEN notif is promoted to primary
            assertIsNotifChip(latest!!.primary, notifIcon)
            assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
        }

@@ -669,12 +679,6 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
            assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false))
        }

    private fun assertIsDemoNotifChip(latest: OngoingActivityChipModel?) {
        assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
        assertThat((latest as OngoingActivityChipModel.Shown).icon)
            .isInstanceOf(OngoingActivityChipModel.ChipIcon.FullColorAppIcon::class.java)
    }

    private fun setNotifs(notifs: List<ActiveNotificationModel>) {
        activeNotificationListRepository.activeNotifications.value =
            ActiveNotificationsStore.Builder()
+0 −7
Original line number Diff line number Diff line
@@ -20,10 +20,8 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModel
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
@@ -32,11 +30,6 @@ import dagger.multibindings.IntoMap

@Module
abstract class StatusBarChipsModule {
    @Binds
    @IntoMap
    @ClassKey(DemoNotifChipViewModel::class)
    abstract fun binds(impl: DemoNotifChipViewModel): CoreStartable

    companion object {
        @Provides
        @SysUISingleton
+0 −175
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.chips.notification.demo.ui.viewmodel

import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.drawable.Drawable
import com.android.systemui.CoreStartable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.commandline.ParseableCommand
import com.android.systemui.statusbar.commandline.Type
import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

/**
 * A view model that will emit demo promoted ongoing notification chips from [chip] based on adb
 * commands sent by the user.
 *
 * Example adb commands:
 *
 * To show a chip with the SysUI icon and custom text and color:
 * ```
 * adb shell cmd statusbar demo-notif -p com.android.systemui -t 10min -c "\\#434343"
 * ```
 *
 * To hide the chip:
 * ```
 * adb shell cmd statusbar demo-notif --hide
 * ```
 *
 * See [DemoNotifCommand] for more information on the adb command spec.
 */
@SysUISingleton
class DemoNotifChipViewModel
@Inject
constructor(
    private val commandRegistry: CommandRegistry,
    private val packageManager: PackageManager,
    private val systemClock: SystemClock,
) : OngoingActivityChipViewModel, CoreStartable {
    override fun start() {
        commandRegistry.registerCommand(DEMO_COMMAND_NAME) { DemoNotifCommand() }
    }

    private val _chip =
        MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden())
    override val chip: StateFlow<OngoingActivityChipModel> = _chip.asStateFlow()

    private inner class DemoNotifCommand : ParseableCommand(DEMO_COMMAND_NAME) {
        private val packageName: String? by
            param(
                longName = "packageName",
                shortName = "p",
                description = "The package name for app \"posting\" the demo notification",
                valueParser = Type.String,
            )

        private val text: String? by
            param(
                longName = "text",
                shortName = "t",
                description = "Text to display in the chip",
                valueParser = Type.String,
            )

        private val backgroundColor: Int? by
            param(
                longName = "color",
                shortName = "c",
                description =
                    "The color to show as the chip background color. " +
                        "You can either just write a basic color like 'red' or 'green', " +
                        "or you can include a #RRGGBB string in this format: \"\\\\#434343\".",
                valueParser = Type.Color,
            )

        private val hide by
            flag(longName = "hide", description = "Hides any existing demo notification chip")

        override fun execute(pw: PrintWriter) {
            if (!StatusBarNotifChips.isEnabled) {
                pw.println(
                    "Error: com.android.systemui.status_bar_notification_chips must be enabled " +
                        "before using this demo feature"
                )
                return
            }

            if (hide) {
                _chip.value = OngoingActivityChipModel.Hidden()
                return
            }

            val currentPackageName = packageName
            if (currentPackageName == null) {
                pw.println("--packageName (or -p) must be included")
                return
            }

            val appIcon = getAppIcon(currentPackageName)
            if (appIcon == null) {
                pw.println("Package $currentPackageName could not be found")
                return
            }

            val colors =
                if (backgroundColor != null) {
                    ColorsModel.Custom(backgroundColorInt = backgroundColor!!)
                } else {
                    ColorsModel.Themed
                }

            val currentText = text
            if (currentText != null) {
                _chip.value =
                    OngoingActivityChipModel.Shown.Text(
                        icon = appIcon,
                        colors = colors,
                        text = currentText,
                    )
            } else {
                _chip.value =
                    OngoingActivityChipModel.Shown.Timer(
                        icon = appIcon,
                        colors = colors,
                        startTimeMs = systemClock.elapsedRealtime(),
                        onClickListener = null,
                    )
            }
        }

        private fun getAppIcon(packageName: String): OngoingActivityChipModel.ChipIcon? {
            lateinit var iconDrawable: Drawable
            try {
                // Note: For the real implementation, we should check if applicationInfo exists
                // before fetching the icon, so that we either don't show the chip or show a good
                // backup icon in case the app info can't be found for some reason.
                iconDrawable = packageManager.getApplicationIcon(packageName)
            } catch (e: NameNotFoundException) {
                return null
            }
            return OngoingActivityChipModel.ChipIcon.FullColorAppIcon(
                Icon.Loaded(drawable = iconDrawable, contentDescription = null)
            )
        }
    }

    companion object {
        private const val DEMO_COMMAND_NAME = "demo-notif"
    }
}
Loading