Loading src/com/android/settings/notification/NotificationVolumePreferenceController.java +13 −56 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.settings.notification; import android.app.ActivityThread; import android.app.INotificationManager; import android.app.NotificationManager; import android.content.BroadcastReceiver; Loading @@ -30,32 +29,26 @@ 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. * Update notification volume icon in Settings in response to user adjusting volume */ public class NotificationVolumePreferenceController extends VolumeSeekBarPreferenceController { 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 Vibrator mVibrator; private int mRingerMode = AudioManager.RINGER_MODE_NORMAL; Loading @@ -63,74 +56,39 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere 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; private final boolean mRingNotificationAliased; public NotificationVolumePreferenceController(Context context) { this(context, KEY_NOTIFICATION_VOLUME); } public NotificationVolumePreferenceController(Context context, String key) { super(context, key); mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); if (mVibrator != null && !mVibrator.hasVibrator()) { mVibrator = null; } mRingNotificationAliased = mContext.getResources().getBoolean( com.android.internal.R.bool.config_alias_ring_notif_stream_types); updateRingerMode(); } /** * Allow for notification slider to be enabled in the scenario where the config switches on * while settings page is already on the screen by always configuring the preference, even if it * is currently inactive. */ @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); if (mPreference == null) { setupVolPreference(screen); } mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL); if (mPreference != null) { mPreference.setVisible(getAvailabilityStatus() == AVAILABLE); } updateEffectsSuppressor(); updatePreferenceIconAndSliderState(); } /** * Only display the notification slider when the corresponding device config flag is set */ private void onDeviceConfigChange(DeviceConfig.Properties properties) { Set<String> changeSet = properties.getKeyset(); if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) { boolean newVal = properties.getBoolean( SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL); if (newVal != mSeparateNotification) { mSeparateNotification = newVal; // manually hiding the preference because being unavailable does not do the job if (mPreference != null) { mPreference.setVisible(getAvailabilityStatus() == AVAILABLE); } } } } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) @Override public void onResume() { super.onResume(); mReceiver.register(true); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, ActivityThread.currentApplication().getMainExecutor(), this::onDeviceConfigChange); updateEffectsSuppressor(); updatePreferenceIconAndSliderState(); } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) Loading @@ -138,17 +96,16 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere public void onPause() { super.onPause(); mReceiver.register(false); DeviceConfig.removeOnPropertiesChangedListener(this::onDeviceConfigChange); } @Override public int getAvailabilityStatus() { boolean separateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false); // Show separate notification slider if ring/notification are not aliased by AudioManager -- // if they are, notification volume is controlled by RingVolumePreferenceController. return mContext.getResources().getBoolean(R.bool.config_show_notification_volume) && (!mRingNotificationAliased || !Utils.isVoiceCapable(mContext)) && !mHelper.isSingleVolume() && (separateNotification || !Utils.isVoiceCapable(mContext)) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } Loading src/com/android/settings/notification/RingVolumePreferenceController.java +30 −63 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.settings.notification; import android.app.ActivityThread; import android.app.INotificationManager; import android.app.NotificationManager; import android.content.BroadcastReceiver; Loading @@ -30,7 +29,6 @@ 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; Loading @@ -38,13 +36,11 @@ 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, Loading @@ -63,21 +59,24 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr private int mMuteIcon; private int mNormalIconId; /* * Whether ring and notification streams are aliased together by AudioManager. * If they are, we'll present one volume control for both. * If not, we'll present separate volume controls. */ private final boolean mRingAliasNotif; private final int mNormalIconId; @VisibleForTesting int mVibrateIconId; final int mVibrateIconId; @VisibleForTesting int mSilentIconId; final int mSilentIconId; @VisibleForTesting int mTitleId; private boolean mSeparateNotification; final int mTitleId; private INotificationManager mNoMan; private static final boolean CONFIG_DEFAULT_VAL = false; public RingVolumePreferenceController(Context context) { this(context, KEY_RING_VOLUME); } Loading @@ -88,56 +87,29 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr if (mVibrator != null && !mVibrator.hasVibrator()) { mVibrator = null; } mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL); loadPreferenceIconResources(mSeparateNotification); updateRingerMode(); } 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 { mRingAliasNotif = isRingAliasNotification(); if (mRingAliasNotif) { mTitleId = R.string.ring_volume_option_title; mNormalIconId = R.drawable.ic_notifications; mSilentIconId = R.drawable.ic_notifications_off_24dp; } else { mTitleId = R.string.separate_ring_volume_option_title; mNormalIconId = R.drawable.ic_ring_volume; mSilentIconId = R.drawable.ic_ring_volume_off; } // todo: set a distinct vibrate icon for ring vs notification mVibrateIconId = R.drawable.ic_volume_ringer_vibrate; } /** * As the responsibility of this slider changes, so should its title & icon */ public 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); updateRingerMode(); } return valueUpdated; @VisibleForTesting boolean isRingAliasNotification() { return mContext.getResources().getBoolean( com.android.internal.R.bool.config_alias_ring_notif_stream_types); } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) Loading @@ -145,11 +117,8 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr public void onResume() { super.onResume(); mReceiver.register(true); readSeparateNotificationVolumeConfig(); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, ActivityThread.currentApplication().getMainExecutor(), this::onDeviceConfigChange); updateEffectsSuppressor(); selectPreferenceIconState(); updatePreferenceIcon(); setPreferenceTitle(); } Loading @@ -158,7 +127,6 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr public void onPause() { super.onPause(); mReceiver.register(false); DeviceConfig.removeOnPropertiesChangedListener(this::onDeviceConfigChange); } @Override Loading Loading @@ -202,7 +170,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr final int ringerMode = mHelper.getRingerModeInternal(); if (mRingerMode == ringerMode) return; mRingerMode = ringerMode; selectPreferenceIconState(); updatePreferenceIcon(); } private void updateEffectsSuppressor() { Loading @@ -222,8 +190,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr return; } if (hintsMatch(hints, DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false))) { if (hintsMatch(hints, mRingAliasNotif)) { mSuppressor = suppressor; if (mPreference != null) { final String text = SuppressorHelper.getSuppressionText(mContext, suppressor); Loading @@ -233,11 +200,11 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr } @VisibleForTesting boolean hintsMatch(int hints, boolean notificationSeparated) { boolean hintsMatch(int hints, boolean ringNotificationAliased) { 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); != 0 && ringNotificationAliased); } @VisibleForTesting Loading @@ -250,7 +217,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr mVibrator = vibrator; } private void selectPreferenceIconState() { private void updatePreferenceIcon() { if (mPreference != null) { if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) { mPreference.showIcon(mNormalIconId); Loading src/com/android/settings/notification/VolumeSeekBarPreferenceController.java +4 −8 Original line number Diff line number Diff line Loading @@ -55,16 +55,12 @@ public abstract class VolumeSeekBarPreferenceController extends public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); if (isAvailable()) { setupVolPreference(screen); } } protected void setupVolPreference(PreferenceScreen screen) { mPreference = screen.findPreference(getPreferenceKey()); mPreference.setCallback(mVolumePreferenceCallback); mPreference.setStream(getAudioStream()); mPreference.setMuteIcon(getMuteIcon()); } } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onResume() { Loading tests/robotests/src/com/android/settings/notification/NotificationVolumePreferenceControllerTest.java +4 −88 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.settings.notification; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; Loading @@ -26,17 +25,10 @@ import android.content.Context; import android.content.res.Resources; import android.media.AudioManager; import android.os.Vibrator; import android.provider.DeviceConfig; import android.service.notification.NotificationListenerService; import android.telephony.TelephonyManager; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.settings.core.BasePreferenceController; import com.android.settings.testutils.shadow.ShadowDeviceConfig; import com.android.internal.R; import org.junit.Before; import org.junit.Test; Loading @@ -45,12 +37,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowDeviceConfig.class}) public class NotificationVolumePreferenceControllerTest { @Mock private AudioHelper mHelper; @Mock Loading @@ -61,11 +52,6 @@ public class NotificationVolumePreferenceControllerTest { private Vibrator mVibrator; @Mock private Resources mResources; @Mock private PreferenceManager mPreferenceManager; private static final String READ_DEVICE_CONFIG_PERMISSION = "android.permission.READ_DEVICE_CONFIG"; private Context mContext; private NotificationVolumePreferenceController mController; Loading Loading @@ -101,9 +87,7 @@ public class NotificationVolumePreferenceControllerTest { public void isAvailable_voiceCapable_aliasedWithRing_shouldReturnFalse() { when(mResources.getBoolean( com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); when(mResources.getBoolean(R.bool.config_alias_ring_notif_stream_types)).thenReturn(true); NotificationVolumePreferenceController controller = new NotificationVolumePreferenceController(mContext); Loading @@ -121,9 +105,7 @@ public class NotificationVolumePreferenceControllerTest { public void isAvailable_voiceCapable_separatedFromRing_shouldReturnTrue() { when(mResources.getBoolean( com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false); when(mResources.getBoolean(R.bool.config_alias_ring_notif_stream_types)).thenReturn(false); NotificationVolumePreferenceController controller = new NotificationVolumePreferenceController(mContext); Loading Loading @@ -188,70 +170,4 @@ public class NotificationVolumePreferenceControllerTest { .isTrue(); } @Test public void enableSeparateNotificationConfig_controllerBecomesAvailable() { PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); VolumeSeekBarPreference volumeSeekBarPreference = mock(VolumeSeekBarPreference.class); when(screen.getPreferenceManager()).thenReturn(mPreferenceManager); when(screen.getContext()).thenReturn(mContext); when(mResources.getBoolean( com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true); // block the alternative condition to enable controller when(mTelephonyManager.isVoiceCapable()).thenReturn(true); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); NotificationVolumePreferenceController controller = new NotificationVolumePreferenceController(mContext); when(screen.findPreference(controller.getPreferenceKey())) .thenReturn(volumeSeekBarPreference); // allow the controller to subscribe Shadows.shadowOf((android.app.Application) ApplicationProvider.getApplicationContext()) .grantPermissions(READ_DEVICE_CONFIG_PERMISSION); controller.onResume(); controller.displayPreference(screen); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, Boolean.toString(true), false); assertThat(controller.getAvailabilityStatus() == BasePreferenceController.AVAILABLE).isTrue(); } @Test public void disableSeparateNotificationConfig_controllerBecomesUnavailable() { PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); VolumeSeekBarPreference volumeSeekBarPreference = mock(VolumeSeekBarPreference.class); when(screen.getPreferenceManager()).thenReturn(mPreferenceManager); when(screen.getContext()).thenReturn(mContext); when(mResources.getBoolean( com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true); // block the alternative condition to enable controller when(mTelephonyManager.isVoiceCapable()).thenReturn(true); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false); NotificationVolumePreferenceController controller = new NotificationVolumePreferenceController(mContext); when(screen.findPreference(controller.getPreferenceKey())) .thenReturn(volumeSeekBarPreference); Shadows.shadowOf((android.app.Application) ApplicationProvider.getApplicationContext()) .grantPermissions(READ_DEVICE_CONFIG_PERMISSION); controller.onResume(); controller.displayPreference(screen); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); assertThat(controller.getAvailabilityStatus() == BasePreferenceController.UNSUPPORTED_ON_DEVICE).isTrue(); } } tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java +11 −18 Original line number Diff line number Diff line Loading @@ -27,13 +27,10 @@ import android.content.Context; import android.content.res.Resources; import android.media.AudioManager; import android.os.Vibrator; import android.provider.DeviceConfig; import android.service.notification.NotificationListenerService; import android.telephony.TelephonyManager; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.settings.R; import com.android.settings.testutils.shadow.ShadowDeviceConfig; import org.junit.Before; import org.junit.Test; Loading @@ -42,11 +39,9 @@ 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 RingVolumePreferenceControllerTest { @Mock Loading Loading @@ -129,10 +124,9 @@ public class RingVolumePreferenceControllerTest { // todo: verify that the title change is displayed, by examining the underlying preference @Test public void ringNotificationStreamsNotAliased_sliderTitleSetToRingOnly() { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false); when(mResources.getBoolean( com.android.internal.R.bool.config_alias_ring_notif_stream_types)) .thenReturn(false); final RingVolumePreferenceController controller = new RingVolumePreferenceController(mContext); Loading @@ -144,9 +138,8 @@ public class RingVolumePreferenceControllerTest { @Test public void ringNotificationStreamsAliased_sliderTitleIncludesBothRingNotification() { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); when(mResources.getBoolean( com.android.internal.R.bool.config_alias_ring_notif_stream_types)).thenReturn(true); final RingVolumePreferenceController control = new RingVolumePreferenceController(mContext); int expectedTitleId = R.string.ring_volume_option_title; Loading @@ -157,39 +150,39 @@ public class RingVolumePreferenceControllerTest { @Test public void setHintsRing_aliased_Matches() { assertThat(mController.hintsMatch( NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, false)).isTrue(); NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, true)).isTrue(); } @Test public void setHintsRingNotification_aliased_Matches() { assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS, false)).isTrue(); true)).isTrue(); } @Test public void setHintNotification_aliased_Matches() { assertThat(mController .hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS, false)).isTrue(); true)).isTrue(); } @Test public void setHintsRing_unaliased_Matches() { assertThat(mController.hintsMatch( NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, true)).isTrue(); NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, false)).isTrue(); } @Test public void setHintsRingNotification_unaliased_Matches() { assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS, true)).isTrue(); false)).isTrue(); } @Test public void setHintNotification_unaliased_doesNotMatch() { assertThat(mController .hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS, true)).isFalse(); false)).isFalse(); } @Test Loading Loading
src/com/android/settings/notification/NotificationVolumePreferenceController.java +13 −56 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.settings.notification; import android.app.ActivityThread; import android.app.INotificationManager; import android.app.NotificationManager; import android.content.BroadcastReceiver; Loading @@ -30,32 +29,26 @@ 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. * Update notification volume icon in Settings in response to user adjusting volume */ public class NotificationVolumePreferenceController extends VolumeSeekBarPreferenceController { 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 Vibrator mVibrator; private int mRingerMode = AudioManager.RINGER_MODE_NORMAL; Loading @@ -63,74 +56,39 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere 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; private final boolean mRingNotificationAliased; public NotificationVolumePreferenceController(Context context) { this(context, KEY_NOTIFICATION_VOLUME); } public NotificationVolumePreferenceController(Context context, String key) { super(context, key); mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); if (mVibrator != null && !mVibrator.hasVibrator()) { mVibrator = null; } mRingNotificationAliased = mContext.getResources().getBoolean( com.android.internal.R.bool.config_alias_ring_notif_stream_types); updateRingerMode(); } /** * Allow for notification slider to be enabled in the scenario where the config switches on * while settings page is already on the screen by always configuring the preference, even if it * is currently inactive. */ @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); if (mPreference == null) { setupVolPreference(screen); } mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL); if (mPreference != null) { mPreference.setVisible(getAvailabilityStatus() == AVAILABLE); } updateEffectsSuppressor(); updatePreferenceIconAndSliderState(); } /** * Only display the notification slider when the corresponding device config flag is set */ private void onDeviceConfigChange(DeviceConfig.Properties properties) { Set<String> changeSet = properties.getKeyset(); if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) { boolean newVal = properties.getBoolean( SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL); if (newVal != mSeparateNotification) { mSeparateNotification = newVal; // manually hiding the preference because being unavailable does not do the job if (mPreference != null) { mPreference.setVisible(getAvailabilityStatus() == AVAILABLE); } } } } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) @Override public void onResume() { super.onResume(); mReceiver.register(true); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, ActivityThread.currentApplication().getMainExecutor(), this::onDeviceConfigChange); updateEffectsSuppressor(); updatePreferenceIconAndSliderState(); } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) Loading @@ -138,17 +96,16 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere public void onPause() { super.onPause(); mReceiver.register(false); DeviceConfig.removeOnPropertiesChangedListener(this::onDeviceConfigChange); } @Override public int getAvailabilityStatus() { boolean separateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false); // Show separate notification slider if ring/notification are not aliased by AudioManager -- // if they are, notification volume is controlled by RingVolumePreferenceController. return mContext.getResources().getBoolean(R.bool.config_show_notification_volume) && (!mRingNotificationAliased || !Utils.isVoiceCapable(mContext)) && !mHelper.isSingleVolume() && (separateNotification || !Utils.isVoiceCapable(mContext)) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } Loading
src/com/android/settings/notification/RingVolumePreferenceController.java +30 −63 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.settings.notification; import android.app.ActivityThread; import android.app.INotificationManager; import android.app.NotificationManager; import android.content.BroadcastReceiver; Loading @@ -30,7 +29,6 @@ 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; Loading @@ -38,13 +36,11 @@ 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, Loading @@ -63,21 +59,24 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr private int mMuteIcon; private int mNormalIconId; /* * Whether ring and notification streams are aliased together by AudioManager. * If they are, we'll present one volume control for both. * If not, we'll present separate volume controls. */ private final boolean mRingAliasNotif; private final int mNormalIconId; @VisibleForTesting int mVibrateIconId; final int mVibrateIconId; @VisibleForTesting int mSilentIconId; final int mSilentIconId; @VisibleForTesting int mTitleId; private boolean mSeparateNotification; final int mTitleId; private INotificationManager mNoMan; private static final boolean CONFIG_DEFAULT_VAL = false; public RingVolumePreferenceController(Context context) { this(context, KEY_RING_VOLUME); } Loading @@ -88,56 +87,29 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr if (mVibrator != null && !mVibrator.hasVibrator()) { mVibrator = null; } mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL); loadPreferenceIconResources(mSeparateNotification); updateRingerMode(); } 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 { mRingAliasNotif = isRingAliasNotification(); if (mRingAliasNotif) { mTitleId = R.string.ring_volume_option_title; mNormalIconId = R.drawable.ic_notifications; mSilentIconId = R.drawable.ic_notifications_off_24dp; } else { mTitleId = R.string.separate_ring_volume_option_title; mNormalIconId = R.drawable.ic_ring_volume; mSilentIconId = R.drawable.ic_ring_volume_off; } // todo: set a distinct vibrate icon for ring vs notification mVibrateIconId = R.drawable.ic_volume_ringer_vibrate; } /** * As the responsibility of this slider changes, so should its title & icon */ public 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); updateRingerMode(); } return valueUpdated; @VisibleForTesting boolean isRingAliasNotification() { return mContext.getResources().getBoolean( com.android.internal.R.bool.config_alias_ring_notif_stream_types); } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) Loading @@ -145,11 +117,8 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr public void onResume() { super.onResume(); mReceiver.register(true); readSeparateNotificationVolumeConfig(); DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, ActivityThread.currentApplication().getMainExecutor(), this::onDeviceConfigChange); updateEffectsSuppressor(); selectPreferenceIconState(); updatePreferenceIcon(); setPreferenceTitle(); } Loading @@ -158,7 +127,6 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr public void onPause() { super.onPause(); mReceiver.register(false); DeviceConfig.removeOnPropertiesChangedListener(this::onDeviceConfigChange); } @Override Loading Loading @@ -202,7 +170,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr final int ringerMode = mHelper.getRingerModeInternal(); if (mRingerMode == ringerMode) return; mRingerMode = ringerMode; selectPreferenceIconState(); updatePreferenceIcon(); } private void updateEffectsSuppressor() { Loading @@ -222,8 +190,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr return; } if (hintsMatch(hints, DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false))) { if (hintsMatch(hints, mRingAliasNotif)) { mSuppressor = suppressor; if (mPreference != null) { final String text = SuppressorHelper.getSuppressionText(mContext, suppressor); Loading @@ -233,11 +200,11 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr } @VisibleForTesting boolean hintsMatch(int hints, boolean notificationSeparated) { boolean hintsMatch(int hints, boolean ringNotificationAliased) { 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); != 0 && ringNotificationAliased); } @VisibleForTesting Loading @@ -250,7 +217,7 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr mVibrator = vibrator; } private void selectPreferenceIconState() { private void updatePreferenceIcon() { if (mPreference != null) { if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) { mPreference.showIcon(mNormalIconId); Loading
src/com/android/settings/notification/VolumeSeekBarPreferenceController.java +4 −8 Original line number Diff line number Diff line Loading @@ -55,16 +55,12 @@ public abstract class VolumeSeekBarPreferenceController extends public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); if (isAvailable()) { setupVolPreference(screen); } } protected void setupVolPreference(PreferenceScreen screen) { mPreference = screen.findPreference(getPreferenceKey()); mPreference.setCallback(mVolumePreferenceCallback); mPreference.setStream(getAudioStream()); mPreference.setMuteIcon(getMuteIcon()); } } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) public void onResume() { Loading
tests/robotests/src/com/android/settings/notification/NotificationVolumePreferenceControllerTest.java +4 −88 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.settings.notification; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; Loading @@ -26,17 +25,10 @@ import android.content.Context; import android.content.res.Resources; import android.media.AudioManager; import android.os.Vibrator; import android.provider.DeviceConfig; import android.service.notification.NotificationListenerService; import android.telephony.TelephonyManager; import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.settings.core.BasePreferenceController; import com.android.settings.testutils.shadow.ShadowDeviceConfig; import com.android.internal.R; import org.junit.Before; import org.junit.Test; Loading @@ -45,12 +37,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowDeviceConfig.class}) public class NotificationVolumePreferenceControllerTest { @Mock private AudioHelper mHelper; @Mock Loading @@ -61,11 +52,6 @@ public class NotificationVolumePreferenceControllerTest { private Vibrator mVibrator; @Mock private Resources mResources; @Mock private PreferenceManager mPreferenceManager; private static final String READ_DEVICE_CONFIG_PERMISSION = "android.permission.READ_DEVICE_CONFIG"; private Context mContext; private NotificationVolumePreferenceController mController; Loading Loading @@ -101,9 +87,7 @@ public class NotificationVolumePreferenceControllerTest { public void isAvailable_voiceCapable_aliasedWithRing_shouldReturnFalse() { when(mResources.getBoolean( com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); when(mResources.getBoolean(R.bool.config_alias_ring_notif_stream_types)).thenReturn(true); NotificationVolumePreferenceController controller = new NotificationVolumePreferenceController(mContext); Loading @@ -121,9 +105,7 @@ public class NotificationVolumePreferenceControllerTest { public void isAvailable_voiceCapable_separatedFromRing_shouldReturnTrue() { when(mResources.getBoolean( com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false); when(mResources.getBoolean(R.bool.config_alias_ring_notif_stream_types)).thenReturn(false); NotificationVolumePreferenceController controller = new NotificationVolumePreferenceController(mContext); Loading Loading @@ -188,70 +170,4 @@ public class NotificationVolumePreferenceControllerTest { .isTrue(); } @Test public void enableSeparateNotificationConfig_controllerBecomesAvailable() { PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); VolumeSeekBarPreference volumeSeekBarPreference = mock(VolumeSeekBarPreference.class); when(screen.getPreferenceManager()).thenReturn(mPreferenceManager); when(screen.getContext()).thenReturn(mContext); when(mResources.getBoolean( com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true); // block the alternative condition to enable controller when(mTelephonyManager.isVoiceCapable()).thenReturn(true); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); NotificationVolumePreferenceController controller = new NotificationVolumePreferenceController(mContext); when(screen.findPreference(controller.getPreferenceKey())) .thenReturn(volumeSeekBarPreference); // allow the controller to subscribe Shadows.shadowOf((android.app.Application) ApplicationProvider.getApplicationContext()) .grantPermissions(READ_DEVICE_CONFIG_PERMISSION); controller.onResume(); controller.displayPreference(screen); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, Boolean.toString(true), false); assertThat(controller.getAvailabilityStatus() == BasePreferenceController.AVAILABLE).isTrue(); } @Test public void disableSeparateNotificationConfig_controllerBecomesUnavailable() { PreferenceScreen screen = spy(new PreferenceScreen(mContext, null)); VolumeSeekBarPreference volumeSeekBarPreference = mock(VolumeSeekBarPreference.class); when(screen.getPreferenceManager()).thenReturn(mPreferenceManager); when(screen.getContext()).thenReturn(mContext); when(mResources.getBoolean( com.android.settings.R.bool.config_show_notification_volume)).thenReturn(true); // block the alternative condition to enable controller when(mTelephonyManager.isVoiceCapable()).thenReturn(true); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false); NotificationVolumePreferenceController controller = new NotificationVolumePreferenceController(mContext); when(screen.findPreference(controller.getPreferenceKey())) .thenReturn(volumeSeekBarPreference); Shadows.shadowOf((android.app.Application) ApplicationProvider.getApplicationContext()) .grantPermissions(READ_DEVICE_CONFIG_PERMISSION); controller.onResume(); controller.displayPreference(screen); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); assertThat(controller.getAvailabilityStatus() == BasePreferenceController.UNSUPPORTED_ON_DEVICE).isTrue(); } }
tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java +11 −18 Original line number Diff line number Diff line Loading @@ -27,13 +27,10 @@ import android.content.Context; import android.content.res.Resources; import android.media.AudioManager; import android.os.Vibrator; import android.provider.DeviceConfig; import android.service.notification.NotificationListenerService; import android.telephony.TelephonyManager; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.settings.R; import com.android.settings.testutils.shadow.ShadowDeviceConfig; import org.junit.Before; import org.junit.Test; Loading @@ -42,11 +39,9 @@ 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 RingVolumePreferenceControllerTest { @Mock Loading Loading @@ -129,10 +124,9 @@ public class RingVolumePreferenceControllerTest { // todo: verify that the title change is displayed, by examining the underlying preference @Test public void ringNotificationStreamsNotAliased_sliderTitleSetToRingOnly() { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "true", false); when(mResources.getBoolean( com.android.internal.R.bool.config_alias_ring_notif_stream_types)) .thenReturn(false); final RingVolumePreferenceController controller = new RingVolumePreferenceController(mContext); Loading @@ -144,9 +138,8 @@ public class RingVolumePreferenceControllerTest { @Test public void ringNotificationStreamsAliased_sliderTitleIncludesBothRingNotification() { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, "false", false); when(mResources.getBoolean( com.android.internal.R.bool.config_alias_ring_notif_stream_types)).thenReturn(true); final RingVolumePreferenceController control = new RingVolumePreferenceController(mContext); int expectedTitleId = R.string.ring_volume_option_title; Loading @@ -157,39 +150,39 @@ public class RingVolumePreferenceControllerTest { @Test public void setHintsRing_aliased_Matches() { assertThat(mController.hintsMatch( NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, false)).isTrue(); NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, true)).isTrue(); } @Test public void setHintsRingNotification_aliased_Matches() { assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS, false)).isTrue(); true)).isTrue(); } @Test public void setHintNotification_aliased_Matches() { assertThat(mController .hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS, false)).isTrue(); true)).isTrue(); } @Test public void setHintsRing_unaliased_Matches() { assertThat(mController.hintsMatch( NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, true)).isTrue(); NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS, false)).isTrue(); } @Test public void setHintsRingNotification_unaliased_Matches() { assertThat(mController.hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_EFFECTS, true)).isTrue(); false)).isTrue(); } @Test public void setHintNotification_unaliased_doesNotMatch() { assertThat(mController .hintsMatch(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS, true)).isFalse(); false)).isFalse(); } @Test Loading