diff --git a/res/xml/sound_settings.xml b/res/xml/sound_settings.xml index 914ce72428d..a84b0aefcd2 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"/> + + + -x + 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 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere @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() @@ -152,72 +132,18 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } - @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 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere 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 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere 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 7fdb1e16141..1399e717749 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.media.AudioManager; 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 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 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr ActivityThread.currentApplication().getMainExecutor(), this::onDeviceConfigChange); updateEffectsSuppressor(); selectPreferenceIconState(); - setPreferenceTitle(); + + if (mPreference != null) { + mPreference.setVisible(getAvailabilityStatus() == AVAILABLE); + } } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) @@ -168,113 +110,27 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr @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 00000000000..e792d534997 --- /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 00000000000..12133721029 --- /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 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 2d6f3778ffc..2cc1b1c2591 100644 --- a/src/com/android/settings/notification/SoundSettings.java +++ b/src/com/android/settings/notification/SoundSettings.java @@ -188,6 +188,7 @@ public class SoundSettings extends DashboardFragment implements OnActivityResult 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 d7283667787..1bced76a6b7 100644 --- a/src/com/android/settings/panel/PanelSlicesAdapter.java +++ b/src/com/android/settings/panel/PanelSlicesAdapter.java @@ -54,7 +54,7 @@ public class PanelSlicesAdapter * 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> mSliceLiveData; private final int mMetricsCategory; diff --git a/src/com/android/settings/panel/VolumePanel.java b/src/com/android/settings/panel/VolumePanel.java index 08884d5621f..938ee9d38aa 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.REMOTE_MEDIA_SLICE 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 @@ public class VolumePanel implements PanelContent, LifecycleObserver { 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 getSlices() { final List uris = new ArrayList<>(); @@ -139,6 +145,8 @@ public class VolumePanel implements PanelContent, LifecycleObserver { 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 @@ public class VolumePanel implements PanelContent, LifecycleObserver { } 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 c49d6224859..c499823817c 100644 --- a/src/com/android/settings/slices/CustomSliceRegistry.java +++ b/src/com/android/settings/slices/CustomSliceRegistry.java @@ -196,7 +196,7 @@ public class CustomSliceRegistry { .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) @@ -205,6 +205,16 @@ public class CustomSliceRegistry { .appendPath("ring_volume") .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. */ diff --git a/tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/RingVolumePreferenceControllerTest.java index 1ad26c71546..07e599371c7 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.service.notification.NotificationListenerService; 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 @@ public class RingVolumePreferenceControllerTest { 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 @@ public class RingVolumePreferenceControllerTest { @Test public void isAvailable_notSingleVolume_VoiceCapable_shouldReturnTrue() { + when(mHelper.isSingleVolume()).thenReturn(false); when(mTelephonyManager.isVoiceCapable()).thenReturn(true); @@ -126,9 +130,11 @@ public class RingVolumePreferenceControllerTest { 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 @@ public class RingVolumePreferenceControllerTest { 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 00000000000..d001653474f --- /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 00000000000..88c8ff9887d --- /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 6c0e131e2e0..74998c9e91d 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 @@ public class VolumePanelTest { 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); }