Home | History | Annotate | Download | only in preference
      1 /*
      2  * Copyright (C) 2014 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.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.database.ContentObserver;
     24 import android.media.AudioManager;
     25 import android.media.Ringtone;
     26 import android.media.RingtoneManager;
     27 import android.net.Uri;
     28 import android.os.Handler;
     29 import android.os.HandlerThread;
     30 import android.os.Message;
     31 import android.preference.VolumePreference.VolumeStore;
     32 import android.provider.Settings;
     33 import android.provider.Settings.System;
     34 import android.util.Log;
     35 import android.widget.SeekBar;
     36 import android.widget.SeekBar.OnSeekBarChangeListener;
     37 
     38 /**
     39  * Turns a {@link SeekBar} into a volume control.
     40  * @hide
     41  */
     42 public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
     43     private static final String TAG = "SeekBarVolumizer";
     44 
     45     public interface Callback {
     46         void onSampleStarting(SeekBarVolumizer sbv);
     47         void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch);
     48         void onMuted(boolean muted);
     49     }
     50 
     51     private final Context mContext;
     52     private final H mUiHandler = new H();
     53     private final Callback mCallback;
     54     private final Uri mDefaultUri;
     55     private final AudioManager mAudioManager;
     56     private final int mStreamType;
     57     private final int mMaxStreamVolume;
     58     private boolean mAffectedByRingerMode;
     59     private boolean mNotificationOrRing;
     60     private final Receiver mReceiver = new Receiver();
     61 
     62     private Handler mHandler;
     63     private Observer mVolumeObserver;
     64     private int mOriginalStreamVolume;
     65     private Ringtone mRingtone;
     66     private int mLastProgress = -1;
     67     private boolean mMuted;
     68     private SeekBar mSeekBar;
     69     private int mVolumeBeforeMute = -1;
     70     private int mRingerMode;
     71 
     72     private static final int MSG_SET_STREAM_VOLUME = 0;
     73     private static final int MSG_START_SAMPLE = 1;
     74     private static final int MSG_STOP_SAMPLE = 2;
     75     private static final int MSG_INIT_SAMPLE = 3;
     76     private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
     77 
     78     public SeekBarVolumizer(Context context, int streamType, Uri defaultUri, Callback callback) {
     79         mContext = context;
     80         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
     81         mStreamType = streamType;
     82         mAffectedByRingerMode = mAudioManager.isStreamAffectedByRingerMode(mStreamType);
     83         mNotificationOrRing = isNotificationOrRing(mStreamType);
     84         if (mNotificationOrRing) {
     85             mRingerMode = mAudioManager.getRingerModeInternal();
     86         }
     87         mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
     88         mCallback = callback;
     89         mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
     90         mMuted = mAudioManager.isStreamMute(mStreamType);
     91         if (mCallback != null) {
     92             mCallback.onMuted(mMuted);
     93         }
     94         if (defaultUri == null) {
     95             if (mStreamType == AudioManager.STREAM_RING) {
     96                 defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
     97             } else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
     98                 defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
     99             } else {
    100                 defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
    101             }
    102         }
    103         mDefaultUri = defaultUri;
    104     }
    105 
    106     private static boolean isNotificationOrRing(int stream) {
    107         return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
    108     }
    109 
    110     public void setSeekBar(SeekBar seekBar) {
    111         if (mSeekBar != null) {
    112             mSeekBar.setOnSeekBarChangeListener(null);
    113         }
    114         mSeekBar = seekBar;
    115         mSeekBar.setOnSeekBarChangeListener(null);
    116         mSeekBar.setMax(mMaxStreamVolume);
    117         updateSeekBar();
    118         mSeekBar.setOnSeekBarChangeListener(this);
    119     }
    120 
    121     protected void updateSeekBar() {
    122         if (mNotificationOrRing && mRingerMode == AudioManager.RINGER_MODE_VIBRATE) {
    123             mSeekBar.setEnabled(true);
    124             mSeekBar.setProgress(0);
    125         } else if (mMuted) {
    126             mSeekBar.setEnabled(false);
    127             mSeekBar.setProgress(0);
    128         } else {
    129             mSeekBar.setEnabled(true);
    130             mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
    131         }
    132     }
    133 
    134     @Override
    135     public boolean handleMessage(Message msg) {
    136         switch (msg.what) {
    137             case MSG_SET_STREAM_VOLUME:
    138                 mAudioManager.setStreamVolume(mStreamType, mLastProgress,
    139                         AudioManager.FLAG_SHOW_UI_WARNINGS);
    140                 break;
    141             case MSG_START_SAMPLE:
    142                 onStartSample();
    143                 break;
    144             case MSG_STOP_SAMPLE:
    145                 onStopSample();
    146                 break;
    147             case MSG_INIT_SAMPLE:
    148                 onInitSample();
    149                 break;
    150             default:
    151                 Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
    152         }
    153         return true;
    154     }
    155 
    156     private void onInitSample() {
    157         mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
    158         if (mRingtone != null) {
    159             mRingtone.setStreamType(mStreamType);
    160         }
    161     }
    162 
    163     private void postStartSample() {
    164         if (mHandler == null) return;
    165         mHandler.removeMessages(MSG_START_SAMPLE);
    166         mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
    167                 isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
    168     }
    169 
    170     private void onStartSample() {
    171         if (!isSamplePlaying()) {
    172             if (mCallback != null) {
    173                 mCallback.onSampleStarting(this);
    174             }
    175             if (mRingtone != null) {
    176                 try {
    177                     mRingtone.play();
    178                 } catch (Throwable e) {
    179                     Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
    180                 }
    181             }
    182         }
    183     }
    184 
    185     private void postStopSample() {
    186         if (mHandler == null) return;
    187         // remove pending delayed start messages
    188         mHandler.removeMessages(MSG_START_SAMPLE);
    189         mHandler.removeMessages(MSG_STOP_SAMPLE);
    190         mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
    191     }
    192 
    193     private void onStopSample() {
    194         if (mRingtone != null) {
    195             mRingtone.stop();
    196         }
    197     }
    198 
    199     public void stop() {
    200         if (mHandler == null) return;  // already stopped
    201         postStopSample();
    202         mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
    203         mReceiver.setListening(false);
    204         mSeekBar.setOnSeekBarChangeListener(null);
    205         mHandler.getLooper().quitSafely();
    206         mHandler = null;
    207         mVolumeObserver = null;
    208     }
    209 
    210     public void start() {
    211         if (mHandler != null) return;  // already started
    212         HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
    213         thread.start();
    214         mHandler = new Handler(thread.getLooper(), this);
    215         mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
    216         mVolumeObserver = new Observer(mHandler);
    217         mContext.getContentResolver().registerContentObserver(
    218                 System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
    219                 false, mVolumeObserver);
    220         mReceiver.setListening(true);
    221     }
    222 
    223     public void revertVolume() {
    224         mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
    225     }
    226 
    227     public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
    228         if (fromTouch) {
    229             postSetVolume(progress);
    230         }
    231         if (mCallback != null) {
    232             mCallback.onProgressChanged(seekBar, progress, fromTouch);
    233         }
    234     }
    235 
    236     private void postSetVolume(int progress) {
    237         if (mHandler == null) return;
    238         // Do the volume changing separately to give responsive UI
    239         mLastProgress = progress;
    240         mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
    241         mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
    242     }
    243 
    244     public void onStartTrackingTouch(SeekBar seekBar) {
    245     }
    246 
    247     public void onStopTrackingTouch(SeekBar seekBar) {
    248         postStartSample();
    249     }
    250 
    251     public boolean isSamplePlaying() {
    252         return mRingtone != null && mRingtone.isPlaying();
    253     }
    254 
    255     public void startSample() {
    256         postStartSample();
    257     }
    258 
    259     public void stopSample() {
    260         postStopSample();
    261     }
    262 
    263     public SeekBar getSeekBar() {
    264         return mSeekBar;
    265     }
    266 
    267     public void changeVolumeBy(int amount) {
    268         mSeekBar.incrementProgressBy(amount);
    269         postSetVolume(mSeekBar.getProgress());
    270         postStartSample();
    271         mVolumeBeforeMute = -1;
    272     }
    273 
    274     public void muteVolume() {
    275         if (mVolumeBeforeMute != -1) {
    276             mSeekBar.setProgress(mVolumeBeforeMute);
    277             postSetVolume(mVolumeBeforeMute);
    278             postStartSample();
    279             mVolumeBeforeMute = -1;
    280         } else {
    281             mVolumeBeforeMute = mSeekBar.getProgress();
    282             mSeekBar.setProgress(0);
    283             postStopSample();
    284             postSetVolume(0);
    285         }
    286     }
    287 
    288     public void onSaveInstanceState(VolumeStore volumeStore) {
    289         if (mLastProgress >= 0) {
    290             volumeStore.volume = mLastProgress;
    291             volumeStore.originalVolume = mOriginalStreamVolume;
    292         }
    293     }
    294 
    295     public void onRestoreInstanceState(VolumeStore volumeStore) {
    296         if (volumeStore.volume != -1) {
    297             mOriginalStreamVolume = volumeStore.originalVolume;
    298             mLastProgress = volumeStore.volume;
    299             postSetVolume(mLastProgress);
    300         }
    301     }
    302 
    303     private final class H extends Handler {
    304         private static final int UPDATE_SLIDER = 1;
    305 
    306         @Override
    307         public void handleMessage(Message msg) {
    308             if (msg.what == UPDATE_SLIDER) {
    309                 if (mSeekBar != null) {
    310                     mLastProgress = msg.arg1;
    311                     final boolean muted = msg.arg2 != 0;
    312                     if (muted != mMuted) {
    313                         mMuted = muted;
    314                         if (mCallback != null) {
    315                             mCallback.onMuted(mMuted);
    316                         }
    317                     }
    318                     updateSeekBar();
    319                 }
    320             }
    321         }
    322 
    323         public void postUpdateSlider(int volume, boolean mute) {
    324             obtainMessage(UPDATE_SLIDER, volume, mute ? 1 : 0).sendToTarget();
    325         }
    326     }
    327 
    328     private void updateSlider() {
    329         if (mSeekBar != null && mAudioManager != null) {
    330             final int volume = mAudioManager.getStreamVolume(mStreamType);
    331             final boolean mute = mAudioManager.isStreamMute(mStreamType);
    332             mUiHandler.postUpdateSlider(volume, mute);
    333         }
    334     }
    335 
    336     private final class Observer extends ContentObserver {
    337         public Observer(Handler handler) {
    338             super(handler);
    339         }
    340 
    341         @Override
    342         public void onChange(boolean selfChange) {
    343             super.onChange(selfChange);
    344             updateSlider();
    345         }
    346     }
    347 
    348     private final class Receiver extends BroadcastReceiver {
    349         private boolean mListening;
    350 
    351         public void setListening(boolean listening) {
    352             if (mListening == listening) return;
    353             mListening = listening;
    354             if (listening) {
    355                 final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
    356                 filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
    357                 mContext.registerReceiver(this, filter);
    358             } else {
    359                 mContext.unregisterReceiver(this);
    360             }
    361         }
    362 
    363         @Override
    364         public void onReceive(Context context, Intent intent) {
    365             final String action = intent.getAction();
    366             if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
    367                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
    368                 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
    369                 final boolean streamMatch = mNotificationOrRing ? isNotificationOrRing(streamType)
    370                         : (streamType == mStreamType);
    371                 if (mSeekBar != null && streamMatch && streamValue != -1) {
    372                     final boolean muted = mAudioManager.isStreamMute(mStreamType);
    373                     mUiHandler.postUpdateSlider(streamValue, muted);
    374                 }
    375             } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
    376                 if (mNotificationOrRing) {
    377                     mRingerMode = mAudioManager.getRingerModeInternal();
    378                 }
    379                 if (mAffectedByRingerMode) {
    380                     updateSlider();
    381                 }
    382             }
    383         }
    384     }
    385 }
    386