Implement a separate controller for ring volume
When ring volume is separated from notification, a new xml preferece and
controller is needed for it, so that the settings search can show/hide
the slice correctly.
1. Use a separate preference and controller for ring volume (vs ring &
notification combined)
2. Notification slice in settings no longer grays out when ringer mode
is set to mute or vibrate.
3. Introduce an abstract RingerModeAffected preference controller class
to factor out duplicate code among ring, notification, and separate-ring
controller classes.
Bug: b/259084354
Test: make ROBOTEST_FILTER=RingVolumePreferenceControllerTest
RunSettingsRoboTests -j40
make ROBOTEST_FILTER=SeparateRingVolumePreferenceControllerTest
RunSettingsRoboTests -j40
make ROBOTEST_FILTER=NotificationVolumePreferenceControllerTest
RunSettingsRoboTests -j40
make ROBOTEST_FILTER=VolumePanelTest RunSettingsRoboTests -j40
make
ROBOTEST_FILTER=RingerModeAffectedVolumePreferenceControllerTest -j40
Known Issue:
1. When streams are separate and ring volume set to mute/vibrate,
notification is set to zero, but not disabled. So it can be turned on
by user (and in settings the icon will stay mute/vibrate instead of
changing to the normal notification icon).
2. In the above scenario after notification is unmuted in settings,
the notification icon continues to stay vibrate/mute -- should change
to the normal notification icon.
Note: This feature is controlled using a boolean DeviceConfig flag:
systemui/"volume_separate_ring". The default value is 'false', which is
meant to keep the experience the same as before. It will be set to
'true' for teamfood and dogfood. Eventually the flag will be removed and
the code in the 'true' branch will prevail.
Change-Id: Ibec871eafeef4081e96c5e0dd04535565d50a077
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);
}