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.lifecycle.OnLifecycleEvent; 32 import androidx.preference.PreferenceScreen; 33 34 import com.android.settings.R; 35 import com.android.settingslib.core.lifecycle.Lifecycle; 36 37 /** 38 * Update notification volume icon in Settings in response to user adjusting volume. 39 */ 40 public class NotificationVolumePreferenceController extends 41 RingerModeAffectedVolumePreferenceController { 42 43 private static final String KEY_NOTIFICATION_VOLUME = "notification_volume"; 44 private static final String TAG = "NotificationVolumePreferenceController"; 45 46 private final RingReceiver mReceiver = new RingReceiver(); 47 private final H mHandler = new H(); 48 NotificationVolumePreferenceController(Context context)49 public NotificationVolumePreferenceController(Context context) { 50 this(context, KEY_NOTIFICATION_VOLUME); 51 } 52 NotificationVolumePreferenceController(Context context, String key)53 public NotificationVolumePreferenceController(Context context, String key) { 54 super(context, key, TAG); 55 56 mNormalIconId = R.drawable.ic_notifications; 57 mVibrateIconId = R.drawable.ic_volume_ringer_vibrate; 58 mSilentIconId = R.drawable.ic_notifications_off_24dp; 59 60 if (updateRingerMode()) { 61 updateEnabledState(); 62 } 63 } 64 65 /** 66 * Allow for notification slider to be enabled in the scenario where the config switches on 67 * while settings page is already on the screen by always configuring the preference, even if it 68 * is currently inactive. 69 */ 70 @Override displayPreference(PreferenceScreen screen)71 public void displayPreference(PreferenceScreen screen) { 72 super.displayPreference(screen); 73 if (mPreference == null) { 74 setupVolPreference(screen); 75 } 76 77 updateEffectsSuppressor(); 78 selectPreferenceIconState(); 79 updateContentDescription(); 80 updateEnabledState(); 81 } 82 83 @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) 84 @Override onResume()85 public void onResume() { 86 super.onResume(); 87 mReceiver.register(true); 88 } 89 90 @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) 91 @Override onPause()92 public void onPause() { 93 super.onPause(); 94 mReceiver.register(false); 95 } 96 97 @Override getAvailabilityStatus()98 public int getAvailabilityStatus() { 99 return mContext.getResources().getBoolean(R.bool.config_show_notification_volume) 100 && !mHelper.isSingleVolume() ? (mRingerMode == AudioManager.RINGER_MODE_NORMAL 101 ? AVAILABLE : DISABLED_DEPENDENT_SETTING) : UNSUPPORTED_ON_DEVICE; 102 } 103 104 @Override getPreferenceKey()105 public String getPreferenceKey() { 106 return KEY_NOTIFICATION_VOLUME; 107 } 108 109 @Override getAudioStream()110 public int getAudioStream() { 111 return AudioManager.STREAM_NOTIFICATION; 112 } 113 114 @Override hintsMatch(int hints)115 protected boolean hintsMatch(int hints) { 116 boolean allEffectsDisabled = 117 (hints & NotificationListenerService.HINT_HOST_DISABLE_EFFECTS) != 0; 118 boolean notificationEffectsDisabled = 119 (hints & NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS) != 0; 120 121 return allEffectsDisabled || notificationEffectsDisabled; 122 } 123 124 @Override getEffectiveRingerMode()125 protected int getEffectiveRingerMode() { 126 if (mVibrator == null && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { 127 return AudioManager.RINGER_MODE_SILENT; 128 } else if (mRingerMode == AudioManager.RINGER_MODE_NORMAL) { 129 if (mHelper.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0) { 130 // Ring is in normal, but notification is in silent. 131 return AudioManager.RINGER_MODE_SILENT; 132 } 133 } 134 return mRingerMode; 135 } 136 137 @Override updateContentDescription()138 protected void updateContentDescription() { 139 if (mPreference != null) { 140 int ringerMode = getEffectiveRingerMode(); 141 if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { 142 mPreference.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); 143 mPreference.updateContentDescription( 144 mContext.getString( 145 R.string.notification_volume_content_description_vibrate_mode)); 146 } else if (ringerMode == AudioManager.RINGER_MODE_SILENT) { 147 mPreference.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); 148 mPreference.updateContentDescription( 149 mContext.getString(R.string.volume_content_description_silent_mode, 150 mPreference.getTitle())); 151 } else { 152 // Set a11y mode to none in order not to trigger talkback while changing 153 // notification volume in normal mode. 154 mPreference.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_NONE); 155 mPreference.updateContentDescription(mPreference.getTitle()); 156 } 157 } 158 } 159 updateEnabledState()160 private void updateEnabledState() { 161 if (mPreference != null) { 162 mPreference.setEnabled(mRingerMode == AudioManager.RINGER_MODE_NORMAL); 163 } 164 } 165 166 private final class H extends Handler { 167 private static final int UPDATE_EFFECTS_SUPPRESSOR = 1; 168 private static final int UPDATE_RINGER_MODE = 2; 169 private static final int NOTIFICATION_VOLUME_CHANGED = 3; 170 H()171 private H() { 172 super(Looper.getMainLooper()); 173 } 174 175 @Override handleMessage(Message msg)176 public void handleMessage(Message msg) { 177 switch (msg.what) { 178 case UPDATE_EFFECTS_SUPPRESSOR: 179 updateEffectsSuppressor(); 180 break; 181 case UPDATE_RINGER_MODE: 182 if (updateRingerMode()) { 183 updateEnabledState(); 184 } 185 break; 186 case NOTIFICATION_VOLUME_CHANGED: 187 selectPreferenceIconState(); 188 updateContentDescription(); 189 updateEnabledState(); 190 break; 191 } 192 } 193 } 194 195 /** 196 * For notification volume icon to be accurate, we need to listen to volume change as well. 197 * That is because the icon can change from mute/vibrate to normal without ringer mode changing. 198 */ 199 private class RingReceiver extends BroadcastReceiver { 200 private boolean mRegistered; 201 register(boolean register)202 public void register(boolean register) { 203 if (mRegistered == register) return; 204 if (register) { 205 final IntentFilter filter = new IntentFilter(); 206 filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); 207 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); 208 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 209 mContext.registerReceiver(this, filter); 210 } else { 211 mContext.unregisterReceiver(this); 212 } 213 mRegistered = register; 214 } 215 216 @Override onReceive(Context context, Intent intent)217 public void onReceive(Context context, Intent intent) { 218 final String action = intent.getAction(); 219 if (NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED.equals(action)) { 220 mHandler.sendEmptyMessage(H.UPDATE_EFFECTS_SUPPRESSOR); 221 } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { 222 mHandler.sendEmptyMessage(H.UPDATE_RINGER_MODE); 223 } else if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) { 224 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 225 if (streamType == AudioManager.STREAM_NOTIFICATION) { 226 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 227 -1); 228 mHandler.obtainMessage(H.NOTIFICATION_VOLUME_CHANGED, streamValue, 0) 229 .sendToTarget(); 230 } 231 } 232 } 233 } 234 } 235