blob: dc683a62fcaddebf1ae4874dd550e415cce23875 [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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054 public VolumePreference(Context context, AttributeSet attrs) {
55 super(context, attrs);
John Reck014fea22011-06-15 16:46:36 -070056
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057 TypedArray a = context.obtainStyledAttributes(attrs,
58 com.android.internal.R.styleable.VolumePreference, 0, 0);
59 mStreamType = a.getInt(android.R.styleable.VolumePreference_streamType, 0);
Amith Yamasani998127c2011-01-31 20:28:03 -080060 a.recycle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -070062
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080063 public void setStreamType(int streamType) {
64 mStreamType = streamType;
65 }
66
67 @Override
68 protected void onBindDialogView(View view) {
69 super.onBindDialogView(view);
John Reck014fea22011-06-15 16:46:36 -070070
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071 final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
72 mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType);
Amith Yamasani9b4742c2009-08-24 17:28:17 -070073
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 getPreferenceManager().registerOnActivityStopListener(this);
Marco Nelissen69f593c2009-07-28 09:55:04 -070075
76 // grab focus and key events so that pressing the volume buttons in the
77 // dialog doesn't also show the normal volume adjust toast.
78 view.setOnKeyListener(this);
79 view.setFocusableInTouchMode(true);
80 view.requestFocus();
81 }
82
83 public boolean onKey(View v, int keyCode, KeyEvent event) {
Amith Yamasani6ff59062009-08-25 15:40:14 -070084 // If key arrives immediately after the activity has been cleaned up.
85 if (mSeekBarVolumizer == null) return true;
Marco Nelissen69f593c2009-07-28 09:55:04 -070086 boolean isdown = (event.getAction() == KeyEvent.ACTION_DOWN);
87 switch (keyCode) {
88 case KeyEvent.KEYCODE_VOLUME_DOWN:
89 if (isdown) {
90 mSeekBarVolumizer.changeVolumeBy(-1);
91 }
92 return true;
93 case KeyEvent.KEYCODE_VOLUME_UP:
94 if (isdown) {
95 mSeekBarVolumizer.changeVolumeBy(1);
96 }
97 return true;
Jeff Brownb0418da2010-11-01 15:24:01 -070098 case KeyEvent.KEYCODE_VOLUME_MUTE:
99 if (isdown) {
100 mSeekBarVolumizer.muteVolume();
101 }
102 return true;
Marco Nelissen69f593c2009-07-28 09:55:04 -0700103 default:
104 return false;
105 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 }
107
108 @Override
109 protected void onDialogClosed(boolean positiveResult) {
110 super.onDialogClosed(positiveResult);
John Reck014fea22011-06-15 16:46:36 -0700111
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 if (!positiveResult && mSeekBarVolumizer != null) {
113 mSeekBarVolumizer.revertVolume();
114 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700115
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116 cleanup();
117 }
118
119 public void onActivityStop() {
Samuel Asteberga4588682011-02-03 14:30:58 +0100120 if (mSeekBarVolumizer != null) {
Eric Laurentaca105c2013-04-12 16:29:40 -0700121 mSeekBarVolumizer.postStopSample();
Samuel Asteberga4588682011-02-03 14:30:58 +0100122 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123 }
124
125 /**
126 * Do clean up. This can be called multiple times!
127 */
128 private void cleanup() {
129 getPreferenceManager().unregisterOnActivityStopListener(this);
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700130
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800131 if (mSeekBarVolumizer != null) {
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700132 Dialog dialog = getDialog();
133 if (dialog != null && dialog.isShowing()) {
Amith Yamasanie43530a2009-08-21 13:11:37 -0700134 View view = dialog.getWindow().getDecorView()
135 .findViewById(com.android.internal.R.id.seekbar);
136 if (view != null) view.setOnKeyListener(null);
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700137 // Stopped while dialog was showing, revert changes
138 mSeekBarVolumizer.revertVolume();
139 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 mSeekBarVolumizer.stop();
141 mSeekBarVolumizer = null;
142 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700143
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700145
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 protected void onSampleStarting(SeekBarVolumizer volumizer) {
147 if (mSeekBarVolumizer != null && volumizer != mSeekBarVolumizer) {
148 mSeekBarVolumizer.stopSample();
149 }
150 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700151
152 @Override
153 protected Parcelable onSaveInstanceState() {
154 final Parcelable superState = super.onSaveInstanceState();
155 if (isPersistent()) {
156 // No need to save instance state since it's persistent
157 return superState;
158 }
159
160 final SavedState myState = new SavedState(superState);
161 if (mSeekBarVolumizer != null) {
162 mSeekBarVolumizer.onSaveInstanceState(myState.getVolumeStore());
163 }
164 return myState;
165 }
166
167 @Override
168 protected void onRestoreInstanceState(Parcelable state) {
169 if (state == null || !state.getClass().equals(SavedState.class)) {
170 // Didn't save state for us in onSaveInstanceState
171 super.onRestoreInstanceState(state);
172 return;
173 }
174
175 SavedState myState = (SavedState) state;
176 super.onRestoreInstanceState(myState.getSuperState());
177 if (mSeekBarVolumizer != null) {
178 mSeekBarVolumizer.onRestoreInstanceState(myState.getVolumeStore());
179 }
180 }
181
182 public static class VolumeStore {
183 public int volume = -1;
184 public int originalVolume = -1;
185 }
186
187 private static class SavedState extends BaseSavedState {
188 VolumeStore mVolumeStore = new VolumeStore();
189
190 public SavedState(Parcel source) {
191 super(source);
192 mVolumeStore.volume = source.readInt();
193 mVolumeStore.originalVolume = source.readInt();
194 }
195
196 @Override
197 public void writeToParcel(Parcel dest, int flags) {
198 super.writeToParcel(dest, flags);
199 dest.writeInt(mVolumeStore.volume);
200 dest.writeInt(mVolumeStore.originalVolume);
201 }
202
203 VolumeStore getVolumeStore() {
204 return mVolumeStore;
205 }
206
207 public SavedState(Parcelable superState) {
208 super(superState);
209 }
210
211 public static final Parcelable.Creator<SavedState> CREATOR =
212 new Parcelable.Creator<SavedState>() {
213 public SavedState createFromParcel(Parcel in) {
214 return new SavedState(in);
215 }
216
217 public SavedState[] newArray(int size) {
218 return new SavedState[size];
219 }
220 };
221 }
222
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 /**
224 * Turns a {@link SeekBar} into a volume control.
225 */
Eric Laurentaca105c2013-04-12 16:29:40 -0700226 public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227
228 private Context mContext;
Eric Laurentaca105c2013-04-12 16:29:40 -0700229 private Handler mHandler;
John Reck014fea22011-06-15 16:46:36 -0700230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 private AudioManager mAudioManager;
232 private int mStreamType;
John Reck014fea22011-06-15 16:46:36 -0700233 private int mOriginalStreamVolume;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 private Ringtone mRingtone;
John Reck014fea22011-06-15 16:46:36 -0700235
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700236 private int mLastProgress = -1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 private SeekBar mSeekBar;
Jeff Brownb0418da2010-11-01 15:24:01 -0700238 private int mVolumeBeforeMute = -1;
John Reck014fea22011-06-15 16:46:36 -0700239
Eric Laurentaca105c2013-04-12 16:29:40 -0700240 private static final int MSG_SET_STREAM_VOLUME = 0;
241 private static final int MSG_START_SAMPLE = 1;
242 private static final int MSG_STOP_SAMPLE = 2;
243 private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
244
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 private ContentObserver mVolumeObserver = new ContentObserver(mHandler) {
246 @Override
247 public void onChange(boolean selfChange) {
248 super.onChange(selfChange);
Eric Laurent0f31fe02011-02-03 17:34:39 -0800249 if (mSeekBar != null && mAudioManager != null) {
Eric Laurent8c787522012-05-14 14:09:43 -0700250 int volume = mAudioManager.getStreamVolume(mStreamType);
Eric Laurent0f31fe02011-02-03 17:34:39 -0800251 mSeekBar.setProgress(volume);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 }
253 }
254 };
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700255
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType) {
Amith Yamasani998127c2011-01-31 20:28:03 -0800257 this(context, seekBar, streamType, null);
258 }
259
260 public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType, Uri defaultUri) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 mContext = context;
262 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
263 mStreamType = streamType;
264 mSeekBar = seekBar;
Amith Yamasani998127c2011-01-31 20:28:03 -0800265
Eric Laurentaca105c2013-04-12 16:29:40 -0700266 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
267 thread.start();
268 mHandler = new Handler(thread.getLooper(), this);
269
Amith Yamasani998127c2011-01-31 20:28:03 -0800270 initSeekBar(seekBar, defaultUri);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 }
272
Amith Yamasani998127c2011-01-31 20:28:03 -0800273 private void initSeekBar(SeekBar seekBar, Uri defaultUri) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800274 seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType));
275 mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
276 seekBar.setProgress(mOriginalStreamVolume);
277 seekBar.setOnSeekBarChangeListener(this);
John Reck014fea22011-06-15 16:46:36 -0700278
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800279 mContext.getContentResolver().registerContentObserver(
280 System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
281 false, mVolumeObserver);
Amith Yamasani998127c2011-01-31 20:28:03 -0800282
283 if (defaultUri == null) {
284 if (mStreamType == AudioManager.STREAM_RING) {
285 defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
286 } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
287 defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
288 } else {
289 defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
290 }
Patrick Scott3156bb002009-04-13 09:57:38 -0700291 }
292
293 mRingtone = RingtoneManager.getRingtone(mContext, defaultUri);
Amith Yamasani998127c2011-01-31 20:28:03 -0800294
Marco Nelissen69f593c2009-07-28 09:55:04 -0700295 if (mRingtone != null) {
296 mRingtone.setStreamType(mStreamType);
297 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 }
Amith Yamasani998127c2011-01-31 20:28:03 -0800299
Eric Laurentaca105c2013-04-12 16:29:40 -0700300 @Override
301 public boolean handleMessage(Message msg) {
302 switch (msg.what) {
303 case MSG_SET_STREAM_VOLUME:
304 mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0);
305 break;
306 case MSG_START_SAMPLE:
307 onStartSample();
308 break;
309 case MSG_STOP_SAMPLE:
310 onStopSample();
311 break;
312 default:
313 Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
314 }
315 return true;
316 }
317
318 private void postStartSample() {
319 mHandler.removeMessages(MSG_START_SAMPLE);
320 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
321 isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
322 }
323
324 private void onStartSample() {
325 if (!isSamplePlaying()) {
326 onSampleStarting(this);
327 if (mRingtone != null) {
328 mRingtone.play();
329 }
330 }
331 }
332
333 private void postStopSample() {
334 // remove pending delayed start messages
335 mHandler.removeMessages(MSG_START_SAMPLE);
336 mHandler.removeMessages(MSG_STOP_SAMPLE);
337 mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
338 }
339
340 private void onStopSample() {
341 if (mRingtone != null) {
342 mRingtone.stop();
343 }
344 }
345
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 public void stop() {
Eric Laurentaca105c2013-04-12 16:29:40 -0700347 postStopSample();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800348 mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
349 mSeekBar.setOnSeekBarChangeListener(null);
350 }
John Reck014fea22011-06-15 16:46:36 -0700351
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 public void revertVolume() {
353 mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
354 }
John Reck014fea22011-06-15 16:46:36 -0700355
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800356 public void onProgressChanged(SeekBar seekBar, int progress,
357 boolean fromTouch) {
358 if (!fromTouch) {
359 return;
360 }
John Reck014fea22011-06-15 16:46:36 -0700361
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 postSetVolume(progress);
363 }
364
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700365 void postSetVolume(int progress) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 // Do the volume changing separately to give responsive UI
367 mLastProgress = progress;
Eric Laurentaca105c2013-04-12 16:29:40 -0700368 mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
369 mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800370 }
John Reck014fea22011-06-15 16:46:36 -0700371
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372 public void onStartTrackingTouch(SeekBar seekBar) {
373 }
374
375 public void onStopTrackingTouch(SeekBar seekBar) {
Eric Laurentaca105c2013-04-12 16:29:40 -0700376 postStartSample();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377 }
Amith Yamasani998127c2011-01-31 20:28:03 -0800378
379 public boolean isSamplePlaying() {
380 return mRingtone != null && mRingtone.isPlaying();
381 }
382
383 public void startSample() {
Eric Laurentaca105c2013-04-12 16:29:40 -0700384 postStartSample();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 }
John Reck014fea22011-06-15 16:46:36 -0700386
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 public void stopSample() {
Eric Laurentaca105c2013-04-12 16:29:40 -0700388 postStopSample();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 }
390
391 public SeekBar getSeekBar() {
392 return mSeekBar;
393 }
John Reck014fea22011-06-15 16:46:36 -0700394
Marco Nelissen69f593c2009-07-28 09:55:04 -0700395 public void changeVolumeBy(int amount) {
396 mSeekBar.incrementProgressBy(amount);
Marco Nelissen69f593c2009-07-28 09:55:04 -0700397 postSetVolume(mSeekBar.getProgress());
Eric Laurentaca105c2013-04-12 16:29:40 -0700398 postStartSample();
Jeff Brownb0418da2010-11-01 15:24:01 -0700399 mVolumeBeforeMute = -1;
400 }
401
402 public void muteVolume() {
403 if (mVolumeBeforeMute != -1) {
404 mSeekBar.setProgress(mVolumeBeforeMute);
Jeff Brownb0418da2010-11-01 15:24:01 -0700405 postSetVolume(mVolumeBeforeMute);
Eric Laurentaca105c2013-04-12 16:29:40 -0700406 postStartSample();
Jeff Brownb0418da2010-11-01 15:24:01 -0700407 mVolumeBeforeMute = -1;
408 } else {
409 mVolumeBeforeMute = mSeekBar.getProgress();
410 mSeekBar.setProgress(0);
Eric Laurentaca105c2013-04-12 16:29:40 -0700411 postStopSample();
Jeff Brownb0418da2010-11-01 15:24:01 -0700412 postSetVolume(0);
413 }
Marco Nelissen69f593c2009-07-28 09:55:04 -0700414 }
Amith Yamasani9b4742c2009-08-24 17:28:17 -0700415
416 public void onSaveInstanceState(VolumeStore volumeStore) {
417 if (mLastProgress >= 0) {
418 volumeStore.volume = mLastProgress;
419 volumeStore.originalVolume = mOriginalStreamVolume;
420 }
421 }
422
423 public void onRestoreInstanceState(VolumeStore volumeStore) {
424 if (volumeStore.volume != -1) {
425 mOriginalStreamVolume = volumeStore.originalVolume;
426 mLastProgress = volumeStore.volume;
427 postSetVolume(mLastProgress);
428 }
429 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 }
431}