1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.notification; 18 19 import android.app.NotificationManager; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.media.AudioManager; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.os.Message; 28 import android.service.notification.NotificationListenerService; 29 import android.view.View; 30 31 import androidx.annotation.NonNull; 32 import androidx.lifecycle.DefaultLifecycleObserver; 33 import androidx.lifecycle.LifecycleOwner; 34 import androidx.preference.PreferenceScreen; 35 36 import com.android.settings.R; 37 38 /** 39 * Update notification volume icon in Settings in response to user adjusting volume. 40 */ 41 public class NotificationVolumePreferenceController extends 42 RingerModeAffectedVolumePreferenceController implements DefaultLifecycleObserver { 43 44 private static final String KEY_NOTIFICATION_VOLUME = "notification_volume"; 45 private static final String TAG = "NotificationVolumePreferenceController"; 46 47 private final RingReceiver mReceiver = new RingReceiver(); 48 private final H mHandler = new H(); 49 NotificationVolumePreferenceController(Context context)50 public NotificationVolumePreferenceController(Context context) { 51 this(context, KEY_NOTIFICATION_VOLUME); 52 } 53 NotificationVolumePreferenceController(Context context, String key)54 public NotificationVolumePreferenceController(Context context, String key) { 55 super(context, key, TAG); 56 57 mNormalIconId = R.drawable.ic_notifications; 58 mVibrateIconId = R.drawable.ic_volume_ringer_vibrate; 59 mSilentIconId = R.drawable.ic_notifications_off_24dp; 60 61 if (updateRingerMode()) { 62 updateEnabledState(); 63 } 64 } 65 66 /** 67 * Allow for notification slider to be enabled in the scenario where the config switches on 68 * while settings page is already on the screen by always configuring the preference, even if it 69 * is currently inactive. 70 */ 71 @Override displayPreference(PreferenceScreen screen)72 public void displayPreference(PreferenceScreen screen) { 73 super.displayPreference(screen); 74 if (mPreference == null) { 75 setupVolPreference(screen); 76 } 77 78 updateEffectsSuppressor(); 79 selectPreferenceIconState(); 80 updateContentDescription(); 81 updateEnabledState(); 82 } 83 84 @Override onResume(@onNull LifecycleOwner owner)85 public void onResume(@NonNull LifecycleOwner owner) { 86 mReceiver.register(true); 87 } 88 89 @Override onPause(@onNull LifecycleOwner owner)90 public void onPause(@NonNull LifecycleOwner owner) { 91 mReceiver.register(false); 92 } 93 94 @Override getAvailabilityStatus()95 public int getAvailabilityStatus() { 96 return mContext.getResources().getBoolean(R.bool.config_show_notification_volume) 97 && !mHelper.isSingleVolume() ? (mRingerMode == AudioManager.RINGER_MODE_NORMAL 98 ? AVAILABLE : DISABLED_DEPENDENT_SETTING) : UNSUPPORTED_ON_DEVICE; 99 } 100 101 @Override getPreferenceKey()102 public String getPreferenceKey() { 103 return KEY_NOTIFICATION_VOLUME; 104 } 105 106 @Override getAudioStream()107 public int getAudioStream() { 108 return AudioManager.STREAM_NOTIFICATION; 109 } 110 111 @Override hintsMatch(int hints)112 protected boolean hintsMatch(int hints) { 113 boolean allEffectsDisabled = 114 (hints & NotificationListenerService.HINT_HOST_DISABLE_EFFECTS) != 0; 115 boolean notificationEffectsDisabled = 116 (hints & NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS) != 0; 117 118 return allEffectsDisabled || notificationEffectsDisabled; 119 } 120 121 @Override getEffectiveRingerMode()122 protected int getEffectiveRingerMode() { 123 if (mVibrator == null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { 124 return AudioManager.RINGER_MODE_SILENT; 125 } else if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) { 126 if (mHelper.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0) { 127 // Ring is in normal, but notification is in silent. 128 return AudioManager.RINGER_MODE_SILENT; 129 } 130 } 131 return mRingerMode; 132 } 133 134 @Override updateContentDescription()135 protected void updateContentDescription() { 136 if (mPreference != null) { 137 int ringerMode = getEffectiveRingerMode(); 138 if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { 139 mPreference.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); 140 mPreference.updateContentDescription( 141 mContext.getString( 142 R.string.notification_volume_content_description_vibrate_mode)); 143 } else if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 144 mPreference.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); 145 mPreference.updateContentDescription( 146 mContext.getString(R.string.volume_content_description_silent_mode, 147 mPreference.getTitle())); 148 } else { 149 // Set a11y mode to none in order not to trigger talkback while changing 150 // notification volume in normal mode. 151 mPreference.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_NONE); 152 mPreference.updateContentDescription(mPreference.getTitle()); 153 } 154 } 155 } 156 updateEnabledState()157 private void updateEnabledState() { 158 if (mPreference != null) { 159 mPreference.setEnabled(mRingerMode == AudioManager.RINGER_MODE_NORMAL); 160 } 161 } 162 163 private final class H extends Handler { 164 private static final int UPDATE_EFFECTS_SUPPRESSOR = 1; 165 private static final int UPDATE_RINGER_MODE = 2; 166 private static final int NOTIFICATION_VOLUME_CHANGED = 3; 167 H()168 private H() { 169 super(Looper.getMainLooper()); 170 } 171 172 @Override handleMessage(Message msg)173 public void handleMessage(Message msg) { 174 switch (msg.what) { 175 case UPDATE_EFFECTS_SUPPRESSOR: 176 updateEffectsSuppressor(); 177 break; 178 case UPDATE_RINGER_MODE: 179 if (updateRingerMode()) { 180 updateEnabledState(); 181 } 182 break; 183 case NOTIFICATION_VOLUME_CHANGED: 184 selectPreferenceIconState(); 185 updateContentDescription(); 186 updateEnabledState(); 187 break; 188 } 189 } 190 } 191 192 /** 193 * For notification volume icon to be accurate, we need to listen to volume change as well. 194 * That is because the icon can change from mute/vibrate to normal without ringer mode changing. 195 */ 196 private class RingReceiver extends BroadcastReceiver { 197 private boolean mRegistered; 198 register(boolean register)199 public void register(boolean register) { 200 if (mRegistered == register) return; 201 if (register) { 202 final IntentFilter filter = new IntentFilter(); 203 filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); 204 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); 205 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 206 mContext.registerReceiver(this, filter); 207 } else { 208 mContext.unregisterReceiver(this); 209 } 210 mRegistered = register; 211 } 212 213 @Override onReceive(Context context, Intent intent)214 public void onReceive(Context context, Intent intent) { 215 final String action = intent.getAction(); 216 if (NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED.equals(action)) { 217 mHandler.sendEmptyMessage(H.UPDATE_EFFECTS_SUPPRESSOR); 218 } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { 219 mHandler.sendEmptyMessage(H.UPDATE_RINGER_MODE); 220 } else if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) { 221 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 222 if (streamType == AudioManager.STREAM_NOTIFICATION) { 223 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 224 -1); 225 mHandler.obtainMessage(H.NOTIFICATION_VOLUME_CHANGED, streamValue, 0) 226 .sendToTarget(); 227 } 228 } 229 } 230 } 231 } 232