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