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

Commit ee56e95f authored by Behnam Heydarshahi's avatar Behnam Heydarshahi Committed by Android (Google) Code Review
Browse files

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

parents fb098662 4f87dd4b
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -72,6 +72,14 @@
        android:order="-160"
        settings:controller="com.android.settings.notification.RingVolumePreferenceController"/>

    <!-- Separate Ring volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
        android:key="separate_ring_volume"
        android:icon="@drawable/ic_ring_volume"
        android:title="@string/separate_ring_volume_option_title"
        android:order="-155"
        settings:controller="com.android.settings.notification.SeparateRingVolumePreferenceController"/>

    <!-- Notification volume -->
    <com.android.settings.notification.VolumeSeekBarPreference
        android:key="notification_volume"
@@ -88,7 +96,7 @@
        android:title="@string/alarm_volume_option_title"
        android:order="-140"
        settings:controller="com.android.settings.notification.AlarmVolumePreferenceController"/>
x

    <!-- TODO(b/174964721): make this a PrimarySwitchPreference -->
    <!-- Interruptions -->
    <com.android.settingslib.RestrictedPreference
+16 −92
Original line number Diff line number Diff line
@@ -17,10 +17,8 @@
package com.android.settings.notification;

import android.app.ActivityThread;
import android.app.INotificationManager;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -28,57 +26,42 @@ import android.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 androidx.preference.PreferenceScreen;

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

import java.util.Objects;
import java.util.Set;

/**
 * Update notification volume icon in Settings in response to user adjusting volume.
 */
public class NotificationVolumePreferenceController extends VolumeSeekBarPreferenceController {
public class NotificationVolumePreferenceController extends
        RingerModeAffectedVolumePreferenceController {

    private static final String TAG = "NotificationVolumePreferenceController";
    private static final String KEY_NOTIFICATION_VOLUME = "notification_volume";
    private static final boolean CONFIG_DEFAULT_VAL = false;
    private boolean mSeparateNotification;
    private static final String TAG = "NotificationVolumePreferenceController";

    private Vibrator mVibrator;
    private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
    private ComponentName mSuppressor;
    private final RingReceiver mReceiver = new RingReceiver();
    private final H mHandler = new H();
    private INotificationManager mNoMan;
    private int mMuteIcon;
    private final int mNormalIconId =  R.drawable.ic_notifications;
    private final int mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
    private final int mSilentIconId = R.drawable.ic_notifications_off_24dp;


    public NotificationVolumePreferenceController(Context context) {
        this(context, KEY_NOTIFICATION_VOLUME);
    }

    public NotificationVolumePreferenceController(Context context, String key) {
        super(context, key);
        super(context, key, TAG);

        mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
        if (mVibrator != null && !mVibrator.hasVibrator()) {
            mVibrator = null;
        }
        mNormalIconId =  R.drawable.ic_notifications;
        mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
        mSilentIconId = R.drawable.ic_notifications_off_24dp;

        updateRingerMode();
    }
@@ -94,13 +77,12 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere
        if (mPreference == null) {
            setupVolPreference(screen);
        }
        mSeparateNotification = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
        mSeparateNotification = isSeparateNotificationConfigEnabled();
        if (mPreference != null) {
            mPreference.setVisible(getAvailabilityStatus() == AVAILABLE);
        }
        updateEffectsSuppressor();
        updatePreferenceIconAndSliderState();
        selectPreferenceIconState();
    }

    /**
@@ -110,8 +92,7 @@ public class NotificationVolumePreferenceController extends VolumeSeekBarPrefere
        Set<String> changeSet = properties.getKeyset();

        if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
            boolean newVal = properties.getBoolean(
                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
            boolean newVal = isSeparateNotificationConfigEnabled();
            if (newVal != mSeparateNotification) {
                mSeparateNotification = newVal;
                // manually hiding the preference because being unavailable does not do the job
@@ -143,8 +124,7 @@ 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;
            }
        }
+20 −164
Original line number Diff line number Diff line
@@ -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
        mVibrateIconId = R.drawable.ic_volume_ringer_vibrate;
        mSilentIconId = R.drawable.ic_notifications_off_24dp;

        mSeparateNotification = isSeparateNotificationConfigEnabled();
        updateRingerMode();
    }

    /**
     * As the responsibility of this slider changes, so should its title & icon
     */
    public void onDeviceConfigChange(DeviceConfig.Properties properties) {
    private void onDeviceConfigChange(DeviceConfig.Properties properties) {
        Set<String> changeSet = properties.getKeyset();
        if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
            boolean valueUpdated = readSeparateNotificationVolumeConfig();
            if (valueUpdated) {
                updateEffectsSuppressor();
                selectPreferenceIconState();
                setPreferenceTitle();
            }
        }
    }

    /**
     * side effect: updates the cached value of the config, and also the icon
     * @return has the config changed?
     */
    private boolean readSeparateNotificationVolumeConfig() {
        boolean newVal = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);

        boolean valueUpdated = newVal != mSeparateNotification;
        if (valueUpdated) {
            mSeparateNotification = newVal;
            loadPreferenceIconResources(newVal);
        }

        return valueUpdated;
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    @Override
    public void onResume() {
@@ -150,7 +89,10 @@ 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,23 +110,10 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr

    @Override
    public int getAvailabilityStatus() {
        return Utils.isVoiceCapable(mContext) && !mHelper.isSingleVolume()
                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
    }

    @Override
    public boolean isSliceable() {
        return TextUtils.equals(getPreferenceKey(), KEY_RING_VOLUME);
    }
        boolean separateNotification = isSeparateNotificationConfigEnabled();

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

    @Override
    public boolean useDynamicSliceSummary() {
        return true;
        return !separateNotification && Utils.isVoiceCapable(mContext) && !mHelper.isSingleVolume()
                ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
    }

    @Override
@@ -193,88 +122,15 @@ public class RingVolumePreferenceController extends VolumeSeekBarPreferenceContr
    }

    @Override
    public int getMuteIcon() {
        return mMuteIcon;
    }

    @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);
            }
        }
    }
    protected boolean hintsMatch(int hints) {
        boolean notificationSeparated = isSeparateNotificationConfigEnabled();

    @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;
+168 −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.
 */

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);

}
+180 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading