Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3ac6aaf7 authored by Behnam Heydarshahi's avatar Behnam Heydarshahi
Browse files

Add notification volume controller in settings

Separate notification and ring controllers based on the ring/volume
stream alias boolean in config.xml.

For both ring and volume controller: Not show vibrate icon when vibration is not supported on device. Show
silent icon instead.

Known issue: Add the notification volume slider only in Settings, and
not in VolumePanelDialog. When the alias is set to false and the streams
separated, the ring volume slider in VolumePanelDialog keeps its title
of "Ring & notification volume" instead of changing to "Ring volume".

Bug: b/38477228

Test: make DEBUG_ROBOLECTRIC=1 ROBOTEST_FILTER=NotificationVolumePreferenceControllerTest RunSettingsRoboTests -j40
      make DEBUG_ROBOLECTRIC=1 ROBOTEST_FILTER=RingVolumePreferenceControllerTest RunSettingsRoboTests -j40
      make DEBUG_ROBOLECTRIC=1 ROBOTEST_FILTER=SoundSettingsTest RunSettingsRoboTests

Change-Id: Id17523f49b291a5cf612b90f93c3b2ab6486c62f
parent 6ecbe4c1
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
<!--
    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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?android:attr/colorControlNormal">
  <path
      android:pathData="M11,7V2H13V7ZM17.6,9.85 L16.2,8.4 19.75,4.85 21.15,6.3ZM6.4,9.85 L2.85,6.3 4.25,4.85 7.8,8.4ZM12,12Q14.95,12 17.812,13.188Q20.675,14.375 22.9,16.75Q23.2,17.05 23.2,17.45Q23.2,17.85 22.9,18.15L20.6,20.4Q20.325,20.675 19.963,20.7Q19.6,20.725 19.3,20.5L16.4,18.3Q16.2,18.15 16.1,17.95Q16,17.75 16,17.5V14.65Q15.05,14.35 14.05,14.175Q13.05,14 12,14Q10.95,14 9.95,14.175Q8.95,14.35 8,14.65V17.5Q8,17.75 7.9,17.95Q7.8,18.15 7.6,18.3L4.7,20.5Q4.4,20.725 4.038,20.7Q3.675,20.675 3.4,20.4L1.1,18.15Q0.8,17.85 0.8,17.45Q0.8,17.05 1.1,16.75Q3.3,14.375 6.175,13.188Q9.05,12 12,12ZM6,15.35Q5.275,15.725 4.6,16.212Q3.925,16.7 3.2,17.3L4.2,18.3L6,16.9ZM18,15.4V16.9L19.8,18.3L20.8,17.35Q20.075,16.7 19.4,16.225Q18.725,15.75 18,15.4ZM6,15.35Q6,15.35 6,15.35Q6,15.35 6,15.35ZM18,15.4Q18,15.4 18,15.4Q18,15.4 18,15.4Z"
      android:fillColor="?android:attr/colorPrimary"/>

</vector>
 No newline at end of file
+34 −0
Original line number Diff line number Diff line
<!--
    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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="?android:attr/colorControlNormal">
<path
      android:pathData="M0.8,4.2l8.1,8.1c-2.2,0.5 -5.2,1.6 -7.8,4.4c-0.4,0.4 -0.4,1 0,1.4l2.3,2.3c0.3,0.3 0.9,0.4 1.3,0.1l2.9,-2.2C7.8,18.1 8,17.8 8,17.5v-2.9c0.9,-0.3 1.7,-0.5 2.7,-0.6l8.5,8.5l1.4,-1.4L2.2,2.8L0.8,4.2z"
    android:fillColor="?android:attr/colorPrimary"/>
  <path
      android:pathData="M11,2h2v5h-2z"
      android:fillColor="?android:attr/colorPrimary"/>
  <path
      android:pathData="M21.2,6.3l-1.4,-1.4l-3.6,3.6l1.4,1.4C17.6,9.8 21,6.3 21.2,6.3z"
      android:fillColor="?android:attr/colorPrimary"/>
  <path
      android:pathData="M22.9,16.7c-2.8,-3 -6.2,-4.1 -8.4,-4.5l7.2,7.2l1.3,-1.3C23.3,17.7 23.3,17.1 22.9,16.7z"
      android:fillColor="?android:attr/colorPrimary"/>
</vector>
+4 −1
Original line number Diff line number Diff line
@@ -8727,9 +8727,12 @@
    <!-- Sound: Title for the option managing alarm volume. [CHAR LIMIT=30] -->
    <string name="alarm_volume_option_title">Alarm volume</string>
    <!-- Sound: Title for the option managing ring volume. [CHAR LIMIT=30] -->
    <!-- Sound: Title for the option managing ring & notification volume. [CHAR LIMIT=30] -->
    <string name="ring_volume_option_title">Ring &amp; notification volume</string>
    <!-- Sound: Title for the option managing ring volume. [CHAR LIMIT=30] -->
    <string name="separate_ring_volume_option_title">Ring volume</string>
    <!-- Sound: Title for the option managing notification volume. [CHAR LIMIT=30] -->
    <string name="notification_volume_option_title">Notification volume</string>
+10 −10
Original line number Diff line number Diff line
@@ -72,23 +72,23 @@
        android:order="-160"
        settings:controller="com.android.settings.notification.RingVolumePreferenceController"/>

    <!-- Notification volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
        android:key="notification_volume"
        android:icon="@drawable/ic_notifications"
        android:title="@string/notification_volume_option_title"
        android:order="-150"
        settings:controller=
            "com.android.settings.notification.NotificationVolumePreferenceController"/>

    <!-- Alarm volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
        android:key="alarm_volume"
        android:icon="@*android:drawable/ic_audio_alarm"
        android:title="@string/alarm_volume_option_title"
        android:order="-150"
        settings:controller="com.android.settings.notification.AlarmVolumePreferenceController"/>

    <!-- Notification volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
        android:key="notification_volume"
        android:icon="@drawable/ic_notifications"
        android:title="@string/notification_volume_option_title"
        android:order="-140"
        settings:controller="com.android.settings.notification.NotificationVolumePreferenceController"/>

        settings:controller="com.android.settings.notification.AlarmVolumePreferenceController"/>
x
    <!-- TODO(b/174964721): make this a PrimarySwitchPreference -->
    <!-- Interruptions -->
    <com.android.settingslib.RestrictedPreference
+214 −5
Original line number Diff line number Diff line
@@ -16,26 +16,96 @@

package com.android.settings.notification;

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;
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.service.notification.NotificationListenerService;
import android.text.TextUtils;
import android.util.Log;

import androidx.lifecycle.OnLifecycleEvent;

import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settingslib.core.lifecycle.Lifecycle;

import java.util.Objects;

public class NotificationVolumePreferenceController extends
    RingVolumePreferenceController {
/**
 * 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 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;

    private final boolean mRingNotificationAliased;


    public NotificationVolumePreferenceController(Context context) {
        super(context, KEY_NOTIFICATION_VOLUME);
        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();
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    @Override
    public void onResume() {
        super.onResume();
        mReceiver.register(true);
        updateEffectsSuppressor();
        updatePreferenceIconAndSliderState();
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    @Override
    public void onPause() {
        super.onPause();
        mReceiver.register(false);
    }

    @Override
    public int getAvailabilityStatus() {

        // 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)
                && !Utils.isVoiceCapable(mContext) && !mHelper.isSingleVolume()
                && (!mRingNotificationAliased || !Utils.isVoiceCapable(mContext))
                && !mHelper.isSingleVolume()
                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
    }

@@ -54,6 +124,11 @@ public class NotificationVolumePreferenceController extends
        return KEY_NOTIFICATION_VOLUME;
    }

    @Override
    public boolean useDynamicSliceSummary() {
        return true;
    }

    @Override
    public int getAudioStream() {
        return AudioManager.STREAM_NOTIFICATION;
@@ -61,7 +136,141 @@ public class NotificationVolumePreferenceController extends

    @Override
    public int getMuteIcon() {
        return R.drawable.ic_notifications_off_24dp;
        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) {
        boolean allEffectsDisabled =
                (hints & NotificationListenerService.HINT_HOST_DISABLE_EFFECTS) != 0;
        boolean notificationEffectsDisabled =
                (hints & NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS) != 0;

        return allEffectsDisabled || notificationEffectsDisabled;
    }

    private void updatePreferenceIconAndSliderState() {
        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;
                    mPreference.showIcon(mSilentIconId);
                } else {
                    mPreference.showIcon(mNormalIconId);
                }
            }
        }
    }

    private final class H extends Handler {
        private static final int UPDATE_EFFECTS_SUPPRESSOR = 1;
        private static final int UPDATE_RINGER_MODE = 2;
        private static final int NOTIFICATION_VOLUME_CHANGED = 3;

        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;
                case NOTIFICATION_VOLUME_CHANGED:
                    updatePreferenceIconAndSliderState();
                    break;
            }
        }
    }

    /**
     * For notification volume icon to be accurate, we need to listen to volume change as well.
     * That is because the icon can change from mute/vibrate to normal without ringer mode changing.
     */
    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);
                filter.addAction(AudioManager.VOLUME_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);
            } else if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                if (streamType == AudioManager.STREAM_NOTIFICATION) {
                    int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE,
                            -1);
                    mHandler.obtainMessage(H.NOTIFICATION_VOLUME_CHANGED, streamValue, 0)
                            .sendToTarget();
                }
            }
        }
    }

}
Loading