VolumePreference.java revision 9b4742cf25c2cd7e10d87554c1e8a60c6702edaa
1/* 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 19import android.app.Dialog; 20import android.content.Context; 21import android.content.res.TypedArray; 22import android.database.ContentObserver; 23import android.media.AudioManager; 24import android.media.Ringtone; 25import android.media.RingtoneManager; 26import android.net.Uri; 27import android.os.Handler; 28import android.os.Parcel; 29import android.os.Parcelable; 30import android.provider.Settings; 31import android.provider.Settings.System; 32import android.util.AttributeSet; 33import android.view.KeyEvent; 34import android.view.View; 35import android.widget.SeekBar; 36import android.widget.SeekBar.OnSeekBarChangeListener; 37 38/** 39 * @hide 40 */ 41public class VolumePreference extends SeekBarPreference implements 42 PreferenceManager.OnActivityStopListener, View.OnKeyListener { 43 44 private static final String TAG = "VolumePreference"; 45 46 private int mStreamType; 47 48 /** May be null if the dialog isn't visible. */ 49 private SeekBarVolumizer mSeekBarVolumizer; 50 51 public VolumePreference(Context context, AttributeSet attrs) { 52 super(context, attrs); 53 54 TypedArray a = context.obtainStyledAttributes(attrs, 55 com.android.internal.R.styleable.VolumePreference, 0, 0); 56 mStreamType = a.getInt(android.R.styleable.VolumePreference_streamType, 0); 57 a.recycle(); 58 } 59 60 public void setStreamType(int streamType) { 61 mStreamType = streamType; 62 } 63 64 @Override 65 protected void onBindDialogView(View view) { 66 super.onBindDialogView(view); 67 68 final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar); 69 mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType); 70 71 getPreferenceManager().registerOnActivityStopListener(this); 72 73 // grab focus and key events so that pressing the volume buttons in the 74 // dialog doesn't also show the normal volume adjust toast. 75 view.setOnKeyListener(this); 76 view.setFocusableInTouchMode(true); 77 view.requestFocus(); 78 } 79 80 public boolean onKey(View v, int keyCode, KeyEvent event) { 81 boolean isdown = (event.getAction() == KeyEvent.ACTION_DOWN); 82 switch (keyCode) { 83 case KeyEvent.KEYCODE_VOLUME_DOWN: 84 if (isdown) { 85 mSeekBarVolumizer.changeVolumeBy(-1); 86 } 87 return true; 88 case KeyEvent.KEYCODE_VOLUME_UP: 89 if (isdown) { 90 mSeekBarVolumizer.changeVolumeBy(1); 91 } 92 return true; 93 default: 94 return false; 95 } 96 } 97 98 @Override 99 protected void onDialogClosed(boolean positiveResult) { 100 super.onDialogClosed(positiveResult); 101 102 if (!positiveResult && mSeekBarVolumizer != null) { 103 mSeekBarVolumizer.revertVolume(); 104 } 105 106 cleanup(); 107 } 108 109 public void onActivityStop() { 110 cleanup(); 111 } 112 113 /** 114 * Do clean up. This can be called multiple times! 115 */ 116 private void cleanup() { 117 getPreferenceManager().unregisterOnActivityStopListener(this); 118 119 if (mSeekBarVolumizer != null) { 120 Dialog dialog = getDialog(); 121 if (dialog != null && dialog.isShowing()) { 122 // Stopped while dialog was showing, revert changes 123 mSeekBarVolumizer.revertVolume(); 124 } 125 mSeekBarVolumizer.stop(); 126 mSeekBarVolumizer = null; 127 } 128 129 } 130 131 protected void onSampleStarting(SeekBarVolumizer volumizer) { 132 if (mSeekBarVolumizer != null && volumizer != mSeekBarVolumizer) { 133 mSeekBarVolumizer.stopSample(); 134 } 135 } 136 137 @Override 138 protected Parcelable onSaveInstanceState() { 139 final Parcelable superState = super.onSaveInstanceState(); 140 if (isPersistent()) { 141 // No need to save instance state since it's persistent 142 return superState; 143 } 144 145 final SavedState myState = new SavedState(superState); 146 if (mSeekBarVolumizer != null) { 147 mSeekBarVolumizer.onSaveInstanceState(myState.getVolumeStore()); 148 } 149 return myState; 150 } 151 152 @Override 153 protected void onRestoreInstanceState(Parcelable state) { 154 if (state == null || !state.getClass().equals(SavedState.class)) { 155 // Didn't save state for us in onSaveInstanceState 156 super.onRestoreInstanceState(state); 157 return; 158 } 159 160 SavedState myState = (SavedState) state; 161 super.onRestoreInstanceState(myState.getSuperState()); 162 if (mSeekBarVolumizer != null) { 163 mSeekBarVolumizer.onRestoreInstanceState(myState.getVolumeStore()); 164 } 165 } 166 167 public static class VolumeStore { 168 public int volume = -1; 169 public int originalVolume = -1; 170 } 171 172 private static class SavedState extends BaseSavedState { 173 VolumeStore mVolumeStore = new VolumeStore(); 174 175 public SavedState(Parcel source) { 176 super(source); 177 mVolumeStore.volume = source.readInt(); 178 mVolumeStore.originalVolume = source.readInt(); 179 } 180 181 @Override 182 public void writeToParcel(Parcel dest, int flags) { 183 super.writeToParcel(dest, flags); 184 dest.writeInt(mVolumeStore.volume); 185 dest.writeInt(mVolumeStore.originalVolume); 186 } 187 188 VolumeStore getVolumeStore() { 189 return mVolumeStore; 190 } 191 192 public SavedState(Parcelable superState) { 193 super(superState); 194 } 195 196 public static final Parcelable.Creator<SavedState> CREATOR = 197 new Parcelable.Creator<SavedState>() { 198 public SavedState createFromParcel(Parcel in) { 199 return new SavedState(in); 200 } 201 202 public SavedState[] newArray(int size) { 203 return new SavedState[size]; 204 } 205 }; 206 } 207 208 /** 209 * Turns a {@link SeekBar} into a volume control. 210 */ 211 public class SeekBarVolumizer implements OnSeekBarChangeListener, Runnable { 212 213 private Context mContext; 214 private Handler mHandler = new Handler(); 215 216 private AudioManager mAudioManager; 217 private int mStreamType; 218 private int mOriginalStreamVolume; 219 private Ringtone mRingtone; 220 221 private int mLastProgress = -1; 222 private SeekBar mSeekBar; 223 224 private ContentObserver mVolumeObserver = new ContentObserver(mHandler) { 225 @Override 226 public void onChange(boolean selfChange) { 227 super.onChange(selfChange); 228 229 if (mSeekBar != null) { 230 mSeekBar.setProgress(System.getInt(mContext.getContentResolver(), 231 System.VOLUME_SETTINGS[mStreamType], 0)); 232 } 233 } 234 }; 235 236 public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType) { 237 mContext = context; 238 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 239 mStreamType = streamType; 240 mSeekBar = seekBar; 241 242 initSeekBar(seekBar); 243 } 244 245 private void initSeekBar(SeekBar seekBar) { 246 seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType)); 247 mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType); 248 seekBar.setProgress(mOriginalStreamVolume); 249 seekBar.setOnSeekBarChangeListener(this); 250 251 mContext.getContentResolver().registerContentObserver( 252 System.getUriFor(System.VOLUME_SETTINGS[mStreamType]), 253 false, mVolumeObserver); 254 255 Uri defaultUri = null; 256 if (mStreamType == AudioManager.STREAM_RING) { 257 defaultUri = Settings.System.DEFAULT_RINGTONE_URI; 258 } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) { 259 defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI; 260 } else { 261 defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI; 262 } 263 264 mRingtone = RingtoneManager.getRingtone(mContext, defaultUri); 265 if (mRingtone != null) { 266 mRingtone.setStreamType(mStreamType); 267 } 268 } 269 270 public void stop() { 271 stopSample(); 272 mContext.getContentResolver().unregisterContentObserver(mVolumeObserver); 273 mSeekBar.setOnSeekBarChangeListener(null); 274 } 275 276 public void revertVolume() { 277 mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0); 278 } 279 280 public void onProgressChanged(SeekBar seekBar, int progress, 281 boolean fromTouch) { 282 if (!fromTouch) { 283 return; 284 } 285 286 postSetVolume(progress); 287 } 288 289 void postSetVolume(int progress) { 290 // Do the volume changing separately to give responsive UI 291 mLastProgress = progress; 292 mHandler.removeCallbacks(this); 293 mHandler.post(this); 294 } 295 296 public void onStartTrackingTouch(SeekBar seekBar) { 297 } 298 299 public void onStopTrackingTouch(SeekBar seekBar) { 300 if (mRingtone != null && !mRingtone.isPlaying()) { 301 sample(); 302 } 303 } 304 305 public void run() { 306 mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0); 307 } 308 309 private void sample() { 310 onSampleStarting(this); 311 mRingtone.play(); 312 } 313 314 public void stopSample() { 315 if (mRingtone != null) { 316 mRingtone.stop(); 317 } 318 } 319 320 public SeekBar getSeekBar() { 321 return mSeekBar; 322 } 323 324 public void changeVolumeBy(int amount) { 325 mSeekBar.incrementProgressBy(amount); 326 if (mRingtone != null && !mRingtone.isPlaying()) { 327 sample(); 328 } 329 postSetVolume(mSeekBar.getProgress()); 330 } 331 332 public void onSaveInstanceState(VolumeStore volumeStore) { 333 if (mLastProgress >= 0) { 334 volumeStore.volume = mLastProgress; 335 volumeStore.originalVolume = mOriginalStreamVolume; 336 } 337 } 338 339 public void onRestoreInstanceState(VolumeStore volumeStore) { 340 if (volumeStore.volume != -1) { 341 mOriginalStreamVolume = volumeStore.originalVolume; 342 mLastProgress = volumeStore.volume; 343 postSetVolume(mLastProgress); 344 } 345 } 346 } 347} 348