Create notification channel interstitial shelf

From NotificationGuts, tapping "Turn off notifications" will now present
a half-shelf allowing the user to directly block up to 4 channels (the
one given from the notification guts + up to 3 other channels from
that app) or the app itself.

Test: visual (for now)
Bug: 130307442
Fixes: 131432719
Change-Id: I7e82928dfd56b9e25e5bef02607eede55b11d9e3
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 4b6306a..48a7495 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -15,6 +15,7 @@
 package com.android.systemui;
 
 import android.annotation.Nullable;
+import android.app.INotificationManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.hardware.SensorPrivacyManager;
@@ -68,6 +69,7 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
 import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.AutoHideController;
@@ -298,6 +300,8 @@
     @Inject Lazy<SensorPrivacyController> mSensorPrivacyController;
     @Inject Lazy<DumpController> mDumpController;
     @Inject Lazy<DockManager> mDockManager;
+    @Inject Lazy<ChannelEditorDialogController> mChannelEditorDialogController;
+    @Inject Lazy<INotificationManager> mINotificationManager;
 
     @Inject
     public Dependency() {
@@ -473,6 +477,8 @@
         mProviders.put(SensorPrivacyController.class, mSensorPrivacyController::get);
         mProviders.put(DumpController.class, mDumpController::get);
         mProviders.put(DockManager.class, mDockManager::get);
+        mProviders.put(ChannelEditorDialogController.class, mChannelEditorDialogController::get);
+        mProviders.put(INotificationManager.class, mINotificationManager::get);
 
         // TODO(b/118592525): to support multi-display , we start to add something which is
         //                    per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
index f649976..321206f 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
@@ -22,6 +22,7 @@
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 
 import android.annotation.Nullable;
+import android.app.INotificationManager;
 import android.content.Context;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.display.NightDisplayListener;
@@ -132,6 +133,14 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
     }
 
+    /** */
+    @Singleton
+    @Provides
+    public INotificationManager provideINotificationManager() {
+        return INotificationManager.Stub.asInterface(
+                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+    }
+
     @Singleton
     @Provides
     // Single instance of DisplayMetrics, gets updated by StatusBar, but can be used
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
new file mode 100644
index 0000000..a065f67
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2019 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.row
+
+import android.app.Dialog
+import android.app.INotificationManager
+import android.app.NotificationChannel
+import android.app.NotificationChannel.DEFAULT_CHANNEL_ID
+import android.app.NotificationChannelGroup
+import android.app.NotificationManager.IMPORTANCE_NONE
+import android.content.Context
+import android.graphics.Color
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.ColorDrawable
+import android.util.Log
+import android.view.Gravity
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.Window
+import android.view.WindowManager
+import android.widget.TextView
+import com.android.internal.annotations.VisibleForTesting
+
+import com.android.systemui.R
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+const val TAG = "ChannelDialogController"
+
+@Singleton
+class ChannelEditorDialogController @Inject constructor(
+    c: Context,
+    private val noMan: INotificationManager
+) {
+    val context: Context = c.applicationContext
+
+    lateinit var dialog: Dialog
+
+    private var appIcon: Drawable? = null
+    private var appUid: Int? = null
+    private var packageName: String? = null
+    private var appName: String? = null
+    private var onSettingsClickListener: NotificationInfo.OnSettingsClickListener? = null
+
+    // Channels handed to us from NotificationInfo
+    @VisibleForTesting
+    internal val providedChannels = mutableListOf<NotificationChannel>()
+
+    // Map from NotificationChannel to importance
+    private val edits = mutableMapOf<NotificationChannel, Int>()
+    var appNotificationsEnabled = true
+
+    // Keep a mapping of NotificationChannel.getGroup() to the actual group name for display
+    @VisibleForTesting
+    internal val groupNameLookup = hashMapOf<String, CharSequence>()
+    private val channelGroupList = mutableListOf<NotificationChannelGroup>()
+
+    fun prepareDialogForApp(
+        appName: String,
+        packageName: String,
+        uid: Int,
+        channels: Set<NotificationChannel>,
+        appIcon: Drawable,
+        onSettingsClickListener: NotificationInfo.OnSettingsClickListener
+    ) {
+        this.appName = appName
+        this.packageName = packageName
+        this.appUid = uid
+        this.appIcon = appIcon
+        this.appNotificationsEnabled = checkAreAppNotificationsOn()
+        this.onSettingsClickListener = onSettingsClickListener
+
+        channelGroupList.clear()
+        channelGroupList.addAll(fetchNotificationChannelGroups())
+        buildGroupNameLookup()
+        padToFourChannels(channels)
+    }
+
+    private fun buildGroupNameLookup() {
+        channelGroupList.forEach { group ->
+            if (group.id != null) {
+                groupNameLookup[group.id] = group.name
+            }
+        }
+    }
+
+    private fun padToFourChannels(channels: Set<NotificationChannel>) {
+        providedChannels.clear()
+        // First, add all of the given channels
+        providedChannels.addAll(channels.asSequence().take(4))
+
+        // Then pad to 4 if we haven't been given that many
+        providedChannels.addAll(getDisplayableChannels(channelGroupList.asSequence())
+                .filterNot { providedChannels.contains(it) }
+                .distinct()
+                .take(4 - providedChannels.size))
+
+        // If we only got one channel and it has the default miscellaneous tag, then we actually
+        // are looking at an app with a targetSdk <= O, and it doesn't make much sense to show the
+        // channel
+        if (providedChannels.size == 1 && DEFAULT_CHANNEL_ID == providedChannels[0].id) {
+            providedChannels.clear()
+        }
+    }
+
+    private fun getDisplayableChannels(
+        groupList: Sequence<NotificationChannelGroup>
+    ): Sequence<NotificationChannel> {
+
+        val channels = groupList
+                .flatMap { group ->
+                    group.channels.asSequence().filterNot { channel ->
+                        channel.isImportanceLockedByOEM
+                                || channel.importance == IMPORTANCE_NONE
+                                || channel.isImportanceLockedByCriticalDeviceFunction
+                    }
+                }
+
+        // TODO: sort these by avgSentWeekly, but for now let's just do alphabetical (why not)
+        return channels.sortedWith(compareBy { it.name?.toString() ?: it.id })
+    }
+
+    fun show() {
+        initDialog()
+        dialog.show()
+    }
+
+    private fun done() {
+        resetState()
+        dialog.dismiss()
+    }
+
+    private fun resetState() {
+        appIcon = null
+        appUid = null
+        packageName = null
+        appName = null
+
+        edits.clear()
+        providedChannels.clear()
+        groupNameLookup.clear()
+    }
+
+    fun groupNameForId(groupId: String?): CharSequence {
+        return groupNameLookup[groupId] ?: ""
+    }
+
+    fun proposeEditForChannel(channel: NotificationChannel, edit: Int) {
+        if (channel.importance == edit) {
+            edits.remove(channel)
+        } else {
+            edits[channel] = edit
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private fun fetchNotificationChannelGroups(): List<NotificationChannelGroup> {
+        return try {
+            noMan.getNotificationChannelGroupsForPackage(packageName!!, appUid!!, false)
+                    .list as? List<NotificationChannelGroup> ?: listOf()
+        } catch (e: Exception) {
+            Log.e(TAG, "Error fetching channel groups", e)
+            listOf()
+        }
+    }
+
+    private fun checkAreAppNotificationsOn(): Boolean {
+        return try {
+            noMan.areNotificationsEnabledForPackage(packageName!!, appUid!!)
+        } catch (e: Exception) {
+            Log.e(TAG, "Error calling NoMan", e)
+            false
+        }
+    }
+
+    private fun applyAppNotificationsOn(b: Boolean) {
+        try {
+            noMan.setNotificationsEnabledForPackage(packageName!!, appUid!!, b)
+        } catch (e: Exception) {
+            Log.e(TAG, "Error calling NoMan", e)
+        }
+    }
+
+    private fun setChannelImportance(channel: NotificationChannel, importance: Int) {
+        try {
+            channel.importance = importance
+            noMan.updateNotificationChannelForPackage(packageName!!, appUid!!, channel)
+        } catch (e: Exception) {
+            Log.e(TAG, "Unable to update notification importance", e)
+        }
+    }
+
+    @VisibleForTesting
+    fun apply() {
+        for ((channel, importance) in edits) {
+            if (channel.importance != importance) {
+                setChannelImportance(channel, importance)
+            }
+        }
+
+        if (appNotificationsEnabled != checkAreAppNotificationsOn()) {
+            applyAppNotificationsOn(appNotificationsEnabled)
+        }
+    }
+
+    private fun initDialog() {
+        dialog = Dialog(context)
+
+        dialog.window?.requestFeature(Window.FEATURE_NO_TITLE)
+        dialog.apply {
+            setContentView(R.layout.notif_half_shelf)
+            setCanceledOnTouchOutside(true)
+            findViewById<ChannelEditorListView>(R.id.half_shelf_container).apply {
+                controller = this@ChannelEditorDialogController
+                appIcon = this@ChannelEditorDialogController.appIcon
+                appName = this@ChannelEditorDialogController.appName
+                channels = providedChannels
+            }
+
+            findViewById<TextView>(R.id.done_button)?.setOnClickListener {
+                apply()
+                done()
+            }
+
+            findViewById<TextView>(R.id.see_more_button)?.setOnClickListener {
+                onSettingsClickListener?.onClick(it, null, appUid!!)
+                dismiss()
+            }
+
+            window?.apply {
+                setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+                addFlags(wmFlags)
+                setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL)
+                setWindowAnimations(com.android.internal.R.style.Animation_InputMethod)
+
+                attributes = attributes.apply {
+                    format = PixelFormat.TRANSLUCENT
+                    title = ChannelEditorDialogController::class.java.simpleName
+                    gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+                    width = MATCH_PARENT
+                    height = WRAP_CONTENT
+                }
+            }
+        }
+    }
+
+    private val wmFlags = (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
+            or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+            or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+            or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
new file mode 100644
index 0000000..7fea30c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2019 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.row
+
+import android.app.NotificationChannel
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_NONE
+import android.app.NotificationManager.IMPORTANCE_UNSPECIFIED
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.text.TextUtils
+import android.transition.AutoTransition
+import android.transition.TransitionManager
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.Switch
+import android.widget.TextView
+
+import com.android.systemui.R
+
+/**
+ * Half-shelf for notification channel controls
+ */
+class ChannelEditorListView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
+    lateinit var controller: ChannelEditorDialogController
+    var appIcon: Drawable? = null
+    var appName: String? = null
+    var channels = mutableListOf<NotificationChannel>()
+        set(newValue) {
+            field = newValue
+            updateRows()
+        }
+
+    // The first row is for the entire app
+    private lateinit var appControlRow: AppControlView
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+
+        appControlRow = findViewById(R.id.app_control)
+    }
+
+    private fun updateRows() {
+        val enabled = controller.appNotificationsEnabled
+
+        val transition = AutoTransition()
+        transition.duration = 200
+        TransitionManager.beginDelayedTransition(this, transition)
+
+        // Remove any rows
+        val n = childCount
+        for (i in n.downTo(0)) {
+            val child = getChildAt(i)
+            if (child is ChannelRow) {
+                removeView(child)
+            }
+        }
+
+        updateAppControlRow(enabled)
+
+        if (enabled) {
+            val inflater = LayoutInflater.from(context)
+            for (channel in channels) {
+                addChannelRow(channel, inflater)
+            }
+        }
+    }
+
+    private fun addChannelRow(channel: NotificationChannel, inflater: LayoutInflater) {
+        val row = inflater.inflate(R.layout.notif_half_shelf_row, null) as ChannelRow
+        row.controller = controller
+        row.channel = channel
+        addView(row)
+    }
+
+    private fun updateAppControlRow(enabled: Boolean) {
+        appControlRow.iconView.setImageDrawable(appIcon)
+        appControlRow.channelName.text = context.resources
+                .getString(R.string.notification_channel_dialog_title, appName)
+        appControlRow.switch.isChecked = enabled
+        appControlRow.switch.setOnCheckedChangeListener { _, b ->
+            controller.appNotificationsEnabled = b
+            updateRows()
+        }
+    }
+}
+
+class AppControlView(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
+    lateinit var iconView: ImageView
+    lateinit var channelName: TextView
+    lateinit var switch: Switch
+
+    override fun onFinishInflate() {
+        iconView = findViewById(R.id.icon)
+        channelName = findViewById(R.id.app_name)
+        switch = findViewById(R.id.toggle)
+    }
+}
+
+class ChannelRow(c: Context, attrs: AttributeSet) : LinearLayout(c, attrs) {
+
+    lateinit var controller: ChannelEditorDialogController
+    private lateinit var iconView: ImageView
+    private lateinit var channelName: TextView
+    private lateinit var channelDescription: TextView
+    private lateinit var switch: Switch
+    var gentle = false
+
+    var channel: NotificationChannel? = null
+        set(newValue) {
+            field = newValue
+            updateImportance()
+            updateViews()
+        }
+
+    override fun onFinishInflate() {
+        iconView = findViewById(R.id.icon)
+        channelName = findViewById(R.id.channel_name)
+        channelDescription = findViewById(R.id.channel_description)
+        switch = findViewById(R.id.toggle)
+        switch.setOnCheckedChangeListener { _, b ->
+            channel?.let {
+                controller.proposeEditForChannel(it, if (b) it.importance else IMPORTANCE_NONE)
+            }
+        }
+    }
+
+    private fun updateViews() {
+        val nc = channel ?: return
+
+        iconView.setImageDrawable(
+                if (gentle)
+                    context.getDrawable(R.drawable.ic_notification_gentle)
+                else context.getDrawable(R.drawable.ic_notification_interruptive))
+
+        channelName.text = nc.name ?: "(missing)"
+
+        nc.group?.let { groupId ->
+            channelDescription.text = controller.groupNameForId(groupId)
+        }
+
+        if (nc.group == null || TextUtils.isEmpty(channelDescription.text)) {
+            channelDescription.visibility = View.GONE
+        } else {
+            channelDescription.visibility = View.VISIBLE
+        }
+
+        switch.isChecked = nc.importance != IMPORTANCE_NONE
+    }
+
+    private fun updateImportance() {
+        val importance = channel?.importance ?: 0
+        gentle = importance != IMPORTANCE_UNSPECIFIED && importance < IMPORTANCE_DEFAULT
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 24c7b291..b3ca88f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2398,6 +2398,14 @@
      * it's a summary notification).
      */
     public int getNumUniqueChannels() {
+        return getUniqueChannels().size();
+    }
+
+    /**
+     * Returns the channels covered by the notification row (including its children if
+     * it's a summary notification).
+     */
+    public ArraySet<NotificationChannel> getUniqueChannels() {
         ArraySet<NotificationChannel> channels = new ArraySet<>();
 
         channels.add(mEntry.channel);
@@ -2417,7 +2425,8 @@
                 }
             }
         }
-        return channels.size();
+
+        return channels;
     }
 
     public void updateChildrenHeaderAppearance() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index faa7898..f15d6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -18,7 +18,6 @@
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_RECORD_AUDIO;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
-import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 
 import android.app.INotificationManager;
 import android.app.NotificationChannel;
@@ -26,6 +25,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -125,13 +125,18 @@
      * Sends an intent to open the notification settings for a particular package and optional
      * channel.
      */
+    public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
     private void startAppNotificationSettingsActivity(String packageName, final int appUid,
             final NotificationChannel channel, ExpandableNotificationRow row) {
         final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
         intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
         intent.putExtra(Settings.EXTRA_APP_UID, appUid);
+
         if (channel != null) {
+            final Bundle args = new Bundle();
             intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
+            args.putString(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
+            intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
         }
         mNotificationActivityStarter.startNotificationGutsIntent(intent, appUid, row);
     }
@@ -301,7 +306,7 @@
                 iNotificationManager,
                 packageName,
                 row.getEntry().channel,
-                row.getNumUniqueChannels(),
+                row.getUniqueChannels(),
                 sbn,
                 mCheckSaveListener,
                 onSettingsClick,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index d49f168..942f566 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -63,6 +63,7 @@
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * The guts of a notification revealed when performing a long press. This also houses the blocking
@@ -96,12 +97,14 @@
     private INotificationManager mINotificationManager;
     private PackageManager mPm;
     private MetricsLogger mMetricsLogger;
+    private ChannelEditorDialogController mChannelEditorDialogController;
 
     private String mPackageName;
     private String mAppName;
     private int mAppUid;
     private String mDelegatePkg;
     private int mNumUniqueChannelsInRow;
+    private Set<NotificationChannel> mUniqueChannelsInRow;
     private NotificationChannel mSingleNotificationChannel;
     private int mStartingChannelImportance;
     private boolean mWasShownHighPriority;
@@ -126,6 +129,7 @@
     private NotificationGuts mGutsContainer;
     private Drawable mSelectedBackground;
     private Drawable mUnselectedBackground;
+    private Drawable mPkgIcon;
 
     /** Whether this view is being shown as part of the blocking helper. */
     private boolean mIsForBlockingHelper;
@@ -233,7 +237,7 @@
             final INotificationManager iNotificationManager,
             final String pkg,
             final NotificationChannel notificationChannel,
-            final int numUniqueChannelsInRow,
+            final Set<NotificationChannel> uniqueChannelsInRow,
             final StatusBarNotification sbn,
             final CheckSaveListener checkSaveListener,
             final OnSettingsClickListener onSettingsClick,
@@ -244,7 +248,7 @@
             boolean wasShownHighPriority)
             throws RemoteException {
         bindNotification(pm, iNotificationManager, pkg, notificationChannel,
-                numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
+                uniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
                 onAppSettingsClick, isDeviceProvisioned, isNonblockable,
                 false /* isBlockingHelper */,
                 importance, wasShownHighPriority);
@@ -255,7 +259,7 @@
             INotificationManager iNotificationManager,
             String pkg,
             NotificationChannel notificationChannel,
-            int numUniqueChannelsInRow,
+            Set<NotificationChannel> uniqueChannelsInRow,
             StatusBarNotification sbn,
             CheckSaveListener checkSaveListener,
             OnSettingsClickListener onSettingsClick,
@@ -268,8 +272,10 @@
             throws RemoteException {
         mINotificationManager = iNotificationManager;
         mMetricsLogger = Dependency.get(MetricsLogger.class);
+        mChannelEditorDialogController = Dependency.get(ChannelEditorDialogController.class);
         mPackageName = pkg;
-        mNumUniqueChannelsInRow = numUniqueChannelsInRow;
+        mUniqueChannelsInRow = uniqueChannelsInRow;
+        mNumUniqueChannelsInRow = uniqueChannelsInRow.size();
         mSbn = sbn;
         mPm = pm;
         mAppSettingsClickListener = onAppSettingsClick;
@@ -355,7 +361,7 @@
         }
 
         View turnOffButton = findViewById(R.id.turn_off_notifications);
-        turnOffButton.setOnClickListener(getSettingsOnClickListener());
+        turnOffButton.setOnClickListener(getTurnOffNotificationsClickListener());
         turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonblockable
                 ? VISIBLE : GONE);
 
@@ -379,7 +385,7 @@
 
     private void bindHeader() {
         // Package name
-        Drawable pkgicon = null;
+        mPkgIcon = null;
         ApplicationInfo info;
         try {
             info = mPm.getApplicationInfo(
@@ -390,13 +396,13 @@
                             | PackageManager.MATCH_DIRECT_BOOT_AWARE);
             if (info != null) {
                 mAppName = String.valueOf(mPm.getApplicationLabel(info));
-                pkgicon = mPm.getApplicationIcon(info);
+                mPkgIcon = mPm.getApplicationIcon(info);
             }
         } catch (PackageManager.NameNotFoundException e) {
             // app is gone, just show package name and generic icon
-            pkgicon = mPm.getDefaultActivityIcon();
+            mPkgIcon = mPm.getDefaultActivityIcon();
         }
-        ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon);
+        ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(mPkgIcon);
         ((TextView) findViewById(R.id.pkgname)).setText(mAppName);
 
         // Delegate
@@ -437,6 +443,16 @@
         return null;
     }
 
+    private OnClickListener getTurnOffNotificationsClickListener() {
+        return ((View view) -> {
+            if (mChannelEditorDialogController != null) {
+                mChannelEditorDialogController.prepareDialogForApp(mAppName, mPackageName, mAppUid,
+                        mUniqueChannelsInRow, mPkgIcon, mOnSettingsClickListener);
+                mChannelEditorDialogController.show();
+            }
+        });
+    }
+
     private void bindChannelDetails() throws RemoteException {
         bindName();
         bindGroup();