Merge "Implement a separate controller for ring volume" into tm-qpr-dev am: ee56e95f90

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/20749598

Change-Id: I1dbbde115a9e92ecc1b602607827d51ec2078102
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/res/xml/sound_settings.xml b/res/xml/sound_settings.xml
index 914ce72..a84b0ae 100644
--- a/res/xml/sound_settings.xml
+++ b/res/xml/sound_settings.xml
@@ -72,6 +72,14 @@
         android:order="-160"
         settings:controller="com.android.settings.notification.RingVolumePreferenceController"/>
 
+    <!-- Separate Ring volume -->
+    <com.android.settings.notification.VolumeSeekBarPreference
+        android:key="separate_ring_volume"
+        android:icon="@drawable/ic_ring_volume"
+        android:title="@string/separate_ring_volume_option_title"
+        android:order="-155"
+        settings:controller="com.android.settings.notification.SeparateRingVolumePreferenceController"/>
+
     <!-- Notification volume -->
     <com.android.settings.notification.VolumeSeekBarPreference
         android:key="notification_volume"
@@ -88,7 +96,7 @@
         android:title="@string/alarm_volume_option_title"
         android:order="-140"
         settings:controller="com.android.settings.notification.AlarmVolumePreferenceController"/>
-x
+
     <!-- TODO(b/174964721): make this a PrimarySwitchPreference -->
     <!-- Interruptions -->
     <com.android.settingslib.RestrictedPreference
diff --git a/src/com/android/settings/notification/NotificationVolumePreferenceController.java b/src/com/android/settings/notification/NotificationVolumePreferenceController.java
index 112debc..c7e7307 100644
--- a/src/com/android/settings/notification/NotificationVolumePreferenceController.java
+++ b/src/com/android/settings/notification/NotificationVolumePreferenceController.java
@@ -17,10 +17,8 @@
 package com.android.settings.notification;
 
 import android.app.ActivityThread;
-import android.app.INotificationManager;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -28,57 +26,42 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.ServiceManager;
-import android.os.Vibrator;
 import android.provider.DeviceConfig;
 import android.service.notification.NotificationListenerService;
-import android.text.TextUtils;
-import android.util.Log;
 
 import androidx.lifecycle.OnLifecycleEvent;
 import androidx.preference.PreferenceScreen;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
-import java.util.Objects;
 import java.util.Set;
 
 /**
  * Update notification volume icon in Settings in response to user adjusting volume.
  */
-public class NotificationVolumePreferenceController extends VolumeSeekBarPreferenceController {
+public class NotificationVolumePreferenceController extends
+        RingerModeAffectedVolumePreferenceController {
 
-    private static final String TAG = "NotificationVolumePreferenceController";
     private static final String KEY_NOTIFICATION_VOLUME = "notification_volume";
-    private static final boolean CONFIG_DEFAULT_VAL = false;
-    private boolean mSeparateNotification;
+    private static final String TAG = "NotificationVolumePreferenceController";
 
-    private Vibrator mVibrator;
-    private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
-    private ComponentName mSuppressor;
     private final RingReceiver mReceiver = new RingReceiver();
     private final H mHandler = new H();
-    private INotificationManager mNoMan;
-    private int mMuteIcon;
-    private final int mNormalIconId =  R.drawable.ic_notifications;
-    private final int mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
-    private final int mSilentIconId = R.drawable.ic_notifications_off_24dp;
+
 
     public NotificationVolumePreferenceController(Context context) {
         this(context, KEY_NOTIFICATION_VOLUME);
     }
 
     public NotificationVolumePreferenceController(Context context, String key) {
-        super(context, key);
+        super(context, key, TAG);
 
-        mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
-        if (mVibrator != null && !mVibrator.hasVibrator()) {
-            mVibrator = null;
-        }
+        mNormalIconId =  R.drawable.ic_notifications;
+        mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
+        mSilentIconId = R.drawable.ic_notifications_off_24dp;
 
         updateRingerMode();
     }
@@ -94,13 +77,12 @@
         if (mPreference == null) {
             setupVolPreference(screen);
         }
-        mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
+        mSeparateNotification = isSeparateNotificationConfigEnabled();
         if (mPreference != null) {
             mPreference.setVisible(getAvailabilityStatus() == AVAILABLE);
         }
         updateEffectsSuppressor();
-        updatePreferenceIconAndSliderState();
+        selectPreferenceIconState();
     }
 
     /**
@@ -110,8 +92,7 @@
         Set<String> changeSet = properties.getKeyset();
 
         if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
-            boolean newVal = properties.getBoolean(
-                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
+            boolean newVal = isSeparateNotificationConfigEnabled();
             if (newVal != mSeparateNotification) {
                 mSeparateNotification = newVal;
                 // manually hiding the preference because being unavailable does not do the job
@@ -143,8 +124,7 @@
 
     @Override
     public int getAvailabilityStatus() {
-        boolean separateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+        boolean separateNotification = isSeparateNotificationConfigEnabled();
 
         return mContext.getResources().getBoolean(R.bool.config_show_notification_volume)
                 && !mHelper.isSingleVolume()
@@ -153,71 +133,17 @@
     }
 
     @Override
-    public boolean isSliceable() {
-        return TextUtils.equals(getPreferenceKey(), KEY_NOTIFICATION_VOLUME);
-    }
-
-    @Override
-    public boolean isPublicSlice() {
-        return true;
-    }
-
-    @Override
     public String getPreferenceKey() {
         return KEY_NOTIFICATION_VOLUME;
     }
 
     @Override
-    public boolean useDynamicSliceSummary() {
-        return true;
-    }
-
-    @Override
     public int getAudioStream() {
         return AudioManager.STREAM_NOTIFICATION;
     }
 
     @Override
-    public int getMuteIcon() {
-        return mMuteIcon;
-    }
-
-    private void updateRingerMode() {
-        final int ringerMode = mHelper.getRingerModeInternal();
-        if (mRingerMode == ringerMode) return;
-        mRingerMode = ringerMode;
-        updatePreferenceIconAndSliderState();
-    }
-
-    private void updateEffectsSuppressor() {
-        final ComponentName suppressor = NotificationManager.from(mContext).getEffectsSuppressor();
-        if (Objects.equals(suppressor, mSuppressor)) return;
-
-        if (mNoMan == null) {
-            mNoMan = INotificationManager.Stub.asInterface(
-                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-        }
-
-        final int hints;
-        try {
-            hints = mNoMan.getHintsFromListenerNoToken();
-        } catch (android.os.RemoteException exception) {
-            Log.w(TAG, "updateEffectsSuppressor: " + exception.getLocalizedMessage());
-            return;
-        }
-
-        if (hintsMatch(hints)) {
-
-            mSuppressor = suppressor;
-            if (mPreference != null) {
-                final String text = SuppressorHelper.getSuppressionText(mContext, suppressor);
-                mPreference.setSuppressionText(text);
-            }
-        }
-    }
-
-    @VisibleForTesting
-    boolean hintsMatch(int hints) {
+    protected boolean hintsMatch(int hints) {
         boolean allEffectsDisabled =
                 (hints & NotificationListenerService.HINT_HOST_DISABLE_EFFECTS) != 0;
         boolean notificationEffectsDisabled =
@@ -226,20 +152,18 @@
         return allEffectsDisabled || notificationEffectsDisabled;
     }
 
-    private void updatePreferenceIconAndSliderState() {
+    @Override
+    protected void selectPreferenceIconState() {
         if (mPreference != null) {
             if (mVibrator != null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
                 mMuteIcon = mVibrateIconId;
                 mPreference.showIcon(mVibrateIconId);
-                mPreference.setEnabled(false);
 
             } else if (mRingerMode == AudioManager.RINGER_MODE_SILENT
                     || mVibrator == null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
                 mMuteIcon = mSilentIconId;
                 mPreference.showIcon(mSilentIconId);
-                mPreference.setEnabled(false);
             } else { // ringmode normal: could be that we are still silent
-                mPreference.setEnabled(true);
                 if (mHelper.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0) {
                     // ring is in normal, but notification is in silent
                     mMuteIcon = mSilentIconId;
@@ -270,7 +194,7 @@
                     updateRingerMode();
                     break;
                 case NOTIFICATION_VOLUME_CHANGED:
-                    updatePreferenceIconAndSliderState();
+                    selectPreferenceIconState();
                     break;
             }
         }
diff --git a/src/com/android/settings/notification/RingVolumePreferenceController.java b/src/com/android/settings/notification/RingVolumePreferenceController.java
index 7fdb1e1..1399e71 100644
--- a/src/com/android/settings/notification/RingVolumePreferenceController.java
+++ b/src/com/android/settings/notification/RingVolumePreferenceController.java
@@ -17,10 +17,8 @@
 package com.android.settings.notification;
 
 import android.app.ActivityThread;
-import android.app.INotificationManager;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -28,118 +26,59 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.ServiceManager;
-import android.os.Vibrator;
 import android.provider.DeviceConfig;
 import android.service.notification.NotificationListenerService;
-import android.text.TextUtils;
-import android.util.Log;
 
 import androidx.lifecycle.OnLifecycleEvent;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.settings.R;
 import com.android.settings.Utils;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
-import java.util.Objects;
 import java.util.Set;
 
 /**
- * This slider can represent both ring and notification, if the corresponding streams are aliased,
- * and only ring if the streams are not aliased.
+ * This slider represents both ring and notification
  */
-public class RingVolumePreferenceController extends VolumeSeekBarPreferenceController {
+public class RingVolumePreferenceController extends
+        RingerModeAffectedVolumePreferenceController {
 
-    private static final String TAG = "RingVolumePreferenceController";
     private static final String KEY_RING_VOLUME = "ring_volume";
+    private static final String TAG = "RingVolumePreferenceController";
 
-    private Vibrator mVibrator;
-    private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
-    private ComponentName mSuppressor;
     private final RingReceiver mReceiver = new RingReceiver();
     private final H mHandler = new H();
 
-    private int mMuteIcon;
-
-    private int mNormalIconId;
-    @VisibleForTesting
-    int mVibrateIconId;
-    @VisibleForTesting
-    int mSilentIconId;
-
-    @VisibleForTesting
-    int mTitleId;
-
-    private boolean mSeparateNotification;
-
-    private INotificationManager mNoMan;
-
-    private static final boolean CONFIG_DEFAULT_VAL = false;
-
     public RingVolumePreferenceController(Context context) {
         this(context, KEY_RING_VOLUME);
     }
 
     public RingVolumePreferenceController(Context context, String key) {
-        super(context, key);
-        mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
-        if (mVibrator != null && !mVibrator.hasVibrator()) {
-            mVibrator = null;
-        }
-        mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
-        loadPreferenceIconResources(mSeparateNotification);
-        updateRingerMode();
-    }
+        super(context, key, TAG);
 
-    private void loadPreferenceIconResources(boolean separateNotification) {
-        if (separateNotification) {
-            mTitleId = R.string.separate_ring_volume_option_title;
-            mNormalIconId = R.drawable.ic_ring_volume;
-            mSilentIconId = R.drawable.ic_ring_volume_off;
-        } else {
-            mTitleId = R.string.ring_volume_option_title;
-            mNormalIconId = R.drawable.ic_notifications;
-            mSilentIconId = R.drawable.ic_notifications_off_24dp;
-        }
-        // todo: set a distinct vibrate icon for ring vs notification
+        mNormalIconId = R.drawable.ic_notifications;
         mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
+        mSilentIconId = R.drawable.ic_notifications_off_24dp;
+
+        mSeparateNotification = isSeparateNotificationConfigEnabled();
+        updateRingerMode();
     }
 
     /**
      * As the responsibility of this slider changes, so should its title & icon
      */
-    public void onDeviceConfigChange(DeviceConfig.Properties properties) {
+    private void onDeviceConfigChange(DeviceConfig.Properties properties) {
         Set<String> changeSet = properties.getKeyset();
         if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
             boolean valueUpdated = readSeparateNotificationVolumeConfig();
             if (valueUpdated) {
                 updateEffectsSuppressor();
                 selectPreferenceIconState();
-                setPreferenceTitle();
             }
         }
     }
 
-    /**
-     * side effect: updates the cached value of the config, and also the icon
-     * @return has the config changed?
-     */
-    private boolean readSeparateNotificationVolumeConfig() {
-        boolean newVal = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
-
-        boolean valueUpdated = newVal != mSeparateNotification;
-        if (valueUpdated) {
-            mSeparateNotification = newVal;
-            loadPreferenceIconResources(newVal);
-        }
-
-        return valueUpdated;
-    }
-
     @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
     @Override
     public void onResume() {
@@ -150,7 +89,10 @@
                 ActivityThread.currentApplication().getMainExecutor(), this::onDeviceConfigChange);
         updateEffectsSuppressor();
         selectPreferenceIconState();
-        setPreferenceTitle();
+
+        if (mPreference != null) {
+            mPreference.setVisible(getAvailabilityStatus() == AVAILABLE);
+        }
     }
 
     @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
@@ -168,113 +110,27 @@
 
     @Override
     public int getAvailabilityStatus() {
-        return Utils.isVoiceCapable(mContext) && !mHelper.isSingleVolume()
+        boolean separateNotification = isSeparateNotificationConfigEnabled();
+
+        return !separateNotification && Utils.isVoiceCapable(mContext) && !mHelper.isSingleVolume()
                 ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
     }
 
     @Override
-    public boolean isSliceable() {
-        return TextUtils.equals(getPreferenceKey(), KEY_RING_VOLUME);
-    }
-
-    @Override
-    public boolean isPublicSlice() {
-        return true;
-    }
-
-    @Override
-    public boolean useDynamicSliceSummary() {
-        return true;
-    }
-
-    @Override
     public int getAudioStream() {
         return AudioManager.STREAM_RING;
     }
 
     @Override
-    public int getMuteIcon() {
-        return mMuteIcon;
-    }
+    protected boolean hintsMatch(int hints) {
+        boolean notificationSeparated = isSeparateNotificationConfigEnabled();
 
-    @VisibleForTesting
-    void updateRingerMode() {
-        final int ringerMode = mHelper.getRingerModeInternal();
-        if (mRingerMode == ringerMode) return;
-        mRingerMode = ringerMode;
-        selectPreferenceIconState();
-    }
-
-    private void updateEffectsSuppressor() {
-        final ComponentName suppressor = NotificationManager.from(mContext).getEffectsSuppressor();
-        if (Objects.equals(suppressor, mSuppressor)) return;
-
-        if (mNoMan == null) {
-            mNoMan = INotificationManager.Stub.asInterface(
-                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-        }
-
-        final int hints;
-        try {
-            hints = mNoMan.getHintsFromListenerNoToken();
-        } catch (android.os.RemoteException ex) {
-            Log.w(TAG, "updateEffectsSuppressor: " + ex.getMessage());
-            return;
-        }
-
-        if (hintsMatch(hints, DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false))) {
-            mSuppressor = suppressor;
-            if (mPreference != null) {
-                final String text = SuppressorHelper.getSuppressionText(mContext, suppressor);
-                mPreference.setSuppressionText(text);
-            }
-        }
-    }
-
-    @VisibleForTesting
-    boolean hintsMatch(int hints, boolean notificationSeparated) {
         return (hints & NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS) != 0
                 || (hints & NotificationListenerService.HINT_HOST_DISABLE_EFFECTS) != 0
                 || ((hints & NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS)
                 != 0 && !notificationSeparated);
     }
 
-    @VisibleForTesting
-    void setPreference(VolumeSeekBarPreference volumeSeekBarPreference) {
-        mPreference = volumeSeekBarPreference;
-    }
-
-    @VisibleForTesting
-    void setVibrator(Vibrator vibrator) {
-        mVibrator = vibrator;
-    }
-
-    private void selectPreferenceIconState() {
-        if (mPreference != null) {
-            if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
-                mPreference.showIcon(mNormalIconId);
-            } else {
-                if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE && mVibrator != null) {
-                    mMuteIcon = mVibrateIconId;
-                } else {
-                    mMuteIcon = mSilentIconId;
-                }
-                mPreference.showIcon(mMuteIcon);
-            }
-        }
-    }
-
-    /**
-     * This slider can represent both ring and notification, or only ring.
-     * Note: This cannot be used in the constructor, as the reference to preference object would
-     * still be null.
-     */
-    private void setPreferenceTitle() {
-        if (mPreference != null) {
-            mPreference.setTitle(mTitleId);
-        }
-    }
 
     private final class H extends Handler {
         private static final int UPDATE_EFFECTS_SUPPRESSOR = 1;
diff --git a/src/com/android/settings/notification/RingerModeAffectedVolumePreferenceController.java b/src/com/android/settings/notification/RingerModeAffectedVolumePreferenceController.java
new file mode 100644
index 0000000..e792d53
--- /dev/null
+++ b/src/com/android/settings/notification/RingerModeAffectedVolumePreferenceController.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2022 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.settings.notification;
+
+import android.app.INotificationManager;
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.ServiceManager;
+import android.os.Vibrator;
+import android.provider.DeviceConfig;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+
+import java.util.Objects;
+
+/**
+ * Shared functionality and interfaces for volume controllers whose state can change by ringer mode
+ */
+public abstract class RingerModeAffectedVolumePreferenceController extends
+        VolumeSeekBarPreferenceController {
+
+    private final String mTag;
+
+    protected int mNormalIconId;
+    protected int mVibrateIconId;
+    protected int mSilentIconId;
+    protected int mMuteIcon;
+
+    protected Vibrator mVibrator;
+    protected int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
+    protected ComponentName mSuppressor;
+    protected boolean mSeparateNotification;
+    protected INotificationManager mNoMan;
+
+    private static final boolean CONFIG_SEPARATE_NOTIFICATION_DEFAULT_VAL = false;
+
+    public RingerModeAffectedVolumePreferenceController(Context context, String key, String tag) {
+        super(context, key);
+        mTag = tag;
+        mVibrator = mContext.getSystemService(Vibrator.class);
+        if (mVibrator != null && !mVibrator.hasVibrator()) {
+            mVibrator = null;
+        }
+    }
+
+    protected void updateEffectsSuppressor() {
+        final ComponentName suppressor = NotificationManager.from(mContext).getEffectsSuppressor();
+        if (Objects.equals(suppressor, mSuppressor)) return;
+
+        if (mNoMan == null) {
+            mNoMan = INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+        }
+
+        final int hints;
+        try {
+            hints = mNoMan.getHintsFromListenerNoToken();
+        } catch (android.os.RemoteException ex) {
+            Log.w(mTag, "updateEffectsSuppressor: " + ex.getMessage());
+            return;
+        }
+
+        if (hintsMatch(hints)) {
+            mSuppressor = suppressor;
+            if (mPreference != null) {
+                final String text = SuppressorHelper.getSuppressionText(mContext, suppressor);
+                mPreference.setSuppressionText(text);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void setPreference(VolumeSeekBarPreference volumeSeekBarPreference) {
+        mPreference = volumeSeekBarPreference;
+    }
+
+    @VisibleForTesting
+    void setVibrator(Vibrator vibrator) {
+        mVibrator = vibrator;
+    }
+
+    @Override
+    public boolean isSliceable() {
+        return true;
+    }
+
+    @Override
+    public boolean isPublicSlice() {
+        return true;
+    }
+
+    @Override
+    public boolean useDynamicSliceSummary() {
+        return true;
+    }
+
+    @Override
+    public int getMuteIcon() {
+        return mMuteIcon;
+    }
+
+    protected boolean isSeparateNotificationConfigEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION,
+                CONFIG_SEPARATE_NOTIFICATION_DEFAULT_VAL);
+    }
+
+    /**
+     * side effect: updates the cached value of the config
+     * @return has the config changed?
+     */
+    protected boolean readSeparateNotificationVolumeConfig() {
+        boolean newVal = isSeparateNotificationConfigEnabled();
+
+        boolean valueUpdated = newVal != mSeparateNotification;
+        if (valueUpdated) {
+            mSeparateNotification = newVal;
+        }
+
+        return valueUpdated;
+    }
+
+    protected void updateRingerMode() {
+        final int ringerMode = mHelper.getRingerModeInternal();
+        if (mRingerMode == ringerMode) return;
+        mRingerMode = ringerMode;
+        selectPreferenceIconState();
+    }
+
+    /**
+     * Switching among normal/mute/vibrate
+     */
+    protected void selectPreferenceIconState() {
+        if (mPreference != null) {
+            if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) {
+                mPreference.showIcon(mNormalIconId);
+            } else {
+                if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE && mVibrator != null) {
+                    mMuteIcon = mVibrateIconId;
+                } else {
+                    mMuteIcon = mSilentIconId;
+                }
+                mPreference.showIcon(getMuteIcon());
+            }
+        }
+    }
+
+    protected abstract boolean hintsMatch(int hints);
+
+}
diff --git a/src/com/android/settings/notification/SeparateRingVolumePreferenceController.java b/src/com/android/settings/notification/SeparateRingVolumePreferenceController.java
new file mode 100644
index 0000000..1213372
--- /dev/null
+++ b/src/com/android/settings/notification/SeparateRingVolumePreferenceController.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 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.settings.notification;
+
+import android.app.ActivityThread;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.DeviceConfig;
+import android.service.notification.NotificationListenerService;
+
+import androidx.lifecycle.OnLifecycleEvent;
+
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.settings.R;
+import com.android.settings.Utils;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.util.Set;
+
+/**
+ * This slider is used to represent ring volume when ring is separated from notification
+ */
+public class SeparateRingVolumePreferenceController extends
+        RingerModeAffectedVolumePreferenceController {
+
+    private static final String KEY_SEPARATE_RING_VOLUME = "separate_ring_volume";
+    private static final String TAG = "SeparateRingVolumePreferenceController";
+
+    private final RingReceiver mReceiver = new RingReceiver();
+    private final H mHandler = new H();
+
+    public SeparateRingVolumePreferenceController(Context context) {
+        this(context, KEY_SEPARATE_RING_VOLUME);
+    }
+
+    public SeparateRingVolumePreferenceController(Context context, String key) {
+        super(context, key, TAG);
+
+        mNormalIconId = R.drawable.ic_ring_volume;
+        mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
+        mSilentIconId = R.drawable.ic_ring_volume_off;
+
+        mSeparateNotification = isSeparateNotificationConfigEnabled();
+        updateRingerMode();
+    }
+
+    /**
+     * Show/hide settings
+     */
+    private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+        Set<String> changeSet = properties.getKeyset();
+        if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+            boolean valueUpdated = readSeparateNotificationVolumeConfig();
+            if (valueUpdated) {
+                updateEffectsSuppressor();
+                selectPreferenceIconState();
+            }
+        }
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+    @Override
+    public void onResume() {
+        super.onResume();
+        mReceiver.register(true);
+        readSeparateNotificationVolumeConfig();
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                ActivityThread.currentApplication().getMainExecutor(), this::onDeviceConfigChange);
+        updateEffectsSuppressor();
+        selectPreferenceIconState();
+
+        if (mPreference != null) {
+            mPreference.setVisible(getAvailabilityStatus() == AVAILABLE);
+        }
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+    @Override
+    public void onPause() {
+        super.onPause();
+        mReceiver.register(false);
+        DeviceConfig.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_SEPARATE_RING_VOLUME;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        boolean separateNotification = isSeparateNotificationConfigEnabled();
+
+        return  separateNotification && Utils.isVoiceCapable(mContext) && !mHelper.isSingleVolume()
+                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
+    }
+
+    @Override
+    public int getAudioStream() {
+        return AudioManager.STREAM_RING;
+    }
+
+    @Override
+    protected boolean hintsMatch(int hints) {
+        return (hints & NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS) != 0
+                || (hints & NotificationListenerService.HINT_HOST_DISABLE_EFFECTS) != 0;
+    }
+
+
+
+    private final class H extends Handler {
+        private static final int UPDATE_EFFECTS_SUPPRESSOR = 1;
+        private static final int UPDATE_RINGER_MODE = 2;
+
+        private H() {
+            super(Looper.getMainLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case UPDATE_EFFECTS_SUPPRESSOR:
+                    updateEffectsSuppressor();
+                    break;
+                case UPDATE_RINGER_MODE:
+                    updateRingerMode();
+                    break;
+            }
+        }
+    }
+
+    private class RingReceiver extends BroadcastReceiver {
+        private boolean mRegistered;
+
+        public void register(boolean register) {
+            if (mRegistered == register) return;
+            if (register) {
+                final IntentFilter filter = new IntentFilter();
+                filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
+                filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
+                mContext.registerReceiver(this, filter);
+            } else {
+                mContext.unregisterReceiver(this);
+            }
+            mRegistered = register;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED.equals(action)) {
+                mHandler.sendEmptyMessage(H.UPDATE_EFFECTS_SUPPRESSOR);
+            } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
+                mHandler.sendEmptyMessage(H.UPDATE_RINGER_MODE);
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/settings/notification/SoundSettings.java b/src/com/android/settings/notification/SoundSettings.java
index 2d6f377..2cc1b1c 100644
--- a/src/com/android/settings/notification/SoundSettings.java
+++ b/src/com/android/settings/notification/SoundSettings.java
@@ -188,6 +188,7 @@
         volumeControllers.add(use(AlarmVolumePreferenceController.class));
         volumeControllers.add(use(MediaVolumePreferenceController.class));
         volumeControllers.add(use(RingVolumePreferenceController.class));
+        volumeControllers.add(use(SeparateRingVolumePreferenceController.class));
         volumeControllers.add(use(NotificationVolumePreferenceController.class));
         volumeControllers.add(use(CallVolumePreferenceController.class));
 
diff --git a/src/com/android/settings/panel/PanelSlicesAdapter.java b/src/com/android/settings/panel/PanelSlicesAdapter.java
index d728366..1bced76 100644
--- a/src/com/android/settings/panel/PanelSlicesAdapter.java
+++ b/src/com/android/settings/panel/PanelSlicesAdapter.java
@@ -54,7 +54,7 @@
      * Maximum number of slices allowed on the panel view.
      */
     @VisibleForTesting
-    static final int MAX_NUM_OF_SLICES = 7;
+    static final int MAX_NUM_OF_SLICES = 9;
 
     private final List<LiveData<Slice>> mSliceLiveData;
     private final int mMetricsCategory;
diff --git a/src/com/android/settings/panel/VolumePanel.java b/src/com/android/settings/panel/VolumePanel.java
index 08884d5..938ee9d 100644
--- a/src/com/android/settings/panel/VolumePanel.java
+++ b/src/com/android/settings/panel/VolumePanel.java
@@ -24,7 +24,9 @@
 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_ALARM_URI;
 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_CALL_URI;
 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI;
+import static com.android.settings.slices.CustomSliceRegistry.VOLUME_NOTIFICATION_URI;
 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_RINGER_URI;
+import static com.android.settings.slices.CustomSliceRegistry.VOLUME_SEPARATE_RING_URI;
 
 import android.app.Activity;
 import android.app.settings.SettingsEnums;
@@ -125,6 +127,10 @@
         return mContext.getText(R.string.sound_settings);
     }
 
+    /**
+     *  When considering ring and notification, we include all controllers unconditionally and rely
+     *  on getAvailability to govern visibility
+     */
     @Override
     public List<Uri> getSlices() {
         final List<Uri> uris = new ArrayList<>();
@@ -139,6 +145,8 @@
         uris.add(MEDIA_OUTPUT_INDICATOR_SLICE_URI);
         uris.add(VOLUME_CALL_URI);
         uris.add(VOLUME_RINGER_URI);
+        uris.add(VOLUME_SEPARATE_RING_URI);
+        uris.add(VOLUME_NOTIFICATION_URI);
         uris.add(VOLUME_ALARM_URI);
         return uris;
     }
@@ -189,4 +197,4 @@
         }
         return null;
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java
index c49d622..c499823 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -196,7 +196,7 @@
             .build();
 
     /**
-     * Full {@link Uri} for the Ringer volume Slice.
+     * Full {@link Uri} for the Ringer volume Slice. (Ring & notification combined)
      */
     public static final Uri VOLUME_RINGER_URI = new Uri.Builder()
             .scheme(ContentResolver.SCHEME_CONTENT)
@@ -206,6 +206,16 @@
             .build();
 
     /**
+     * Full {@link Uri} for the Separate Ring volume Slice.
+     */
+    public static final Uri VOLUME_SEPARATE_RING_URI = new Uri.Builder()
+            .scheme(ContentResolver.SCHEME_CONTENT)
+            .authority(SettingsSliceProvider.SLICE_AUTHORITY)
+            .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
+            .appendPath("separate_ring_volume")
+            .build();
+
+    /**
      * Full {@link Uri} for the Notification volume Slice.
      */
     public static final Uri VOLUME_NOTIFICATION_URI = new Uri.Builder()
diff --git a/tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java
index 1ad26c7..07e5993 100644
--- a/tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java
@@ -32,7 +32,7 @@
 import android.telephony.TelephonyManager;
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
 import com.android.settings.testutils.shadow.ShadowDeviceConfig;
 
 import org.junit.Before;
@@ -83,6 +83,9 @@
         when(mContext.getResources()).thenReturn(mResources);
         mController = new RingVolumePreferenceController(mContext);
         mController.setAudioHelper(mHelper);
+
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
     }
 
     @Test
@@ -103,6 +106,7 @@
 
     @Test
     public void isAvailable_notSingleVolume_VoiceCapable_shouldReturnTrue() {
+
         when(mHelper.isSingleVolume()).thenReturn(false);
         when(mTelephonyManager.isVoiceCapable()).thenReturn(true);
 
@@ -126,9 +130,11 @@
         assertThat(mController.isPublicSlice()).isTrue();
     }
 
-    // todo: verify that the title change is displayed, by examining the underlying preference
+    /**
+     * Only when the two streams are merged would this controller appear
+     */
     @Test
-    public void ringNotificationStreamsNotAliased_sliderTitleSetToRingOnly() {
+    public void ringNotificationStreamsSeparate_controllerIsNotAvailable() {
 
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
@@ -136,60 +142,68 @@
         final RingVolumePreferenceController controller =
                 new RingVolumePreferenceController(mContext);
 
-        int expectedTitleId = R.string.separate_ring_volume_option_title;
+        int controllerAvailability = controller.getAvailabilityStatus();
 
-        assertThat(controller.mTitleId).isEqualTo(expectedTitleId);
-    }
-
-    @Test
-    public void ringNotificationStreamsAliased_sliderTitleIncludesBothRingNotification() {
-
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
-
-        final RingVolumePreferenceController control = new RingVolumePreferenceController(mContext);
-
-        int expectedTitleId = R.string.ring_volume_option_title;
-
-        assertThat(control.mTitleId).isEqualTo(expectedTitleId);
+        assertThat(controllerAvailability)
+                .isNotEqualTo(BasePreferenceController.AVAILABLE);
     }
 
     @Test
     public void setHintsRing_aliased_Matches() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
+
         assertThat(mController.hintsMatch(
-                NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, false)).isTrue();
+                NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS)).isTrue();
     }
 
     @Test
     public void setHintsRingNotification_aliased_Matches() {
-        assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS,
-                false)).isTrue();
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
+        assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS))
+                .isTrue();
     }
 
     @Test
     public void setHintNotification_aliased_Matches() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false);
+
+
         assertThat(mController
-                .hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS,
-                false)).isTrue();
+                .hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS))
+                .isTrue();
     }
 
     @Test
     public void setHintsRing_unaliased_Matches() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
+
         assertThat(mController.hintsMatch(
-                NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, true)).isTrue();
+                NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS)).isTrue();
     }
 
     @Test
     public void setHintsRingNotification_unaliased_Matches() {
-        assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS,
-                true)).isTrue();
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
+
+        assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS))
+                .isTrue();
     }
 
     @Test
     public void setHintNotification_unaliased_doesNotMatch() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false);
+
         assertThat(mController
-                .hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS,
-                        true)).isFalse();
+                .hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS))
+                .isFalse();
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/notification/RingerModeAffectedVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/RingerModeAffectedVolumePreferenceControllerTest.java
new file mode 100644
index 0000000..d001653
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/RingerModeAffectedVolumePreferenceControllerTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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.settings.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class RingerModeAffectedVolumePreferenceControllerTest {
+
+    private RingerModeAffectedVolumePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        mController = Mockito.mock(
+                RingerModeAffectedVolumePreferenceController.class,
+                Mockito.CALLS_REAL_METHODS);
+    }
+
+    @Test
+    public void isSliceable_returnsTrue() {
+        assertThat(mController.isSliceable()).isTrue();
+    }
+
+    @Test
+    public void isPublicSlice_returnsTrue() {
+        assertThat(mController.isPublicSlice()).isTrue();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/notification/SeparateRingVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/SeparateRingVolumePreferenceControllerTest.java
new file mode 100644
index 0000000..88c8ff9
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/notification/SeparateRingVolumePreferenceControllerTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 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.settings.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.NotificationManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.os.Vibrator;
+import android.telephony.TelephonyManager;
+
+import com.android.settings.testutils.shadow.ShadowDeviceConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowDeviceConfig.class})
+public class SeparateRingVolumePreferenceControllerTest {
+
+    @Mock
+    private AudioHelper mHelper;
+    @Mock
+    private TelephonyManager mTelephonyManager;
+    @Mock
+    private AudioManager mAudioManager;
+    @Mock
+    private Vibrator mVibrator;
+    @Mock
+    private NotificationManager mNotificationManager;
+    @Mock
+    private ComponentName mSuppressor;
+    @Mock
+    private Resources mResources;
+
+    private Context mContext;
+
+    private SeparateRingVolumePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowApplication shadowContext = ShadowApplication.getInstance();
+        shadowContext.setSystemService(Context.TELEPHONY_SERVICE, mTelephonyManager);
+        shadowContext.setSystemService(Context.AUDIO_SERVICE, mAudioManager);
+        shadowContext.setSystemService(Context.VIBRATOR_SERVICE, mVibrator);
+        shadowContext.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager);
+        mContext = spy(RuntimeEnvironment.application);
+        when(mNotificationManager.getEffectsSuppressor()).thenReturn(mSuppressor);
+        when(mContext.getResources()).thenReturn(mResources);
+        mController = new SeparateRingVolumePreferenceController(mContext);
+        mController.setAudioHelper(mHelper);
+    }
+
+    @Test
+    public void isAvailable_ringNotificationAliased_shouldReturnFalse() {
+        when(mHelper.isSingleVolume()).thenReturn(true);
+        when(mTelephonyManager.isVoiceCapable()).thenReturn(true);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void getAudioStream_shouldReturnRing() {
+        assertThat(mController.getAudioStream()).isEqualTo(AudioManager.STREAM_RING);
+    }
+
+    @Test
+    public void isSliceableCorrectKey_returnsTrue() {
+        final SeparateRingVolumePreferenceController controller =
+                new SeparateRingVolumePreferenceController(mContext);
+        assertThat(controller.isSliceable()).isTrue();
+    }
+
+    @Test
+    public void isPublicSlice_returnTrue() {
+        assertThat(mController.isPublicSlice()).isTrue();
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/panel/VolumePanelTest.java b/tests/robotests/src/com/android/settings/panel/VolumePanelTest.java
index 6c0e131..74998c9 100644
--- a/tests/robotests/src/com/android/settings/panel/VolumePanelTest.java
+++ b/tests/robotests/src/com/android/settings/panel/VolumePanelTest.java
@@ -63,6 +63,8 @@
                 CustomSliceRegistry.VOLUME_MEDIA_URI,
                 CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI,
                 CustomSliceRegistry.VOLUME_RINGER_URI,
+                CustomSliceRegistry.VOLUME_SEPARATE_RING_URI,
+                CustomSliceRegistry.VOLUME_NOTIFICATION_URI,
                 CustomSliceRegistry.VOLUME_ALARM_URI);
     }