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.HandlerThread;
     29 import android.os.Message;
     30 import android.os.Parcel;
     31 import android.os.Parcelable;
     32 import android.provider.Settings;
     33 import android.provider.Settings.System;
     34 import android.util.AttributeSet;
     35 import android.util.Log;
     36 import android.view.KeyEvent;
     37 import android.view.View;
     38 import android.widget.SeekBar;
     39 import android.widget.SeekBar.OnSeekBarChangeListener;
     40 
     41 /**
     42  * @hide
     43  */
     44 public class VolumePreference extends SeekBarDialogPreference implements
     45         PreferenceManager.OnActivityStopListener, View.OnKeyListener {
     46 
     47     private static final String TAG = "VolumePreference";
     48 
     49     private int mStreamType;
     50 
     51     /** May be null if the dialog isn't visible. */
     52     private SeekBarVolumizer mSeekBarVolumizer;
     53 
     54     public VolumePreference(Context context, AttributeSet attrs) {
     55         super(context, attrs);
     56 
     57         TypedArray a = context.obtainStyledAttributes(attrs,
     58                 com.android.internal.R.styleable.VolumePreference, 0, 0);
     59         mStreamType = a.getInt(android.R.styleable.VolumePreference_streamType, 0);
     60         a.recycle();
     61     }
     62 
     63     public void setStreamType(int streamType) {
     64         mStreamType = streamType;
     65     }
     66 
     67     @Override
     68     protected void onBindDialogView(View view) {
     69         super.onBindDialogView(view);
     70 
     71         final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
     72         mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType);
     73 
     74         getPreferenceManager().registerOnActivityStopListener(this);
     75 
     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) {
     84         // If key arrives immediately after the activity has been cleaned up.
     85         if (mSeekBarVolumizer == null) return true;
     86         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;
     98             case KeyEvent.KEYCODE_VOLUME_MUTE:
     99                 if (isdown) {
    100                     mSeekBarVolumizer.muteVolume();
    101                 }
    102                 return true;
    103             default:
    104                 return false;
    105         }
    106     }
    107 
    108     @Override
    109     protected void onDialogClosed(boolean positiveResult) {
    110         super.onDialogClosed(positiveResult);
    111 
    112         if (!positiveResult && mSeekBarVolumizer != null) {
    113             mSeekBarVolumizer.revertVolume();
    114         }
    115 
    116         cleanup();
    117     }
    118 
    119     public void onActivityStop() {
    120         if (mSeekBarVolumizer != null) {
    121             mSeekBarVolumizer.postStopSample();
    122         }
    123     }
    124 
    125     /**
    126      * Do clean up.  This can be called multiple times!
    127      */
    128     private void cleanup() {
    129        getPreferenceManager().unregisterOnActivityStopListener(this);
    130 
    131        if (mSeekBarVolumizer != null) {
    132            Dialog dialog = getDialog();
    133            if (dialog != null && dialog.isShowing()) {
    134                View view = dialog.getWindow().getDecorView()
    135                        .findViewById(com.android.internal.R.id.seekbar);
    136                if (view != null) view.setOnKeyListener(null);
    137                // Stopped while dialog was showing, revert changes
    138                mSeekBarVolumizer.revertVolume();
    139            }
    140            mSeekBarVolumizer.stop();
    141            mSeekBarVolumizer = null;
    142        }
    143 
    144     }
    145 
    146     protected void onSampleStarting(SeekBarVolumizer volumizer) {
    147         if (mSeekBarVolumizer != null && volumizer != mSeekBarVolumizer) {
    148             mSeekBarVolumizer.stopSample();
    149         }
    150     }
    151 
    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 
    223     /**
    224      * Turns a {@link SeekBar} into a volume control.
    225      */
    226     public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
    227 
    228         private Context mContext;
    229         private Handler mHandler;
    230 
    231         private AudioManager mAudioManager;
    232         private int mStreamType;
    233         private int mOriginalStreamVolume;
    234         private Ringtone mRingtone;
    235 
    236         private int mLastProgress = -1;
    237         private SeekBar mSeekBar;
    238         private int mVolumeBeforeMute = -1;
    239 
    240         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 
    245         private ContentObserver mVolumeObserver = new ContentObserver(mHandler) {
    246             @Override
    247             public void onChange(boolean selfChange) {
    248                 super.onChange(selfChange);
    249                 if (mSeekBar != null && mAudioManager != null) {
    250                     int volume = mAudioManager.getStreamVolume(mStreamType);
    251                     mSeekBar.setProgress(volume);
    252                 }
    253             }
    254         };
    255 
    256         public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType) {
    257             this(context, seekBar, streamType, null);
    258         }
    259 
    260         public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType, Uri defaultUri) {
    261             mContext = context;
    262             mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    263             mStreamType = streamType;
    264             mSeekBar = seekBar;
    265 
    266             HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
    267             thread.start();
    268             mHandler = new Handler(thread.getLooper(), this);
    269 
    270             initSeekBar(seekBar, defaultUri);
    271         }
    272 
    273         private void initSeekBar(SeekBar seekBar, Uri defaultUri) {
    274             seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType));
    275             mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
    276             seekBar.setProgress(mOriginalStreamVolume);
    277             seekBar.setOnSeekBarChangeListener(this);
    278 
    279             mContext.getContentResolver().registerContentObserver(
    280                     System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
    281                     false, mVolumeObserver);
    282 
    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                 }
    291             }
    292 
    293             mRingtone = RingtoneManager.getRingtone(mContext, defaultUri);
    294 
    295             if (mRingtone != null) {
    296                 mRingtone.setStreamType(mStreamType);
    297             }
    298         }
    299 
    300         @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 
    346         public void stop() {
    347             postStopSample();
    348             mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
    349             mSeekBar.setOnSeekBarChangeListener(null);
    350         }
    351 
    352         public void revertVolume() {
    353             mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
    354         }
    355 
    356         public void onProgressChanged(SeekBar seekBar, int progress,
    357                 boolean fromTouch) {
    358             if (!fromTouch) {
    359                 return;
    360             }
    361 
    362             postSetVolume(progress);
    363         }
    364 
    365         void postSetVolume(int progress) {
    366             // Do the volume changing separately to give responsive UI
    367             mLastProgress = progress;
    368             mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
    369             mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
    370         }
    371 
    372         public void onStartTrackingTouch(SeekBar seekBar) {
    373         }
    374 
    375         public void onStopTrackingTouch(SeekBar seekBar) {
    376             postStartSample();
    377         }
    378 
    379         public boolean isSamplePlaying() {
    380             return mRingtone != null && mRingtone.isPlaying();
    381         }
    382 
    383         public void startSample() {
    384             postStartSample();
    385         }
    386 
    387         public void stopSample() {
    388             postStopSample();
    389         }
    390 
    391         public SeekBar getSeekBar() {
    392             return mSeekBar;
    393         }
    394 
    395         public void changeVolumeBy(int amount) {
    396             mSeekBar.incrementProgressBy(amount);
    397             postSetVolume(mSeekBar.getProgress());
    398             postStartSample();
    399             mVolumeBeforeMute = -1;
    400         }
    401 
    402         public void muteVolume() {
    403             if (mVolumeBeforeMute != -1) {
    404                 mSeekBar.setProgress(mVolumeBeforeMute);
    405                 postSetVolume(mVolumeBeforeMute);
    406                 postStartSample();
    407                 mVolumeBeforeMute = -1;
    408             } else {
    409                 mVolumeBeforeMute = mSeekBar.getProgress();
    410                 mSeekBar.setProgress(0);
    411                 postStopSample();
    412                 postSetVolume(0);
    413             }
    414         }
    415 
    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         }
    430     }
    431 }
    432