blob: 29f25455878c90d25928b550f675c56cefc77038 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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
Amith Yamasani9b4742c2009-08-24 17:28:17 -070019import android.app.Dialog;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.Context;
21import android.content.res.TypedArray;
22import android.database.ContentObserver;
Amith Yamasani9b4742c2009-08-24 17:28:17 -070023import android.media.AudioManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.media.Ringtone;
25import android.media.RingtoneManager;
Patrick Scott3156bb002009-04-13 09:57:38 -070026import android.net.Uri;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.os.Handler;
Eric Laurentaca105c2013-04-12 16:29:40 -070028import android.os.HandlerThread;
29import android.os.Message;
Amith Yamasani9b4742c2009-08-24 17:28:17 -070030import android.os.Parcel;
31import android.os.Parcelable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.provider.Settings;
33import android.provider.Settings.System;
34import android.util.AttributeSet;
Eric Laurentaca105c2013-04-12 16:29:40 -070035import android.util.Log;
Marco Nelissen69f593c2009-07-28 09:55:04 -070036import android.view.KeyEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.view.View;
38import android.widget.SeekBar;
39import android.widget.SeekBar.OnSeekBarChangeListener;
40
41/**
42 * @hide
43 */
John Reck014fea22011-06-15 16:46:36 -070044public class VolumePreference extends SeekBarDialogPreference implements
Marco Nelissen69f593c2009-07-28 09:55:04 -070045 PreferenceManager.OnActivityStopListener, View.OnKeyListener {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046
47 private static final String TAG = "VolumePreference";
John Reck014fea22011-06-15 16:46:36 -070048
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049 private int mStreamType;
Amith Yamasani9b4742c2009-08-24 17:28:17 -070050
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051 /** May be null if the dialog isn't visible. */
52 private SeekBarVolumizer mSeekBarVolumizer;
John Reck014fea22011-06-15 16:46:36 -070053
Alan Viverette617feb92013-09-09 18:09:13 -070054 public VolumePreference(
55 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
56 super(context, attrs, defStyleAttr, defStyleRes);
John Reck014fea22011-06-15 16:46:36 -070057
Alan Viverette617feb92013-09-09 18:09:13 -070058 final TypedArray a = context.obtainStyledAttributes(attrs,
59 com.android.internal.R.styleable.VolumePreference, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 mStreamType = a.getInt(android.R.styleable.VolumePreference_streamType, 0);
Amith Yamasani998127c2011-01-31 20:28:03 -080061 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -070063
Alan Viverette617feb92013-09-09 18:09:13 -070064 public VolumePreference(Context context, AttributeSet attrs, int defStyleAttr) {
65 this(context, attrs, defStyleAttr, 0);
66 }
67
68 public VolumePreference(Context context, AttributeSet attrs) {
69 this(context, attrs, 0);
70 }
71
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 public void setStreamType(int streamType) {
73 mStreamType = streamType;
74 }
75
76 @Override
77 protected void onBindDialogView(View view) {
78 super.onBindDialogView(view);
John Reck014fea22011-06-15 16:46:36 -070079
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080 final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
81 mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType);
Amith Yamasani9b4742c2009-08-24 17:28:17 -070082
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080083 getPreferenceManager().registerOnActivityStopListener(this);
Marco Nelissen69f593c2009-07-28 09:55:04 -070084
85 // grab focus and key events so that pressing the volume buttons in the
86 // dialog doesn't also show the normal volume adjust toast.
87 view.setOnKeyListener(this);
88 view.setFocusableInTouchMode(true);
89 view.requestFocus();
90 }
91
92 public boolean onKey(View v, int keyCode, KeyEvent event) {
Amith Yamasani6ff59062009-08-25 15:40:14 -070093 // If key arrives immediately after the activity has been cleaned up.
94 if (mSeekBarVolumizer == null) return true;
Marco Nelissen69f593c2009-07-28 09:55:04 -070095 boolean isdown = (event.getAction() == KeyEvent.ACTION_DOWN);
96 switch (keyCode) {
97 case KeyEvent.KEYCODE_VOLUME_DOWN:
98 if (isdown) {
99 mSeekBarVolumizer.changeVolumeBy(-1);
100 }
101 return true;
102 case KeyEvent.KEYCODE_VOLUME_UP:
103 if (isdown) {
104 mSeekBarVolumizer.changeVolumeBy(1);
105 }
106 return true;
Jeff Brownb0418da2010-11-01 15:24:01 -0700107 case KeyEvent.KEYCODE_VOLUME_MUTE:
108 if (isdown) {
109 mSeekBarVolumizer.muteVolume();
110 }
111 return true;
Marco Nelissen69f593c2009-07-28 09:55:04 -0700112 default:
113 return false;
114 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 }
116
117 @Override
118 protected void onDialogClosed(boolean positiveResult) {
119 super.onDialogClosed(positiveResult);
John Reck014fea22011-06-15 16:46:36 -0700120
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121 if (!positiveResult && mSeekBarVolumizer != null) {
122 mSeekBarVolumizer.revertVolume();
123 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700124
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 cleanup();
126 }
127
128 public void onActivityStop() {
Samuel Asteberga4588682011-02-03 14:30:58 +0100129 if (mSeekBarVolumizer != null) {
Eric Laurentaca105c2013-04-12 16:29:40 -0700130 mSeekBarVolumizer.postStopSample();
Samuel Asteberga4588682011-02-03 14:30:58 +0100131 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 }
133
134 /**
135 * Do clean up. This can be called multiple times!
136 */
137 private void cleanup() {
138 getPreferenceManager().unregisterOnActivityStopListener(this);
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700139
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 if (mSeekBarVolumizer != null) {
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700141 Dialog dialog = getDialog();
142 if (dialog != null && dialog.isShowing()) {
Amith Yamasanie43530a2009-08-21 13:11:37 -0700143 View view = dialog.getWindow().getDecorView()
144 .findViewById(com.android.internal.R.id.seekbar);
145 if (view != null) view.setOnKeyListener(null);
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700146 // Stopped while dialog was showing, revert changes
147 mSeekBarVolumizer.revertVolume();
148 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 mSeekBarVolumizer.stop();
150 mSeekBarVolumizer = null;
151 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700152
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700154
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 protected void onSampleStarting(SeekBarVolumizer volumizer) {
156 if (mSeekBarVolumizer != null && volumizer != mSeekBarVolumizer) {
157 mSeekBarVolumizer.stopSample();
158 }
159 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700160
161 @Override
162 protected Parcelable onSaveInstanceState() {
163 final Parcelable superState = super.onSaveInstanceState();
164 if (isPersistent()) {
165 // No need to save instance state since it's persistent
166 return superState;
167 }
168
169 final SavedState myState = new SavedState(superState);
170 if (mSeekBarVolumizer != null) {
171 mSeekBarVolumizer.onSaveInstanceState(myState.getVolumeStore());
172 }
173 return myState;
174 }
175
176 @Override
177 protected void onRestoreInstanceState(Parcelable state) {
178 if (state == null || !state.getClass().equals(SavedState.class)) {
179 // Didn't save state for us in onSaveInstanceState
180 super.onRestoreInstanceState(state);
181 return;
182 }
183
184 SavedState myState = (SavedState) state;
185 super.onRestoreInstanceState(myState.getSuperState());
186 if (mSeekBarVolumizer != null) {
187 mSeekBarVolumizer.onRestoreInstanceState(myState.getVolumeStore());
188 }
189 }
190
191 public static class VolumeStore {
192 public int volume = -1;
193 public int originalVolume = -1;
194 }
195
196 private static class SavedState extends BaseSavedState {
197 VolumeStore mVolumeStore = new VolumeStore();
198
199 public SavedState(Parcel source) {
200 super(source);
201 mVolumeStore.volume = source.readInt();
202 mVolumeStore.originalVolume = source.readInt();
203 }
204
205 @Override
206 public void writeToParcel(Parcel dest, int flags) {
207 super.writeToParcel(dest, flags);
208 dest.writeInt(mVolumeStore.volume);
209 dest.writeInt(mVolumeStore.originalVolume);
210 }
211
212 VolumeStore getVolumeStore() {
213 return mVolumeStore;
214 }
215
216 public SavedState(Parcelable superState) {
217 super(superState);
218 }
219
220 public static final Parcelable.Creator<SavedState> CREATOR =
221 new Parcelable.Creator<SavedState>() {
222 public SavedState createFromParcel(Parcel in) {
223 return new SavedState(in);
224 }
225
226 public SavedState[] newArray(int size) {
227 return new SavedState[size];
228 }
229 };
230 }
231
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 /**
233 * Turns a {@link SeekBar} into a volume control.
234 */
Eric Laurentaca105c2013-04-12 16:29:40 -0700235 public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236
237 private Context mContext;
Eric Laurentaca105c2013-04-12 16:29:40 -0700238 private Handler mHandler;
John Reck014fea22011-06-15 16:46:36 -0700239
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 private AudioManager mAudioManager;
241 private int mStreamType;
John Reck014fea22011-06-15 16:46:36 -0700242 private int mOriginalStreamVolume;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 private Ringtone mRingtone;
John Reck014fea22011-06-15 16:46:36 -0700244
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700245 private int mLastProgress = -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 private SeekBar mSeekBar;
Jeff Brownb0418da2010-11-01 15:24:01 -0700247 private int mVolumeBeforeMute = -1;
John Reck014fea22011-06-15 16:46:36 -0700248
Eric Laurentaca105c2013-04-12 16:29:40 -0700249 private static final int MSG_SET_STREAM_VOLUME = 0;
250 private static final int MSG_START_SAMPLE = 1;
251 private static final int MSG_STOP_SAMPLE = 2;
252 private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
253
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254 private ContentObserver mVolumeObserver = new ContentObserver(mHandler) {
255 @Override
256 public void onChange(boolean selfChange) {
257 super.onChange(selfChange);
Eric Laurent0f31fe02011-02-03 17:34:39 -0800258 if (mSeekBar != null && mAudioManager != null) {
Eric Laurent8c787522012-05-14 14:09:43 -0700259 int volume = mAudioManager.getStreamVolume(mStreamType);
Eric Laurent0f31fe02011-02-03 17:34:39 -0800260 mSeekBar.setProgress(volume);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 }
262 }
263 };
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700264
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800265 public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType) {
Amith Yamasani998127c2011-01-31 20:28:03 -0800266 this(context, seekBar, streamType, null);
267 }
268
269 public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType, Uri defaultUri) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800270 mContext = context;
271 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
272 mStreamType = streamType;
273 mSeekBar = seekBar;
Amith Yamasani998127c2011-01-31 20:28:03 -0800274
Eric Laurentaca105c2013-04-12 16:29:40 -0700275 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
276 thread.start();
277 mHandler = new Handler(thread.getLooper(), this);
278
Amith Yamasani998127c2011-01-31 20:28:03 -0800279 initSeekBar(seekBar, defaultUri);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 }
281
Amith Yamasani998127c2011-01-31 20:28:03 -0800282 private void initSeekBar(SeekBar seekBar, Uri defaultUri) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283 seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType));
284 mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
285 seekBar.setProgress(mOriginalStreamVolume);
286 seekBar.setOnSeekBarChangeListener(this);
John Reck014fea22011-06-15 16:46:36 -0700287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288 mContext.getContentResolver().registerContentObserver(
289 System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
290 false, mVolumeObserver);
Amith Yamasani998127c2011-01-31 20:28:03 -0800291
292 if (defaultUri == null) {
293 if (mStreamType == AudioManager.STREAM_RING) {
294 defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
295 } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
296 defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
297 } else {
298 defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
299 }
Patrick Scott3156bb002009-04-13 09:57:38 -0700300 }
301
302 mRingtone = RingtoneManager.getRingtone(mContext, defaultUri);
Amith Yamasani998127c2011-01-31 20:28:03 -0800303
Marco Nelissen69f593c2009-07-28 09:55:04 -0700304 if (mRingtone != null) {
305 mRingtone.setStreamType(mStreamType);
306 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 }
Amith Yamasani998127c2011-01-31 20:28:03 -0800308
Eric Laurentaca105c2013-04-12 16:29:40 -0700309 @Override
310 public boolean handleMessage(Message msg) {
311 switch (msg.what) {
312 case MSG_SET_STREAM_VOLUME:
313 mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0);
314 break;
315 case MSG_START_SAMPLE:
316 onStartSample();
317 break;
318 case MSG_STOP_SAMPLE:
319 onStopSample();
320 break;
321 default:
322 Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
323 }
324 return true;
325 }
326
327 private void postStartSample() {
328 mHandler.removeMessages(MSG_START_SAMPLE);
329 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
330 isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
331 }
332
333 private void onStartSample() {
334 if (!isSamplePlaying()) {
335 onSampleStarting(this);
336 if (mRingtone != null) {
337 mRingtone.play();
338 }
339 }
340 }
341
342 private void postStopSample() {
343 // remove pending delayed start messages
344 mHandler.removeMessages(MSG_START_SAMPLE);
345 mHandler.removeMessages(MSG_STOP_SAMPLE);
346 mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
347 }
348
349 private void onStopSample() {
350 if (mRingtone != null) {
351 mRingtone.stop();
352 }
353 }
354
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 public void stop() {
Eric Laurentaca105c2013-04-12 16:29:40 -0700356 postStopSample();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
358 mSeekBar.setOnSeekBarChangeListener(null);
359 }
John Reck014fea22011-06-15 16:46:36 -0700360
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800361 public void revertVolume() {
362 mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
363 }
John Reck014fea22011-06-15 16:46:36 -0700364
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 public void onProgressChanged(SeekBar seekBar, int progress,
366 boolean fromTouch) {
367 if (!fromTouch) {
368 return;
369 }
John Reck014fea22011-06-15 16:46:36 -0700370
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800371 postSetVolume(progress);
372 }
373
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700374 void postSetVolume(int progress) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800375 // Do the volume changing separately to give responsive UI
376 mLastProgress = progress;
Eric Laurentaca105c2013-04-12 16:29:40 -0700377 mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
378 mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 }
John Reck014fea22011-06-15 16:46:36 -0700380
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 public void onStartTrackingTouch(SeekBar seekBar) {
382 }
383
384 public void onStopTrackingTouch(SeekBar seekBar) {
Eric Laurentaca105c2013-04-12 16:29:40 -0700385 postStartSample();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 }
Amith Yamasani998127c2011-01-31 20:28:03 -0800387
388 public boolean isSamplePlaying() {
389 return mRingtone != null && mRingtone.isPlaying();
390 }
391
392 public void startSample() {
Eric Laurentaca105c2013-04-12 16:29:40 -0700393 postStartSample();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 }
John Reck014fea22011-06-15 16:46:36 -0700395
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 public void stopSample() {
Eric Laurentaca105c2013-04-12 16:29:40 -0700397 postStopSample();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800398 }
399
400 public SeekBar getSeekBar() {
401 return mSeekBar;
402 }
John Reck014fea22011-06-15 16:46:36 -0700403
Marco Nelissen69f593c2009-07-28 09:55:04 -0700404 public void changeVolumeBy(int amount) {
405 mSeekBar.incrementProgressBy(amount);
Marco Nelissen69f593c2009-07-28 09:55:04 -0700406 postSetVolume(mSeekBar.getProgress());
Eric Laurentaca105c2013-04-12 16:29:40 -0700407 postStartSample();
Jeff Brownb0418da2010-11-01 15:24:01 -0700408 mVolumeBeforeMute = -1;
409 }
410
411 public void muteVolume() {
412 if (mVolumeBeforeMute != -1) {
413 mSeekBar.setProgress(mVolumeBeforeMute);
Jeff Brownb0418da2010-11-01 15:24:01 -0700414 postSetVolume(mVolumeBeforeMute);
Eric Laurentaca105c2013-04-12 16:29:40 -0700415 postStartSample();
Jeff Brownb0418da2010-11-01 15:24:01 -0700416 mVolumeBeforeMute = -1;
417 } else {
418 mVolumeBeforeMute = mSeekBar.getProgress();
419 mSeekBar.setProgress(0);
Eric Laurentaca105c2013-04-12 16:29:40 -0700420 postStopSample();
Jeff Brownb0418da2010-11-01 15:24:01 -0700421 postSetVolume(0);
422 }
Marco Nelissen69f593c2009-07-28 09:55:04 -0700423 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700424
425 public void onSaveInstanceState(VolumeStore volumeStore) {
426 if (mLastProgress >= 0) {
427 volumeStore.volume = mLastProgress;
428 volumeStore.originalVolume = mOriginalStreamVolume;
429 }
430 }
431
432 public void onRestoreInstanceState(VolumeStore volumeStore) {
433 if (volumeStore.volume != -1) {
434 mOriginalStreamVolume = volumeStore.originalVolume;
435 mLastProgress = volumeStore.volume;
436 postSetVolume(mLastProgress);
437 }
438 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800439 }
440}