blob: 2445bc28abd8b84b8f427ee830ab3ff1ad913cd5 [file] [log] [blame]
John Spurlock74a2e062014-05-16 21:03:29 -04001/*
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
17package android.preference;
18
John Spurlock96d4a9e2015-06-10 17:49:35 -040019import android.app.NotificationManager;
John Spurlock0dbf1a62014-06-01 13:01:23 -040020import android.content.BroadcastReceiver;
John Spurlock74a2e062014-05-16 21:03:29 -040021import android.content.Context;
John Spurlock0dbf1a62014-06-01 13:01:23 -040022import android.content.Intent;
23import android.content.IntentFilter;
John Spurlock74a2e062014-05-16 21:03:29 -040024import android.database.ContentObserver;
John Spurlockbbfd31a2015-02-18 11:58:14 -050025import android.media.AudioAttributes;
John Spurlock74a2e062014-05-16 21:03:29 -040026import android.media.AudioManager;
27import android.media.Ringtone;
28import android.media.RingtoneManager;
29import android.net.Uri;
30import android.os.Handler;
31import android.os.HandlerThread;
32import android.os.Message;
33import android.preference.VolumePreference.VolumeStore;
34import android.provider.Settings;
John Spurlock96d4a9e2015-06-10 17:49:35 -040035import android.provider.Settings.Global;
John Spurlock74a2e062014-05-16 21:03:29 -040036import android.provider.Settings.System;
37import android.util.Log;
38import android.widget.SeekBar;
39import android.widget.SeekBar.OnSeekBarChangeListener;
40
41/**
42 * Turns a {@link SeekBar} into a volume control.
43 * @hide
44 */
45public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
John Spurlock95caba12014-05-27 13:10:38 -040046 private static final String TAG = "SeekBarVolumizer";
John Spurlock74a2e062014-05-16 21:03:29 -040047
48 public interface Callback {
49 void onSampleStarting(SeekBarVolumizer sbv);
John Spurlockbcc10872014-11-28 15:29:21 -050050 void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch);
John Spurlock96d4a9e2015-06-10 17:49:35 -040051 void onMuted(boolean muted, boolean zenMuted);
John Spurlock74a2e062014-05-16 21:03:29 -040052 }
53
John Spurlock95caba12014-05-27 13:10:38 -040054 private final Context mContext;
John Spurlock0dbf1a62014-06-01 13:01:23 -040055 private final H mUiHandler = new H();
John Spurlock74a2e062014-05-16 21:03:29 -040056 private final Callback mCallback;
John Spurlock95caba12014-05-27 13:10:38 -040057 private final Uri mDefaultUri;
58 private final AudioManager mAudioManager;
John Spurlock96d4a9e2015-06-10 17:49:35 -040059 private final NotificationManager mNotificationManager;
John Spurlock95caba12014-05-27 13:10:38 -040060 private final int mStreamType;
61 private final int mMaxStreamVolume;
John Spurlockbcc10872014-11-28 15:29:21 -050062 private boolean mAffectedByRingerMode;
63 private boolean mNotificationOrRing;
John Spurlock0dbf1a62014-06-01 13:01:23 -040064 private final Receiver mReceiver = new Receiver();
John Spurlock74a2e062014-05-16 21:03:29 -040065
John Spurlock0e588ea2014-10-08 12:33:22 -040066 private Handler mHandler;
67 private Observer mVolumeObserver;
John Spurlock74a2e062014-05-16 21:03:29 -040068 private int mOriginalStreamVolume;
John Spurlock96d4a9e2015-06-10 17:49:35 -040069 private int mLastAudibleStreamVolume;
John Spurlock74a2e062014-05-16 21:03:29 -040070 private Ringtone mRingtone;
John Spurlock74a2e062014-05-16 21:03:29 -040071 private int mLastProgress = -1;
John Spurlockbcc10872014-11-28 15:29:21 -050072 private boolean mMuted;
John Spurlock74a2e062014-05-16 21:03:29 -040073 private SeekBar mSeekBar;
74 private int mVolumeBeforeMute = -1;
John Spurlockbcc10872014-11-28 15:29:21 -050075 private int mRingerMode;
John Spurlock96d4a9e2015-06-10 17:49:35 -040076 private int mZenMode;
John Spurlock74a2e062014-05-16 21:03:29 -040077
78 private static final int MSG_SET_STREAM_VOLUME = 0;
79 private static final int MSG_START_SAMPLE = 1;
80 private static final int MSG_STOP_SAMPLE = 2;
John Spurlock95caba12014-05-27 13:10:38 -040081 private static final int MSG_INIT_SAMPLE = 3;
John Spurlock74a2e062014-05-16 21:03:29 -040082 private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
83
John Spurlockbcc10872014-11-28 15:29:21 -050084 public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) {
John Spurlock74a2e062014-05-16 21:03:29 -040085 mContext = context;
John Spurlock96d4a9e2015-06-10 17:49:35 -040086 mAudioManager = context.getSystemService(AudioManager.class);
87 mNotificationManager = context.getSystemService(NotificationManager.class);
John Spurlock74a2e062014-05-16 21:03:29 -040088 mStreamType = streamType;
John Spurlockbcc10872014-11-28 15:29:21 -050089 mAffectedByRingerMode = mAudioManager.isStreamAffectedByRingerMode(mStreamType);
90 mNotificationOrRing = isNotificationOrRing(mStreamType);
91 if (mNotificationOrRing) {
92 mRingerMode = mAudioManager.getRingerModeInternal();
93 }
John Spurlock96d4a9e2015-06-10 17:49:35 -040094 mZenMode = mNotificationManager.getZenMode();
John Spurlock95caba12014-05-27 13:10:38 -040095 mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
John Spurlock74a2e062014-05-16 21:03:29 -040096 mCallback = callback;
John Spurlock74a2e062014-05-16 21:03:29 -040097 mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
John Spurlock96d4a9e2015-06-10 17:49:35 -040098 mLastAudibleStreamVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);
John Spurlockbcc10872014-11-28 15:29:21 -050099 mMuted = mAudioManager.isStreamMute(mStreamType);
100 if (mCallback != null) {
John Spurlock96d4a9e2015-06-10 17:49:35 -0400101 mCallback.onMuted(mMuted, isZenMuted());
John Spurlockbcc10872014-11-28 15:29:21 -0500102 }
John Spurlock74a2e062014-05-16 21:03:29 -0400103 if (defaultUri == null) {
104 if (mStreamType == AudioManager.STREAM_RING) {
105 defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
106 } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
107 defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
108 } else {
109 defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
110 }
111 }
John Spurlock95caba12014-05-27 13:10:38 -0400112 mDefaultUri = defaultUri;
John Spurlock95caba12014-05-27 13:10:38 -0400113 }
John Spurlock74a2e062014-05-16 21:03:29 -0400114
John Spurlockbcc10872014-11-28 15:29:21 -0500115 private static boolean isNotificationOrRing(int stream) {
116 return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
117 }
118
John Spurlock95caba12014-05-27 13:10:38 -0400119 public void setSeekBar(SeekBar seekBar) {
John Spurlock0dbf1a62014-06-01 13:01:23 -0400120 if (mSeekBar != null) {
121 mSeekBar.setOnSeekBarChangeListener(null);
122 }
John Spurlock95caba12014-05-27 13:10:38 -0400123 mSeekBar = seekBar;
124 mSeekBar.setOnSeekBarChangeListener(null);
125 mSeekBar.setMax(mMaxStreamVolume);
John Spurlockbcc10872014-11-28 15:29:21 -0500126 updateSeekBar();
John Spurlock95caba12014-05-27 13:10:38 -0400127 mSeekBar.setOnSeekBarChangeListener(this);
John Spurlock74a2e062014-05-16 21:03:29 -0400128 }
129
John Spurlock96d4a9e2015-06-10 17:49:35 -0400130 private boolean isZenMuted() {
131 return mNotificationOrRing && mZenMode == Global.ZEN_MODE_ALARMS
132 || mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
133 }
134
John Spurlockbcc10872014-11-28 15:29:21 -0500135 protected void updateSeekBar() {
John Spurlock96d4a9e2015-06-10 17:49:35 -0400136 final boolean zenMuted = isZenMuted();
137 mSeekBar.setEnabled(!zenMuted);
138 if (zenMuted) {
139 mSeekBar.setProgress(mLastAudibleStreamVolume);
140 } else if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
John Spurlockbcc10872014-11-28 15:29:21 -0500141 mSeekBar.setProgress(0);
142 } else if (mMuted) {
John Spurlockbcc10872014-11-28 15:29:21 -0500143 mSeekBar.setProgress(0);
144 } else {
John Spurlockbcc10872014-11-28 15:29:21 -0500145 mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
146 }
147 }
148
John Spurlock74a2e062014-05-16 21:03:29 -0400149 @Override
150 public boolean handleMessage(Message msg) {
151 switch (msg.what) {
152 case MSG_SET_STREAM_VOLUME:
John Spurlock721d4572015-04-16 11:01:15 -0400153 if (mMuted && mLastProgress > 0) {
154 mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_UNMUTE, 0);
155 } else if (!mMuted && mLastProgress == 0) {
156 mAudioManager.adjustStreamVolume(mStreamType, AudioManager.ADJUST_MUTE, 0);
157 }
John Spurlock35134602014-07-24 18:10:48 -0400158 mAudioManager.setStreamVolume(mStreamType, mLastProgress,
159 AudioManager.FLAG_SHOW_UI_WARNINGS);
John Spurlock74a2e062014-05-16 21:03:29 -0400160 break;
161 case MSG_START_SAMPLE:
162 onStartSample();
163 break;
164 case MSG_STOP_SAMPLE:
165 onStopSample();
166 break;
John Spurlock95caba12014-05-27 13:10:38 -0400167 case MSG_INIT_SAMPLE:
168 onInitSample();
169 break;
John Spurlock74a2e062014-05-16 21:03:29 -0400170 default:
John Spurlock95caba12014-05-27 13:10:38 -0400171 Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
John Spurlock74a2e062014-05-16 21:03:29 -0400172 }
173 return true;
174 }
175
John Spurlock95caba12014-05-27 13:10:38 -0400176 private void onInitSample() {
177 mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
178 if (mRingtone != null) {
179 mRingtone.setStreamType(mStreamType);
180 }
181 }
182
John Spurlock74a2e062014-05-16 21:03:29 -0400183 private void postStartSample() {
John Spurlock735f9eb2014-10-21 14:23:24 -0400184 if (mHandler == null) return;
John Spurlock74a2e062014-05-16 21:03:29 -0400185 mHandler.removeMessages(MSG_START_SAMPLE);
186 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
187 isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
188 }
189
190 private void onStartSample() {
191 if (!isSamplePlaying()) {
192 if (mCallback != null) {
193 mCallback.onSampleStarting(this);
194 }
195 if (mRingtone != null) {
John Spurlock0dbf1a62014-06-01 13:01:23 -0400196 try {
John Spurlockbbfd31a2015-02-18 11:58:14 -0500197 mRingtone.setAudioAttributes(new AudioAttributes.Builder(mRingtone
198 .getAudioAttributes())
199 .setFlags(AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY |
200 AudioAttributes.FLAG_BYPASS_MUTE)
201 .build());
John Spurlock0dbf1a62014-06-01 13:01:23 -0400202 mRingtone.play();
203 } catch (Throwable e) {
204 Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
205 }
John Spurlock74a2e062014-05-16 21:03:29 -0400206 }
207 }
208 }
209
John Spurlock735f9eb2014-10-21 14:23:24 -0400210 private void postStopSample() {
211 if (mHandler == null) return;
John Spurlock74a2e062014-05-16 21:03:29 -0400212 // remove pending delayed start messages
213 mHandler.removeMessages(MSG_START_SAMPLE);
214 mHandler.removeMessages(MSG_STOP_SAMPLE);
215 mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
216 }
217
218 private void onStopSample() {
219 if (mRingtone != null) {
220 mRingtone.stop();
221 }
222 }
223
224 public void stop() {
John Spurlock0e588ea2014-10-08 12:33:22 -0400225 if (mHandler == null) return; // already stopped
John Spurlock74a2e062014-05-16 21:03:29 -0400226 postStopSample();
227 mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
John Spurlock0dbf1a62014-06-01 13:01:23 -0400228 mReceiver.setListening(false);
John Spurlock0e588ea2014-10-08 12:33:22 -0400229 mSeekBar.setOnSeekBarChangeListener(null);
John Spurlock0dbf1a62014-06-01 13:01:23 -0400230 mHandler.getLooper().quitSafely();
John Spurlock0e588ea2014-10-08 12:33:22 -0400231 mHandler = null;
232 mVolumeObserver = null;
233 }
234
235 public void start() {
236 if (mHandler != null) return; // already started
237 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
238 thread.start();
239 mHandler = new Handler(thread.getLooper(), this);
240 mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
241 mVolumeObserver = new Observer(mHandler);
242 mContext.getContentResolver().registerContentObserver(
243 System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
244 false, mVolumeObserver);
245 mReceiver.setListening(true);
John Spurlock74a2e062014-05-16 21:03:29 -0400246 }
247
248 public void revertVolume() {
249 mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
250 }
251
John Spurlockbcc10872014-11-28 15:29:21 -0500252 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
253 if (fromTouch) {
254 postSetVolume(progress);
John Spurlock74a2e062014-05-16 21:03:29 -0400255 }
John Spurlockbcc10872014-11-28 15:29:21 -0500256 if (mCallback != null) {
257 mCallback.onProgressChanged(seekBar, progress, fromTouch);
258 }
John Spurlock74a2e062014-05-16 21:03:29 -0400259 }
260
John Spurlock735f9eb2014-10-21 14:23:24 -0400261 private void postSetVolume(int progress) {
262 if (mHandler == null) return;
John Spurlock74a2e062014-05-16 21:03:29 -0400263 // Do the volume changing separately to give responsive UI
264 mLastProgress = progress;
265 mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
266 mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
267 }
268
269 public void onStartTrackingTouch(SeekBar seekBar) {
270 }
271
272 public void onStopTrackingTouch(SeekBar seekBar) {
273 postStartSample();
274 }
275
276 public boolean isSamplePlaying() {
277 return mRingtone != null && mRingtone.isPlaying();
278 }
279
280 public void startSample() {
281 postStartSample();
282 }
283
284 public void stopSample() {
285 postStopSample();
286 }
287
288 public SeekBar getSeekBar() {
289 return mSeekBar;
290 }
291
292 public void changeVolumeBy(int amount) {
293 mSeekBar.incrementProgressBy(amount);
294 postSetVolume(mSeekBar.getProgress());
295 postStartSample();
296 mVolumeBeforeMute = -1;
297 }
298
299 public void muteVolume() {
300 if (mVolumeBeforeMute != -1) {
301 mSeekBar.setProgress(mVolumeBeforeMute);
302 postSetVolume(mVolumeBeforeMute);
303 postStartSample();
304 mVolumeBeforeMute = -1;
305 } else {
306 mVolumeBeforeMute = mSeekBar.getProgress();
307 mSeekBar.setProgress(0);
308 postStopSample();
309 postSetVolume(0);
310 }
311 }
312
313 public void onSaveInstanceState(VolumeStore volumeStore) {
314 if (mLastProgress >= 0) {
315 volumeStore.volume = mLastProgress;
316 volumeStore.originalVolume = mOriginalStreamVolume;
317 }
318 }
319
320 public void onRestoreInstanceState(VolumeStore volumeStore) {
321 if (volumeStore.volume != -1) {
322 mOriginalStreamVolume = volumeStore.originalVolume;
323 mLastProgress = volumeStore.volume;
324 postSetVolume(mLastProgress);
325 }
326 }
John Spurlock0dbf1a62014-06-01 13:01:23 -0400327
328 private final class H extends Handler {
329 private static final int UPDATE_SLIDER = 1;
330
331 @Override
332 public void handleMessage(Message msg) {
333 if (msg.what == UPDATE_SLIDER) {
334 if (mSeekBar != null) {
John Spurlockbcc10872014-11-28 15:29:21 -0500335 mLastProgress = msg.arg1;
John Spurlock96d4a9e2015-06-10 17:49:35 -0400336 mLastAudibleStreamVolume = Math.abs(msg.arg2);
337 final boolean muted = msg.arg2 < 0;
John Spurlockbcc10872014-11-28 15:29:21 -0500338 if (muted != mMuted) {
339 mMuted = muted;
340 if (mCallback != null) {
John Spurlock96d4a9e2015-06-10 17:49:35 -0400341 mCallback.onMuted(mMuted, isZenMuted());
John Spurlockbcc10872014-11-28 15:29:21 -0500342 }
343 }
344 updateSeekBar();
John Spurlock0dbf1a62014-06-01 13:01:23 -0400345 }
346 }
347 }
348
John Spurlock96d4a9e2015-06-10 17:49:35 -0400349 public void postUpdateSlider(int volume, int lastAudibleVolume, boolean mute) {
350 final int arg2 = lastAudibleVolume * (mute ? -1 : 1);
351 obtainMessage(UPDATE_SLIDER, volume, arg2).sendToTarget();
John Spurlockbcc10872014-11-28 15:29:21 -0500352 }
353 }
354
355 private void updateSlider() {
356 if (mSeekBar != null && mAudioManager != null) {
357 final int volume = mAudioManager.getStreamVolume(mStreamType);
John Spurlock96d4a9e2015-06-10 17:49:35 -0400358 final int lastAudibleVolume = mAudioManager.getLastAudibleStreamVolume(mStreamType);
John Spurlockbcc10872014-11-28 15:29:21 -0500359 final boolean mute = mAudioManager.isStreamMute(mStreamType);
John Spurlock96d4a9e2015-06-10 17:49:35 -0400360 mUiHandler.postUpdateSlider(volume, lastAudibleVolume, mute);
John Spurlock0dbf1a62014-06-01 13:01:23 -0400361 }
362 }
363
364 private final class Observer extends ContentObserver {
365 public Observer(Handler handler) {
366 super(handler);
367 }
368
369 @Override
370 public void onChange(boolean selfChange) {
371 super.onChange(selfChange);
John Spurlockbcc10872014-11-28 15:29:21 -0500372 updateSlider();
John Spurlock0dbf1a62014-06-01 13:01:23 -0400373 }
374 }
375
376 private final class Receiver extends BroadcastReceiver {
377 private boolean mListening;
378
379 public void setListening(boolean listening) {
380 if (mListening == listening) return;
381 mListening = listening;
382 if (listening) {
383 final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
John Spurlockbcc10872014-11-28 15:29:21 -0500384 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
John Spurlock96d4a9e2015-06-10 17:49:35 -0400385 filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
Julia Reynolds3a10b102015-10-14 16:13:28 -0400386 filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
John Spurlock0dbf1a62014-06-01 13:01:23 -0400387 mContext.registerReceiver(this, filter);
388 } else {
389 mContext.unregisterReceiver(this);
390 }
391 }
392
393 @Override
394 public void onReceive(Context context, Intent intent) {
John Spurlockbcc10872014-11-28 15:29:21 -0500395 final String action = intent.getAction();
396 if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
397 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
398 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
Julia Reynolds3a10b102015-10-14 16:13:28 -0400399 updateVolumeSlider(streamType, streamValue);
John Spurlockbcc10872014-11-28 15:29:21 -0500400 } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
401 if (mNotificationOrRing) {
402 mRingerMode = mAudioManager.getRingerModeInternal();
403 }
404 if (mAffectedByRingerMode) {
405 updateSlider();
406 }
Julia Reynolds3a10b102015-10-14 16:13:28 -0400407 } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
408 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
409 int streamVolume = mAudioManager.getStreamVolume(streamType);
410 updateVolumeSlider(streamType, streamVolume);
John Spurlock96d4a9e2015-06-10 17:49:35 -0400411 } else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) {
412 mZenMode = mNotificationManager.getZenMode();
413 updateSlider();
John Spurlock0dbf1a62014-06-01 13:01:23 -0400414 }
415 }
Julia Reynolds3a10b102015-10-14 16:13:28 -0400416
417 private void updateVolumeSlider(int streamType, int streamValue) {
418 final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
419 : (streamType == mStreamType);
420 if (mSeekBar != null && streamMatch && streamValue != -1) {
421 final boolean muted = mAudioManager.isStreamMute(mStreamType)
422 || streamValue == 0;
423 mUiHandler.postUpdateSlider(streamValue, mLastAudibleStreamVolume, muted);
424 }
425 }
John Spurlock0dbf1a62014-06-01 13:01:23 -0400426 }
427}