• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 android.preference;
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.database.ContentObserver;
25 import android.media.AudioAttributes;
26 import android.media.AudioManager;
27 import android.media.Ringtone;
28 import android.media.RingtoneManager;
29 import android.net.Uri;
30 import android.os.Handler;
31 import android.os.HandlerThread;
32 import android.os.Message;
33 import android.preference.VolumePreference.VolumeStore;
34 import android.provider.Settings;
35 import android.provider.Settings.Global;
36 import android.provider.Settings.System;
37 import android.service.notification.ZenModeConfig;
38 import android.util.Log;
39 import android.widget.SeekBar;
40 import android.widget.SeekBar.OnSeekBarChangeListener;
41 
42 import com.android.internal.annotations.GuardedBy;
43 
44 /**
45  * Turns a {@link SeekBar} into a volume control.
46  * @hide
47  */
48 public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
49     private static final String TAG = "SeekBarVolumizer";
50 
51     public interface Callback {
onSampleStarting(SeekBarVolumizer sbv)52         void onSampleStarting(SeekBarVolumizer sbv);
onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch)53         void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch);
onMuted(boolean muted, boolean zenMuted)54         void onMuted(boolean muted, boolean zenMuted);
55     }
56 
57     private final Context mContext;
58     private final H mUiHandler = new H();
59     private final Callback mCallback;
60     private final Uri mDefaultUri;
61     private final AudioManager mAudioManager;
62     private final NotificationManager mNotificationManager;
63     private final int mStreamType;
64     private final int mMaxStreamVolume;
65     private boolean mAffectedByRingerMode;
66     private boolean mNotificationOrRing;
67     private final Receiver mReceiver = new Receiver();
68 
69     private Handler mHandler;
70     private Observer mVolumeObserver;
71     private int mOriginalStreamVolume;
72     private int mLastAudibleStreamVolume;
73     // When the old handler is destroyed and a new one is created, there could be a situation where
74     // this is accessed at the same time in different handlers. So, access to this field needs to be
75     // synchronized.
76     @GuardedBy("this")
77     private Ringtone mRingtone;
78     private int mLastProgress = -1;
79     private boolean mMuted;
80     private SeekBar mSeekBar;
81     private int mVolumeBeforeMute = -1;
82     private int mRingerMode;
83     private int mZenMode;
84 
85     private static final int MSG_SET_STREAM_VOLUME = 0;
86     private static final int MSG_START_SAMPLE = 1;
87     private static final int MSG_STOP_SAMPLE = 2;
88     private static final int MSG_INIT_SAMPLE = 3;
89     private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
90 
91     private NotificationManager.Policy mNotificationPolicy;
92     private boolean mAllowAlarms;
93     private boolean mAllowMedia;
94     private boolean mAllowRinger;
95 
SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback)96     public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) {
97         mContext = context;
98         mAudioManager = context.getSystemService(AudioManager.class);
99         mNotificationManager = context.getSystemService(NotificationManager.class);
100         mNotificationPolicy = mNotificationManager.getNotificationPolicy();
101         mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy
102                 .PRIORITY_CATEGORY_ALARMS) != 0;
103         mAllowMedia = (mNotificationPolicy.priorityCategories & NotificationManager.Policy
104                 .PRIORITY_CATEGORY_MEDIA) != 0;
105         mAllowRinger = !ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(
106                 mNotificationPolicy);
107         mStreamType = streamType;
108         mAffectedByRingerMode = mAudioManager.isStreamAffectedByRingerMode(mStreamType);
109         mNotificationOrRing = isNotificationOrRing(mStreamType);
110         if (mNotificationOrRing) {
111             mRingerMode = mAudioManager.getRingerModeInternal();
112         }
113         mZenMode = mNotificationManager.getZenMode();
114         mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
115         mCallback = callback;
116         mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
117         mLastAudibleStreamVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);
118         mMuted = mAudioManager.isStreamMute(mStreamType);
119         if (mCallback != null) {
120             mCallback.onMuted(mMuted, isZenMuted());
121         }
122         if (defaultUri == null) {
123             if (mStreamType == AudioManager.STREAM_RING) {
124                 defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
125             } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
126                 defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
127             } else {
128                 defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
129             }
130         }
131         mDefaultUri = defaultUri;
132     }
133 
isNotificationOrRing(int stream)134     private static boolean isNotificationOrRing(int stream) {
135         return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
136     }
137 
isAlarmsStream(int stream)138     private static boolean isAlarmsStream(int stream) {
139         return stream == AudioManager.STREAM_ALARM;
140     }
141 
isMediaStream(int stream)142     private static boolean isMediaStream(int stream) {
143         return stream == AudioManager.STREAM_MUSIC;
144     }
145 
setSeekBar(SeekBar seekBar)146     public void setSeekBar(SeekBar seekBar) {
147         if (mSeekBar != null) {
148             mSeekBar.setOnSeekBarChangeListener(null);
149         }
150         mSeekBar = seekBar;
151         mSeekBar.setOnSeekBarChangeListener(null);
152         mSeekBar.setMax(mMaxStreamVolume);
153         updateSeekBar();
154         mSeekBar.setOnSeekBarChangeListener(this);
155     }
156 
isZenMuted()157     private boolean isZenMuted() {
158         return mNotificationOrRing && mZenMode == Global.ZEN_MODE_ALARMS
159                 || mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
160                 || (mZenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
161                     && ((!mAllowAlarms && isAlarmsStream(mStreamType))
162                         || (!mAllowMedia && isMediaStream(mStreamType))
163                         || (!mAllowRinger && isNotificationOrRing(mStreamType))));
164     }
165 
updateSeekBar()166     protected void updateSeekBar() {
167         final boolean zenMuted = isZenMuted();
168         mSeekBar.setEnabled(!zenMuted);
169         if (zenMuted) {
170             mSeekBar.setProgress(mLastAudibleStreamVolume, true);
171         } else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
172             mSeekBar.setProgress(0, true);
173         } else if (mMuted) {
174             mSeekBar.setProgress(0, true);
175         } else {
176             mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume, true);
177         }
178     }
179 
180     @Override
handleMessage(Message msg)181     public boolean handleMessage(Message msg) {
182         switch (msg.what) {
183             case MSG_SET_STREAM_VOLUME:
184                 if (mMuted && mLastProgress > 0) {
185                     mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_UNMUTE, 0);
186                 } else if (!mMuted && mLastProgress == 0) {
187                     mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_MUTE, 0);
188                 }
189                 mAudioManager.setStreamVolume(mStreamType, mLastProgress,
190                         AudioManager.FLAG_SHOW_UI_WARNINGS);
191                 break;
192             case MSG_START_SAMPLE:
193                 onStartSample();
194                 break;
195             case MSG_STOP_SAMPLE:
196                 onStopSample();
197                 break;
198             case MSG_INIT_SAMPLE:
199                 onInitSample();
200                 break;
201             default:
202                 Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
203         }
204         return true;
205     }
206 
onInitSample()207     private void onInitSample() {
208         synchronized (this) {
209             mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
210             if (mRingtone != null) {
211                 mRingtone.setStreamType(mStreamType);
212             }
213         }
214     }
215 
postStartSample()216     private void postStartSample() {
217         if (mHandler == null) return;
218         mHandler.removeMessages(MSG_START_SAMPLE);
219         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
220                 isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
221     }
222 
onStartSample()223     private void onStartSample() {
224         if (!isSamplePlaying()) {
225             if (mCallback != null) {
226                 mCallback.onSampleStarting(this);
227             }
228 
229             synchronized (this) {
230                 if (mRingtone != null) {
231                     try {
232                         mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
233                                 .getAudioAttributes())
234                                 .setFlags(AudioAttributes.FLAG_BYPASS_MUTE)
235                                 .build());
236                         mRingtone.play();
237                     } catch (Throwable e) {
238                         Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
239                     }
240                 }
241             }
242         }
243     }
244 
postStopSample()245     private void postStopSample() {
246         if (mHandler == null) return;
247         // remove pending delayed start messages
248         mHandler.removeMessages(MSG_START_SAMPLE);
249         mHandler.removeMessages(MSG_STOP_SAMPLE);
250         mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
251     }
252 
onStopSample()253     private void onStopSample() {
254         synchronized (this) {
255             if (mRingtone != null) {
256                 mRingtone.stop();
257             }
258         }
259     }
260 
stop()261     public void stop() {
262         if (mHandler == null) return;  // already stopped
263         postStopSample();
264         mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
265         mReceiver.setListening(false);
266         mSeekBar.setOnSeekBarChangeListener(null);
267         mHandler.getLooper().quitSafely();
268         mHandler = null;
269         mVolumeObserver = null;
270     }
271 
start()272     public void start() {
273         if (mHandler != null) return;  // already started
274         HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
275         thread.start();
276         mHandler = new Handler(thread.getLooper(), this);
277         mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
278         mVolumeObserver = new Observer(mHandler);
279         mContext.getContentResolver().registerContentObserver(
280                 System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
281                 false, mVolumeObserver);
282         mReceiver.setListening(true);
283     }
284 
revertVolume()285     public void revertVolume() {
286         mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
287     }
288 
onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch)289     public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
290         if (fromTouch) {
291             postSetVolume(progress);
292         }
293         if (mCallback != null) {
294             mCallback.onProgressChanged(seekBar, progress, fromTouch);
295         }
296     }
297 
postSetVolume(int progress)298     private void postSetVolume(int progress) {
299         if (mHandler == null) return;
300         // Do the volume changing separately to give responsive UI
301         mLastProgress = progress;
302         mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
303         mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
304     }
305 
onStartTrackingTouch(SeekBar seekBar)306     public void onStartTrackingTouch(SeekBar seekBar) {
307     }
308 
onStopTrackingTouch(SeekBar seekBar)309     public void onStopTrackingTouch(SeekBar seekBar) {
310         postStartSample();
311     }
312 
isSamplePlaying()313     public boolean isSamplePlaying() {
314         synchronized (this) {
315             return mRingtone != null && mRingtone.isPlaying();
316         }
317     }
318 
startSample()319     public void startSample() {
320         postStartSample();
321     }
322 
stopSample()323     public void stopSample() {
324         postStopSample();
325     }
326 
getSeekBar()327     public SeekBar getSeekBar() {
328         return mSeekBar;
329     }
330 
changeVolumeBy(int amount)331     public void changeVolumeBy(int amount) {
332         mSeekBar.incrementProgressBy(amount);
333         postSetVolume(mSeekBar.getProgress());
334         postStartSample();
335         mVolumeBeforeMute = -1;
336     }
337 
muteVolume()338     public void muteVolume() {
339         if (mVolumeBeforeMute != -1) {
340             mSeekBar.setProgress(mVolumeBeforeMute, true);
341             postSetVolume(mVolumeBeforeMute);
342             postStartSample();
343             mVolumeBeforeMute = -1;
344         } else {
345             mVolumeBeforeMute = mSeekBar.getProgress();
346             mSeekBar.setProgress(0, true);
347             postStopSample();
348             postSetVolume(0);
349         }
350     }
351 
onSaveInstanceState(VolumeStore volumeStore)352     public void onSaveInstanceState(VolumeStore volumeStore) {
353         if (mLastProgress >= 0) {
354             volumeStore.volume = mLastProgress;
355             volumeStore.originalVolume = mOriginalStreamVolume;
356         }
357     }
358 
onRestoreInstanceState(VolumeStore volumeStore)359     public void onRestoreInstanceState(VolumeStore volumeStore) {
360         if (volumeStore.volume != -1) {
361             mOriginalStreamVolume = volumeStore.originalVolume;
362             mLastProgress = volumeStore.volume;
363             postSetVolume(mLastProgress);
364         }
365     }
366 
367     private final class H extends Handler {
368         private static final int UPDATE_SLIDER = 1;
369 
370         @Override
handleMessage(Message msg)371         public void handleMessage(Message msg) {
372             if (msg.what == UPDATE_SLIDER) {
373                 if (mSeekBar != null) {
374                     mLastProgress = msg.arg1;
375                     mLastAudibleStreamVolume = msg.arg2;
376                     final boolean muted = ((Boolean)msg.obj).booleanValue();
377                     if (muted != mMuted) {
378                         mMuted = muted;
379                         if (mCallback != null) {
380                             mCallback.onMuted(mMuted, isZenMuted());
381                         }
382                     }
383                     updateSeekBar();
384                 }
385             }
386         }
387 
postUpdateSlider(int volume, int lastAudibleVolume, boolean mute)388         public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) {
389             obtainMessage(UPDATE_SLIDER, volume, lastAudibleVolume, new Boolean(mute)).sendToTarget();
390         }
391     }
392 
updateSlider()393     private void updateSlider() {
394         if (mSeekBar != null && mAudioManager != null) {
395             final int volume = mAudioManager.getStreamVolume(mStreamType);
396             final int lastAudibleVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);
397             final boolean mute = mAudioManager.isStreamMute(mStreamType);
398             mUiHandler.postUpdateSlider(volume, lastAudibleVolume, mute);
399         }
400     }
401 
402     private final class Observer extends ContentObserver {
Observer(Handler handler)403         public Observer(Handler handler) {
404             super(handler);
405         }
406 
407         @Override
onChange(boolean selfChange)408         public void onChange(boolean selfChange) {
409             super.onChange(selfChange);
410             updateSlider();
411         }
412     }
413 
414     private final class Receiver extends BroadcastReceiver {
415         private boolean mListening;
416 
setListening(boolean listening)417         public void setListening(boolean listening) {
418             if (mListening == listening) return;
419             mListening = listening;
420             if (listening) {
421                 final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
422                 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
423                 filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
424                 filter.addAction(NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED);
425                 filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
426                 mContext.registerReceiver(this, filter);
427             } else {
428                 mContext.unregisterReceiver(this);
429             }
430         }
431 
432         @Override
onReceive(Context context, Intent intent)433         public void onReceive(Context context, Intent intent) {
434             final String action = intent.getAction();
435             if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
436                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
437                 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
438                 updateVolumeSlider(streamType, streamValue);
439             } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
440                 if (mNotificationOrRing) {
441                     mRingerMode = mAudioManager.getRingerModeInternal();
442                 }
443                 if (mAffectedByRingerMode) {
444                     updateSlider();
445                 }
446             } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
447                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
448                 int streamVolume = mAudioManager.getStreamVolume(streamType);
449                 updateVolumeSlider(streamType, streamVolume);
450             } else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) {
451                 mZenMode = mNotificationManager.getZenMode();
452                 updateSlider();
453             } else if (NotificationManager.ACTION_NOTIFICATION_POLICY_CHANGED.equals(action)) {
454                 mNotificationPolicy = mNotificationManager.getNotificationPolicy();
455                 mAllowAlarms = (mNotificationPolicy.priorityCategories & NotificationManager.Policy
456                         .PRIORITY_CATEGORY_ALARMS) != 0;
457                 mAllowMedia = (mNotificationPolicy.priorityCategories
458                         & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) != 0;
459                 mAllowRinger = !ZenModeConfig.areAllPriorityOnlyNotificationZenSoundsMuted(
460                         mNotificationPolicy);
461                 updateSlider();
462             }
463         }
464 
updateVolumeSlider(int streamType, int streamValue)465         private void updateVolumeSlider(int streamType, int streamValue) {
466             final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
467                     : (streamType == mStreamType);
468             if (mSeekBar != null && streamMatch && streamValue != -1) {
469                 final boolean muted = mAudioManager.isStreamMute(mStreamType)
470                         || streamValue == 0;
471                 mUiHandler.postUpdateSlider(streamValue, mLastAudibleStreamVolume, muted);
472             }
473         }
474     }
475 }
476