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