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