• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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