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