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