Home | History | Annotate | Download | only in preference
      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