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

Commit 4edd4298 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Allow bundled notifications to be blocked

However, the user will only have the option to block the original channel
the notification was posted to, not the "bundle channel"

Test: BundledNotificationInfoTest
Fixes: 443752792
Flag: com.android.systemui.notification_bundle_ui
Change-Id: I027085eca98bc749ef98abf1c26977eca66e58c4
parent 2a5f2bfd
Loading
Loading
Loading
Loading
+52 −25
Original line number Diff line number Diff line
@@ -18,9 +18,6 @@ package com.android.systemui.statusbar.notification.row
import android.app.INotificationManager
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationChannel.NEWS_ID
import android.app.NotificationChannel.PROMOTIONS_ID
import android.app.NotificationChannel.RECS_ID
import android.app.NotificationChannel.SOCIAL_MEDIA_ID
import android.app.NotificationManager.IMPORTANCE_LOW
import android.content.ComponentName
@@ -82,7 +79,6 @@ class BundledNotificationInfoTest : SysuiTestCase() {

    private lateinit var underTest: NotificationInfo
    private lateinit var notificationChannel: NotificationChannel
    private lateinit var defaultNotificationChannel: NotificationChannel
    private lateinit var classifiedNotificationChannel: NotificationChannel
    private lateinit var sbn: StatusBarNotification
    private lateinit var entry: NotificationEntry
@@ -99,6 +95,7 @@ class BundledNotificationInfoTest : SysuiTestCase() {
    private val channelEditorDialogController = mock<ChannelEditorDialogController>()
    private val packageDemotionInteractor = mock<PackageDemotionInteractor>()
    private val assistantFeedbackController = mock<AssistantFeedbackController>()
    private val onSettingsClick = mock<NotificationInfo.OnSettingsClickListener>()

    @Before
    fun setUp() {
@@ -127,8 +124,7 @@ class BundledNotificationInfoTest : SysuiTestCase() {
        systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME
        whenever(mockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
            .thenReturn(systemPackageInfo)
        whenever(mockPackageManager.getPackageInfo(eq("android"), anyInt()))
            .thenReturn(packageInfo)
        whenever(mockPackageManager.getPackageInfo(eq("android"), anyInt())).thenReturn(packageInfo)
        whenever(mockPackageManager.getApplicationLabel(applicationInfo)).thenReturn("App")

        val assistant = ComponentName("package", "service")
@@ -151,16 +147,10 @@ class BundledNotificationInfoTest : SysuiTestCase() {

        // Some test channels.
        notificationChannel = NotificationChannel(TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW)
        defaultNotificationChannel =
            NotificationChannel(
                NotificationChannel.DEFAULT_CHANNEL_ID,
                TEST_CHANNEL_NAME,
                IMPORTANCE_LOW,
            )
        classifiedNotificationChannel =
            NotificationChannel(SOCIAL_MEDIA_ID, "social", IMPORTANCE_LOW)

        val notification = Notification()
        val notification = Notification.Builder(mContext, notificationChannel.id).build()
        notification.extras.putParcelable(
            Notification.EXTRA_BUILDER_APPLICATION_INFO,
            applicationInfo,
@@ -187,8 +177,17 @@ class BundledNotificationInfoTest : SysuiTestCase() {
        whenever(assistantFeedbackController.isFeedbackEnabled).thenReturn(false)
        whenever(assistantFeedbackController.getInlineDescriptionResource(any()))
            .thenReturn(R.string.notification_channel_summary_automatic)
    }

        whenever(
                mockINotificationManager.getNotificationChannel(
                    anyString(),
                    anyInt(),
                    eq(sbn.packageName),
                    eq(notificationChannel.id),
                )
            )
            .thenReturn(notificationChannel)
    }

    @Test
    fun testHandleCloseControls_DoesNotMakeBinderCalllIfUnchanged() {
@@ -202,8 +201,14 @@ class BundledNotificationInfoTest : SysuiTestCase() {

    @Test
    fun testToggleCallsUpdate() {
        whenever(mockINotificationManager.isAdjustmentSupportedForPackage(
            anyInt(), anyString(), anyString())).thenReturn(true)
        whenever(
                mockINotificationManager.isAdjustmentSupportedForPackage(
                    anyInt(),
                    anyString(),
                    anyString(),
                )
            )
            .thenReturn(true)

        bindNotification()

@@ -218,8 +223,14 @@ class BundledNotificationInfoTest : SysuiTestCase() {

    @Test
    fun testToggleContainerCallsUpdate() {
        whenever(mockINotificationManager.isAdjustmentSupportedForPackage(
            anyInt(), anyString(), anyString())).thenReturn(true)
        whenever(
                mockINotificationManager.isAdjustmentSupportedForPackage(
                    anyInt(),
                    anyString(),
                    anyString(),
                )
            )
            .thenReturn(true)

        bindNotification()

@@ -234,13 +245,29 @@ class BundledNotificationInfoTest : SysuiTestCase() {

    @Test
    fun testSummaryText() {
        val channel = NotificationChannel(NEWS_ID, "news", 2)
        entry = NotificationEntryBuilder(entry)
            .updateRanking { it.setChannel(channel) }
        entry =
            NotificationEntryBuilder(entry)
                .updateRanking { it.setChannel(classifiedNotificationChannel) }
                .build()
        bindNotification()
        assertThat((underTest.findViewById(R.id.feature_summary) as TextView).text).isEqualTo(
            "For App")
        assertThat((underTest.findViewById(R.id.feature_summary) as TextView).text)
            .isEqualTo("For App")
    }

    @Test
    fun testTurnOffNotifications() {
        bindNotification()
        underTest.findViewById<View>(R.id.turn_off_notifications).performClick()
        verify(channelEditorDialogController)
            .prepareDialogForApp(
                "App",
                sbn.packageName,
                sbn.uid,
                notificationChannel,
                null,
                onSettingsClick,
            )
        verify(channelEditorDialogController).show()
    }

    private fun bindNotification(
@@ -255,7 +282,7 @@ class BundledNotificationInfoTest : SysuiTestCase() {
        pkg: String = TEST_PACKAGE_NAME,
        entry: NotificationEntry = this.entry,
        entryAdapter: EntryAdapter = this.entryAdapter,
        onSettingsClick: NotificationInfo.OnSettingsClickListener? = mock(),
        onSettingsClick: NotificationInfo.OnSettingsClickListener? = this.onSettingsClick,
        onAppSettingsClick: NotificationInfo.OnAppSettingsClickListener? = mock(),
        onFeedbackClickListener: NotificationInfo.OnFeedbackClickListener? = mock(),
        uiEventLogger: UiEventLogger = this.uiEventLogger,
+25 −11
Original line number Diff line number Diff line
@@ -188,10 +188,9 @@
            android:id="@+id/bottom_buttons"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="60dp"
            android:gravity="center_vertical"
            android:paddingStart="4dp"
            android:paddingEnd="4dp"
            android:layout_marginTop="@*android:dimen/notification_2025_margin"
            android:minHeight="@dimen/notification_2025_guts_button_size"
            android:layout_gravity="center_vertical"
            android:theme="@style/Theme.SystemUI.Dialog"
            >
            <Button
@@ -201,21 +200,36 @@
                android:layout_height="wrap_content"
                android:layout_alignParentStart="true"
                android:gravity="center"
                android:minWidth="@dimen/notification_importance_toggle_size"
                android:minHeight="@dimen/notification_importance_toggle_size"
                android:minWidth="@dimen/notification_2025_min_tap_target_size"
                android:minHeight="@dimen/notification_2025_min_tap_target_size"
                android:maxWidth="200dp"
                android:layout_marginEnd="8dp"
                style="@style/Widget.Dialog.Button.BorderButton"
                />
            <Button
                android:id="@+id/turn_off_notifications"
                android:text="@string/inline_turn_off_notifications"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_toEndOf="@id/inline_dismiss"
                android:gravity="center"
                android:minWidth="@dimen/notification_2025_min_tap_target_size"
                android:minHeight="@dimen/notification_2025_min_tap_target_size"
                android:maxWidth="200dp"
                style="@style/Widget.Dialog.Button.BorderButton"/>
                style="@style/Widget.Dialog.Button.BorderButton"
                />
            <Button
                android:id="@+id/done"
                android:text="@string/inline_ok_button"
                android:text="@string/inline_done_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:gravity="center"
                android:minWidth="@dimen/notification_importance_toggle_size"
                android:minHeight="@dimen/notification_importance_toggle_size"
                android:minWidth="@dimen/notification_2025_min_tap_target_size"
                android:minHeight="@dimen/notification_2025_min_tap_target_size"
                android:maxWidth="125dp"
                style="@style/Widget.Dialog.Button"/>
                style="@style/Widget.Dialog.Button"
                />
        </RelativeLayout>
    </LinearLayout>
</com.android.systemui.statusbar.notification.row.BundledNotificationInfo>
+11 −0
Original line number Diff line number Diff line
@@ -35,6 +35,13 @@ class BundledNotificationInfo(context: Context?, attrs: AttributeSet?) :
    NotificationInfo(context, attrs) {

    override fun bindInlineControls() {
        val originalChannel =
            mINotificationManager.getNotificationChannel(
                mContext.packageName,
                mSbn.normalizedUserId,
                mSbn.packageName,
                mSbn.notification.channelId,
            )
        val enabled =
            mINotificationManager.isAdjustmentSupportedForPackage(
                mSbn.normalizedUserId,
@@ -79,6 +86,10 @@ class BundledNotificationInfo(context: Context?, attrs: AttributeSet?) :
        findViewById<TextView>(R.id.feature_summary)
            .setText(resources.getString(R.string.notification_guts_bundle_summary, mAppName))

        val turnOffButton = findViewById<View>(R.id.turn_off_notifications)
        turnOffButton.setOnClickListener(getTurnOffNotificationsClickListener(originalChannel))
        turnOffButton.visibility = if (turnOffButton.hasOnClickListeners()) VISIBLE else GONE

        val dismissButton = findViewById<View>(R.id.inline_dismiss)
        dismissButton.setOnClickListener(mOnCloseClickListener)
        dismissButton.visibility =
+48 −50
Original line number Diff line number Diff line
@@ -37,18 +37,17 @@ import android.view.WindowInsets.Type.statusBars
import android.view.WindowManager
import android.widget.TextView
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor
import javax.inject.Inject

private const val TAG = "ChannelDialogController"

/**
 * ChannelEditorDialogController is the controller for the dialog half-shelf
 * that allows users to quickly turn off channels. It is launched from the NotificationInfo
 * guts view and displays controls for toggling app notifications as well as up to 4 channels
 * from that app like so:
 * ChannelEditorDialogController is the controller for the dialog half-shelf that allows users to
 * quickly turn off channels. It is launched from the NotificationInfo guts view and displays
 * controls for toggling app notifications as well as up to 4 channels from that app like so:
 *
 * APP TOGGLE <on/off>
 * - Channel from which we launched <on/off>
@@ -57,7 +56,9 @@ private const val TAG = "ChannelDialogController"
 * - <on/off>
 */
@SysUISingleton
class ChannelEditorDialogController @Inject constructor(
class ChannelEditorDialogController
@Inject
constructor(
    private val shadeDialogContextInteractor: ShadeDialogContextInteractor,
    private val noMan: INotificationManager,
    private val dialogBuilder: ChannelEditorDialog.Builder,
@@ -77,8 +78,7 @@ class ChannelEditorDialogController @Inject constructor(
    var onFinishListener: OnChannelEditorDialogFinishedListener? = null

    // Channels handed to us from NotificationInfo
    @VisibleForTesting
    internal val channelList = mutableListOf<NotificationChannel>()
    @VisibleForTesting internal val channelList = mutableListOf<NotificationChannel>()

    // Map from NotificationChannel to importance
    private val edits = mutableMapOf<NotificationChannel, Int>()
@@ -87,21 +87,20 @@ class ChannelEditorDialogController @Inject constructor(
    private var appNotificationsCurrentlyEnabled: Boolean? = null

    // Keep a mapping of NotificationChannel.getGroup() to the actual group name for display
    @VisibleForTesting
    internal val groupNameLookup = hashMapOf<String, CharSequence>()
    @VisibleForTesting internal val groupNameLookup = hashMapOf<String, CharSequence>()
    private val channelGroupList = mutableListOf<NotificationChannelGroup>()

    /**
     * Give the controller all the information it needs to present the dialog
     * for a given app. Does a bunch of querying of NoMan, but won't present anything yet
     * Give the controller all the information it needs to present the dialog for a given app. Does
     * a bunch of querying of NoMan, but won't present anything yet
     */
    fun prepareDialogForApp(
        appName: String,
        packageName: String,
        uid: Int,
        channel: NotificationChannel,
        appIcon: Drawable,
        onSettingsClickListener: NotificationInfo.OnSettingsClickListener?
        appIcon: Drawable?,
        onSettingsClickListener: NotificationInfo.OnSettingsClickListener?,
    ) {
        this.appName = appName
        this.packageName = packageName
@@ -135,22 +134,23 @@ class ChannelEditorDialogController @Inject constructor(
        channelList.clear()
        if (DEFAULT_CHANNEL_ID != channel!!.id) {
            channelList.add(0, channel!!)
            channelList.addAll(getDisplayableChannels(channelGroupList.asSequence())
            channelList.addAll(
                getDisplayableChannels(channelGroupList.asSequence())
                    .filterNot { it.id == channel!!.id }
                    .distinct())
                    .distinct()
            )
        }
    }

    private fun getDisplayableChannels(
        groupList: Sequence<NotificationChannelGroup>
    ): Sequence<NotificationChannel> {
        val channels = groupList
                .flatMap { group ->
                    group.channels.asSequence()
        val channels =
            groupList.flatMap { group ->
                group.channels
                    .asSequence()
                    .sortedWith(compareBy { group.name?.toString() ?: group.id })
                            .filterNot { channel ->
                                channel.isImportanceLockedByCriticalDeviceFunction
                            }
                    .filterNot { channel -> channel.isImportanceLockedByCriticalDeviceFunction }
            }

        // TODO: sort these by avgSentWeekly, but for now let's just do alphabetical (why not)
@@ -164,9 +164,7 @@ class ChannelEditorDialogController @Inject constructor(
        dialog.show()
    }

    /**
     * Close the dialog without saving. For external callers
     */
    /** Close the dialog without saving. For external callers */
    fun close() {
        done()
    }
@@ -218,8 +216,8 @@ class ChannelEditorDialogController @Inject constructor(
    @Suppress("unchecked_cast")
    private fun fetchNotificationChannelGroups(): List<NotificationChannelGroup> {
        return try {
            noMan.getRecentBlockedNotificationChannelGroupsForPackage(packageName!!, appUid!!)
                    .list as? List<NotificationChannelGroup> ?: listOf()
            noMan.getRecentBlockedNotificationChannelGroupsForPackage(packageName!!, appUid!!).list
                as? List<NotificationChannelGroup> ?: listOf()
        } catch (e: Exception) {
            Log.e(TAG, "Error fetching channel groups", e)
            listOf()
@@ -311,7 +309,8 @@ class ChannelEditorDialogController @Inject constructor(
                setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
                setWindowAnimations(com.android.internal.R.style.Animation_InputMethod)

                attributes = attributes.apply {
                attributes =
                    attributes.apply {
                        format = PixelFormat.TRANSLUCENT
                        title = ChannelEditorDialogController::class.java.simpleName
                        gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
@@ -323,22 +322,21 @@ class ChannelEditorDialogController @Inject constructor(
        }
    }

    private val wmFlags = (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
            or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
            or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
    private val wmFlags =
        (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
            WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
}

class ChannelEditorDialog(context: Context, theme: Int) : Dialog(context, theme) {
    fun updateDoneButtonText(hasChanges: Boolean) {
        findViewById<TextView>(R.id.done_button)?.setText(
                if (hasChanges)
                    R.string.inline_ok_button
                else
                    R.string.inline_done_button)
        findViewById<TextView>(R.id.done_button)
            ?.setText(if (hasChanges) R.string.inline_ok_button else R.string.inline_done_button)
    }

    class Builder @Inject constructor() {
        private lateinit var context: Context

        fun setContext(context: Context): Builder {
            this.context = context
            return this
+4 −3
Original line number Diff line number Diff line
@@ -294,7 +294,8 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
        }

        View turnOffButton = findViewById(R.id.turn_off_notifications);
        turnOffButton.setOnClickListener(getTurnOffNotificationsClickListener());
        turnOffButton.setOnClickListener(
                getTurnOffNotificationsClickListener(mSingleNotificationChannel));
        turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonblockable
                ? VISIBLE : GONE);

@@ -512,13 +513,13 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
        return null;
    }

    private OnClickListener getTurnOffNotificationsClickListener() {
    OnClickListener getTurnOffNotificationsClickListener(NotificationChannel channel) {
        return ((View view) -> {
            if (!mPresentingChannelEditorDialog && mChannelEditorDialogController != null) {
                mPresentingChannelEditorDialog = true;

                mChannelEditorDialogController.prepareDialogForApp(mAppName, mPackageName, mAppUid,
                        mSingleNotificationChannel, mPkgIcon, mOnSettingsClickListener);
                        channel, mPkgIcon, mOnSettingsClickListener);
                mChannelEditorDialogController.setOnFinishListener(() -> {
                    mPresentingChannelEditorDialog = false;
                    mGutsContainer.closeControls(this, false);