VolumePreference.java revision 998127c8049f22cb6f74483f11d693bfd7c59511
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 // If key arrives immediately after the activity has been cleaned up. 82 if (mSeekBarVolumizer == null) return true; 83 boolean isdown = (event.getAction() == KeyEvent.ACTION_DOWN); 84 switch (keyCode) { 85 case KeyEvent.KEYCODE_VOLUME_DOWN: 86 if (isdown) { 87 mSeekBarVolumizer.changeVolumeBy(-1); 88 } 89 return true; 90 case KeyEvent.KEYCODE_VOLUME_UP: 91 if (isdown) { 92 mSeekBarVolumizer.changeVolumeBy(1); 93 } 94 return true; 95 case KeyEvent.KEYCODE_VOLUME_MUTE: 96 if (isdown) { 97 mSeekBarVolumizer.muteVolume(); 98 } 99 return true; 100 default: 101 return false; 102 } 103 } 104 105 @Override 106 protected void onDialogClosed(boolean positiveResult) { 107 super.onDialogClosed(positiveResult); 108 109 if (!positiveResult && mSeekBarVolumizer != null) { 110 mSeekBarVolumizer.revertVolume(); 111 } 112 113 cleanup(); 114 } 115 116 public void onActivityStop() { 117 cleanup(); 118 } 119 120 /** 121 * Do clean up. This can be called multiple times! 122 */ 123 private void cleanup() { 124 getPreferenceManager().unregisterOnActivityStopListener(this); 125 126 if (mSeekBarVolumizer != null) { 127 Dialog dialog = getDialog(); 128 if (dialog != null && dialog.isShowing()) { 129 View view = dialog.getWindow().getDecorView() 130 .findViewById(com.android.internal.R.id.seekbar); 131 if (view != null) view.setOnKeyListener(null); 132 // Stopped while dialog was showing, revert changes 133 mSeekBarVolumizer.revertVolume(); 134 } 135 mSeekBarVolumizer.stop(); 136 mSeekBarVolumizer = null; 137 } 138 139 } 140 141 protected void onSampleStarting(SeekBarVolumizer volumizer) { 142 if (mSeekBarVolumizer != null && volumizer != mSeekBarVolumizer) { 143 mSeekBarVolumizer.stopSample(); 144 } 145 } 146 147 @Override 148 protected Parcelable onSaveInstanceState() { 149 final Parcelable superState = super.onSaveInstanceState(); 150 if (isPersistent()) { 151 // No need to save instance state since it's persistent 152 return superState; 153 } 154 155 final SavedState myState = new SavedState(superState); 156 if (mSeekBarVolumizer != null) { 157 mSeekBarVolumizer.onSaveInstanceState(myState.getVolumeStore()); 158 } 159 return myState; 160 } 161 162 @Override 163 protected void onRestoreInstanceState(Parcelable state) { 164 if (state == null || !state.getClass().equals(SavedState.class)) { 165 // Didn't save state for us in onSaveInstanceState 166 super.onRestoreInstanceState(state); 167 return; 168 } 169 170 SavedState myState = (SavedState) state; 171 super.onRestoreInstanceState(myState.getSuperState()); 172 if (mSeekBarVolumizer != null) { 173 mSeekBarVolumizer.onRestoreInstanceState(myState.getVolumeStore()); 174 } 175 } 176 177 public static class VolumeStore { 178 public int volume = -1; 179 public int originalVolume = -1; 180 } 181 182 private static class SavedState extends BaseSavedState { 183 VolumeStore mVolumeStore = new VolumeStore(); 184 185 public SavedState(Parcel source) { 186 super(source); 187 mVolumeStore.volume = source.readInt(); 188 mVolumeStore.originalVolume = source.readInt(); 189 } 190 191 @Override 192 public void writeToParcel(Parcel dest, int flags) { 193 super.writeToParcel(dest, flags); 194 dest.writeInt(mVolumeStore.volume); 195 dest.writeInt(mVolumeStore.originalVolume); 196 } 197 198 VolumeStore getVolumeStore() { 199 return mVolumeStore; 200 } 201 202 public SavedState(Parcelable superState) { 203 super(superState); 204 } 205 206 public static final Parcelable.Creator<SavedState> CREATOR = 207 new Parcelable.Creator<SavedState>() { 208 public SavedState createFromParcel(Parcel in) { 209 return new SavedState(in); 210 } 211 212 public SavedState[] newArray(int size) { 213 return new SavedState[size]; 214 } 215 }; 216 } 217 218 /** 219 * Turns a {@link SeekBar} into a volume control. 220 */ 221 public class SeekBarVolumizer implements OnSeekBarChangeListener, Runnable { 222 223 private Context mContext; 224 private Handler mHandler = new Handler(); 225 226 private AudioManager mAudioManager; 227 private int mStreamType; 228 private int mOriginalStreamVolume; 229 private Ringtone mRingtone; 230 231 private int mLastProgress = -1; 232 private SeekBar mSeekBar; 233 private int mVolumeBeforeMute = -1; 234 235 private ContentObserver mVolumeObserver = new ContentObserver(mHandler) { 236 @Override 237 public void onChange(boolean selfChange) { 238 super.onChange(selfChange); 239 if (mSeekBar != null) { 240 int volume = System.getInt(mContext.getContentResolver(), 241 System.VOLUME_SETTINGS[mStreamType], -1); 242 // Works around an atomicity problem with volume updates 243 // TODO: Fix the actual issue, probably in AudioService 244 if (volume >= 0) { 245 mSeekBar.setProgress(volume); 246 } 247 } 248 } 249 }; 250 251 public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType) { 252 this(context, seekBar, streamType, null); 253 } 254 255 public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType, Uri defaultUri) { 256 mContext = context; 257 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 258 mStreamType = streamType; 259 mSeekBar = seekBar; 260 261 initSeekBar(seekBar, defaultUri); 262 } 263 264 private void initSeekBar(SeekBar seekBar, Uri defaultUri) { 265 seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType)); 266 mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType); 267 seekBar.setProgress(mOriginalStreamVolume); 268 seekBar.setOnSeekBarChangeListener(this); 269 270 mContext.getContentResolver().registerContentObserver( 271 System.getUriFor(System.VOLUME_SETTINGS[mStreamType]), 272 false, mVolumeObserver); 273 274 if (defaultUri == null) { 275 if (mStreamType == AudioManager.STREAM_RING) { 276 defaultUri = Settings.System.DEFAULT_RINGTONE_URI; 277 } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) { 278 defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI; 279 } else { 280 defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI; 281 } 282 } 283 284 mRingtone = RingtoneManager.getRingtone(mContext, defaultUri); 285 286 if (mRingtone != null) { 287 mRingtone.setStreamType(mStreamType); 288 } 289 } 290 291 public void stop() { 292 stopSample(); 293 mContext.getContentResolver().unregisterContentObserver(mVolumeObserver); 294 mSeekBar.setOnSeekBarChangeListener(null); 295 } 296 297 public void revertVolume() { 298 mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0); 299 } 300 301 public void onProgressChanged(SeekBar seekBar, int progress, 302 boolean fromTouch) { 303 if (!fromTouch) { 304 return; 305 } 306 307 postSetVolume(progress); 308 } 309 310 void postSetVolume(int progress) { 311 // Do the volume changing separately to give responsive UI 312 mLastProgress = progress; 313 mHandler.removeCallbacks(this); 314 mHandler.post(this); 315 } 316 317 public void onStartTrackingTouch(SeekBar seekBar) { 318 } 319 320 public void onStopTrackingTouch(SeekBar seekBar) { 321 if (!isSamplePlaying()) { 322 startSample(); 323 } 324 } 325 326 public void run() { 327 mAudioManager.setStreamVolume(mStreamType, mLastProgress, 0); 328 } 329 330 public boolean isSamplePlaying() { 331 return mRingtone != null && mRingtone.isPlaying(); 332 } 333 334 public void startSample() { 335 onSampleStarting(this); 336 if (mRingtone != null) { 337 mRingtone.play(); 338 } 339 } 340 341 public void stopSample() { 342 if (mRingtone != null) { 343 mRingtone.stop(); 344 } 345 } 346 347 public SeekBar getSeekBar() { 348 return mSeekBar; 349 } 350 351 public void changeVolumeBy(int amount) { 352 mSeekBar.incrementProgressBy(amount); 353 if (!isSamplePlaying()) { 354 startSample(); 355 } 356 postSetVolume(mSeekBar.getProgress()); 357 mVolumeBeforeMute = -1; 358 } 359 360 public void muteVolume() { 361 if (mVolumeBeforeMute != -1) { 362 mSeekBar.setProgress(mVolumeBeforeMute); 363 startSample(); 364 postSetVolume(mVolumeBeforeMute); 365 mVolumeBeforeMute = -1; 366 } else { 367 mVolumeBeforeMute = mSeekBar.getProgress(); 368 mSeekBar.setProgress(0); 369 stopSample(); 370 postSetVolume(0); 371 } 372 } 373 374 public void onSaveInstanceState(VolumeStore volumeStore) { 375 if (mLastProgress >= 0) { 376 volumeStore.volume = mLastProgress; 377 volumeStore.originalVolume = mOriginalStreamVolume; 378 } 379 } 380 381 public void onRestoreInstanceState(VolumeStore volumeStore) { 382 if (volumeStore.volume != -1) { 383 mOriginalStreamVolume = volumeStore.originalVolume; 384 mLastProgress = volumeStore.volume; 385 postSetVolume(mLastProgress); 386 } 387 } 388 } 389} 390