SeekBarVolumizer.java revision 735f9ebc188e1cb225533e1f5f9ef925d1f0b20e
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
17package android.preference;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.database.ContentObserver;
24import android.media.AudioManager;
25import android.media.Ringtone;
26import android.media.RingtoneManager;
27import android.net.Uri;
28import android.os.Handler;
29import android.os.HandlerThread;
30import android.os.Message;
31import android.preference.VolumePreference.VolumeStore;
32import android.provider.Settings;
33import android.provider.Settings.System;
34import android.util.Log;
35import android.widget.SeekBar;
36import android.widget.SeekBar.OnSeekBarChangeListener;
37
38/**
39 * Turns a {@link SeekBar} into a volume control.
40 * @hide
41 */
42public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
43    private static final String TAG = "SeekBarVolumizer";
44
45    public interface Callback {
46        void onSampleStarting(SeekBarVolumizer sbv);
47    }
48
49    private final Context mContext;
50    private final H mUiHandler = new H();
51    private final Callback mCallback;
52    private final Uri mDefaultUri;
53    private final AudioManager mAudioManager;
54    private final int mStreamType;
55    private final int mMaxStreamVolume;
56    private final Receiver mReceiver = new Receiver();
57
58    private Handler mHandler;
59    private Observer mVolumeObserver;
60    private int mOriginalStreamVolume;
61    private Ringtone mRingtone;
62    private int mLastProgress = -1;
63    private SeekBar mSeekBar;
64    private int mVolumeBeforeMute = -1;
65
66    private static final int MSG_SET_STREAM_VOLUME = 0;
67    private static final int MSG_START_SAMPLE = 1;
68    private static final int MSG_STOP_SAMPLE = 2;
69    private static final int MSG_INIT_SAMPLE = 3;
70    private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
71
72    public SeekBarVolumizer(Context context, int streamType, Uri defaultUri,
73            Callback callback) {
74        mContext = context;
75        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
76        mStreamType = streamType;
77        mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
78        mCallback = callback;
79        mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
80        if (defaultUri == null) {
81            if (mStreamType == AudioManager.STREAM_RING) {
82                defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
83            } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
84                defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
85            } else {
86                defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
87            }
88        }
89        mDefaultUri = defaultUri;
90    }
91
92    public void setSeekBar(SeekBar seekBar) {
93        if (mSeekBar != null) {
94            mSeekBar.setOnSeekBarChangeListener(null);
95        }
96        mSeekBar = seekBar;
97        mSeekBar.setOnSeekBarChangeListener(null);
98        mSeekBar.setMax(mMaxStreamVolume);
99        mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
100        mSeekBar.setOnSeekBarChangeListener(this);
101    }
102
103    @Override
104    public boolean handleMessage(Message msg) {
105        switch (msg.what) {
106            case MSG_SET_STREAM_VOLUME:
107                mAudioManager.setStreamVolume(mStreamType, mLastProgress,
108                        AudioManager.FLAG_SHOW_UI_WARNINGS);
109                break;
110            case MSG_START_SAMPLE:
111                onStartSample();
112                break;
113            case MSG_STOP_SAMPLE:
114                onStopSample();
115                break;
116            case MSG_INIT_SAMPLE:
117                onInitSample();
118                break;
119            default:
120                Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
121        }
122        return true;
123    }
124
125    private void onInitSample() {
126        mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
127        if (mRingtone != null) {
128            mRingtone.setStreamType(mStreamType);
129        }
130    }
131
132    private void postStartSample() {
133        if (mHandler == null) return;
134        mHandler.removeMessages(MSG_START_SAMPLE);
135        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
136                isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
137    }
138
139    private void onStartSample() {
140        if (!isSamplePlaying()) {
141            if (mCallback != null) {
142                mCallback.onSampleStarting(this);
143            }
144            if (mRingtone != null) {
145                try {
146                    mRingtone.play();
147                } catch (Throwable e) {
148                    Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
149                }
150            }
151        }
152    }
153
154    private void postStopSample() {
155        if (mHandler == null) return;
156        // remove pending delayed start messages
157        mHandler.removeMessages(MSG_START_SAMPLE);
158        mHandler.removeMessages(MSG_STOP_SAMPLE);
159        mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
160    }
161
162    private void onStopSample() {
163        if (mRingtone != null) {
164            mRingtone.stop();
165        }
166    }
167
168    public void stop() {
169        if (mHandler == null) return;  // already stopped
170        postStopSample();
171        mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
172        mReceiver.setListening(false);
173        mSeekBar.setOnSeekBarChangeListener(null);
174        mHandler.getLooper().quitSafely();
175        mHandler = null;
176        mVolumeObserver = null;
177    }
178
179    public void start() {
180        if (mHandler != null) return;  // already started
181        HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
182        thread.start();
183        mHandler = new Handler(thread.getLooper(), this);
184        mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
185        mVolumeObserver = new Observer(mHandler);
186        mContext.getContentResolver().registerContentObserver(
187                System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
188                false, mVolumeObserver);
189        mReceiver.setListening(true);
190    }
191
192    public void revertVolume() {
193        mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
194    }
195
196    public void onProgressChanged(SeekBar seekBar, int progress,
197            boolean fromTouch) {
198        if (!fromTouch) {
199            return;
200        }
201
202        postSetVolume(progress);
203    }
204
205    private void postSetVolume(int progress) {
206        if (mHandler == null) return;
207        // Do the volume changing separately to give responsive UI
208        mLastProgress = progress;
209        mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
210        mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
211    }
212
213    public void onStartTrackingTouch(SeekBar seekBar) {
214    }
215
216    public void onStopTrackingTouch(SeekBar seekBar) {
217        postStartSample();
218    }
219
220    public boolean isSamplePlaying() {
221        return mRingtone != null && mRingtone.isPlaying();
222    }
223
224    public void startSample() {
225        postStartSample();
226    }
227
228    public void stopSample() {
229        postStopSample();
230    }
231
232    public SeekBar getSeekBar() {
233        return mSeekBar;
234    }
235
236    public void changeVolumeBy(int amount) {
237        mSeekBar.incrementProgressBy(amount);
238        postSetVolume(mSeekBar.getProgress());
239        postStartSample();
240        mVolumeBeforeMute = -1;
241    }
242
243    public void muteVolume() {
244        if (mVolumeBeforeMute != -1) {
245            mSeekBar.setProgress(mVolumeBeforeMute);
246            postSetVolume(mVolumeBeforeMute);
247            postStartSample();
248            mVolumeBeforeMute = -1;
249        } else {
250            mVolumeBeforeMute = mSeekBar.getProgress();
251            mSeekBar.setProgress(0);
252            postStopSample();
253            postSetVolume(0);
254        }
255    }
256
257    public void onSaveInstanceState(VolumeStore volumeStore) {
258        if (mLastProgress >= 0) {
259            volumeStore.volume = mLastProgress;
260            volumeStore.originalVolume = mOriginalStreamVolume;
261        }
262    }
263
264    public void onRestoreInstanceState(VolumeStore volumeStore) {
265        if (volumeStore.volume != -1) {
266            mOriginalStreamVolume = volumeStore.originalVolume;
267            mLastProgress = volumeStore.volume;
268            postSetVolume(mLastProgress);
269        }
270    }
271
272    private final class H extends Handler {
273        private static final int UPDATE_SLIDER = 1;
274
275        @Override
276        public void handleMessage(Message msg) {
277            if (msg.what == UPDATE_SLIDER) {
278                if (mSeekBar != null) {
279                    mSeekBar.setProgress(msg.arg1);
280                    mLastProgress = mSeekBar.getProgress();
281                }
282            }
283        }
284
285        public void postUpdateSlider(int volume) {
286            obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget();
287        }
288    }
289
290    private final class Observer extends ContentObserver {
291        public Observer(Handler handler) {
292            super(handler);
293        }
294
295        @Override
296        public void onChange(boolean selfChange) {
297            super.onChange(selfChange);
298            if (mSeekBar != null && mAudioManager != null) {
299                final int volume = mAudioManager.getStreamVolume(mStreamType);
300                mUiHandler.postUpdateSlider(volume);
301            }
302        }
303    }
304
305    private final class Receiver extends BroadcastReceiver {
306        private boolean mListening;
307
308        public void setListening(boolean listening) {
309            if (mListening == listening) return;
310            mListening = listening;
311            if (listening) {
312                final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
313                mContext.registerReceiver(this, filter);
314            } else {
315                mContext.unregisterReceiver(this);
316            }
317        }
318
319        @Override
320        public void onReceive(Context context, Intent intent) {
321            if (!AudioManager.VOLUME_CHANGED_ACTION.equals(intent.getAction())) return;
322            final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
323            final int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
324            if (mSeekBar != null && streamType == mStreamType && streamValue != -1) {
325                mUiHandler.postUpdateSlider(streamValue);
326            }
327        }
328    }
329}
330