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