設(shè)置中音量相關(guān)的Fragment調(diào)整到了NotificationSettings.java中。
我們可以在R.xml.notification_settings中看到如下
xref: /packages/apps/Settings/res/xml/notification_settings.xml
<!-- Media volume -->
<com.android.settings.notification.VolumeSeekBarPreference
android:key="media_volume"
android:icon="@*android:drawable/ic_audio_vol"
android:title="@string/media_volume_option_title" />
<!-- Alarm volume -->
<com.android.settings.notification.VolumeSeekBarPreference
android:key="alarm_volume"
android:icon="@*android:drawable/ic_audio_alarm"
android:title="@string/alarm_volume_option_title" />
<!-- Ring volume -->
<com.android.settings.notification.VolumeSeekBarPreference
android:key="ring_volume"
android:icon="@*android:drawable/ic_audio_ring_notif"
android:title="@string/ring_volume_option_title" />
<!-- Notification volume -->
<com.android.settings.notification.VolumeSeekBarPreference
android:key="notification_volume"
android:icon="@*android:drawable/ic_audio_ring_notif"
android:title="@string/notification_volume_option_title" />
這分別代表多媒體、鬧鐘蝗锥、來電鈴聲和系統(tǒng)通知音量。所以在UI層面上用戶是通過調(diào)節(jié)VolumeSeekBarPreference這個seekbar來最終對系統(tǒng)音量進行調(diào)整。
回頭看NotificationSettings.java中初始化流程如下
xref: /packages/apps/Settings/src/com/android/settings/notification/NotificationSettings.java
//sound是包含上面4個SeekBar的PreferenceCategory
final PreferenceCategory sound = (PreferenceCategory) findPreference(KEY_SOUND);
initVolumePreference(KEY_MEDIA_VOLUME, AudioManager.STREAM_MUSIC);
initVolumePreference(KEY_ALARM_VOLUME, AudioManager.STREAM_ALARM);
//判斷是否支持通話盗蟆,支持移除NOTIFICATION_VOLUME否則移除RING_VOLUME
if (mVoiceCapable) {
mRingOrNotificationPreference =
initVolumePreference(KEY_RING_VOLUME, AudioManager.STREAM_RING);
sound.removePreference(sound.findPreference(KEY_NOTIFICATION_VOLUME));
} else {
mRingOrNotificationPreference =
initVolumePreference(KEY_NOTIFICATION_VOLUME, AudioManager.STREAM_NOTIFICATION);
sound.removePreference(sound.findPreference(KEY_RING_VOLUME));
}
initRingtones(sound);
private final VolumePreferenceCallback mVolumeCallback = new VolumePreferenceCallback();
private VolumeSeekBarPreference initVolumePreference(String key, int stream) {
final VolumeSeekBarPreference volumePref = (VolumeSeekBarPreference) findPreference(key);
設(shè)置callback
volumePref.setCallback(mVolumeCallback);
//設(shè)置AudioManager的stream,可以在AudioSystem中看到具體int值代表的意義
volumePref.setStream(stream);
return volumePref;
}
mVolumeCallback如下舒裤,當(dāng)你拖動seekbar對音量進行調(diào)節(jié)時這個callback會回饋響應(yīng)喳资,在界面上進行相應(yīng)的調(diào)整。
private final class VolumePreferenceCallback implements VolumeSeekBarPreference.Callback {
//SeekBarVolumizer是主要負責(zé)對系統(tǒng)音量進行調(diào)整的類腾供,會在后面描述
private SeekBarVolumizer mCurrent;
@Override
public void onSampleStarting(SeekBarVolumizer sbv) {
if (mCurrent != null && mCurrent != sbv) {
mCurrent.stopSample();
}
mCurrent = sbv;
if (mCurrent != null) {
mHandler.removeMessages(H.STOP_SAMPLE);
mHandler.sendEmptyMessageDelayed(H.STOP_SAMPLE, SAMPLE_CUTOFF);
}
}
@Override
public void onStreamValueChanged(int stream, int progress) {
if (stream == AudioManager.STREAM_RING) {
mHandler.removeMessages(H.UPDATE_RINGER_ICON);
mHandler.obtainMessage(H.UPDATE_RINGER_ICON, progress, 0).sendToTarget();
}
}
public void stopSample() {
if (mCurrent != null) {
mCurrent.stopSample();
}
}
};
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_PHONE_RINGTONE:
mPhoneRingtonePreference.setSummary((CharSequence) msg.obj);
break;
case UPDATE_NOTIFICATION_RINGTONE:
mNotificationRingtonePreference.setSummary((CharSequence) msg.obj);
break;
case STOP_SAMPLE:
mVolumeCallback.stopSample();
break;
case UPDATE_RINGER_ICON:
updateRingOrNotificationIcon(msg.arg1);
break;
}
}
下面來看VolumeSeekBarPreference.java中可以看到如下代碼
xref: /packages/apps/Settings/src/com/android/settings/notification/VolumeSeekBarPreference.java
private SeekBarVolumizer mVolumizer;
final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
if (mVolumizer == null) {
mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc) {
// we need to piggyback on SBV's SeekBar listener to update our icon
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromTouch) {
super.onProgressChanged(seekBar, progress, fromTouch);
//這里的mCallback就是VolumePreferenceCallback對象
mCallback.onStreamValueChanged(mStream, progress);
}
};
}
mVolumizer.setSeekBar(mSeekBar);
mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
mCallback.onStreamValueChanged(mStream, mSeekBar.getProgress());
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromTouch) {
super.onProgressChanged(seekBar, progress, fromTouch);
mCallback.onStreamValueChanged(mStream, progress);
}
這里我們進行下梳理在NotificationSettings.java內(nèi)initVolumePreference函數(shù)的volumePref.setCallback(mVolumeCallback);把VolumePreferenceCallback這個callbak設(shè)置到VolumeSeekBarPreference.java內(nèi)的mCallback中
使得SeekBarVolumizer內(nèi)的onProgressChanged進行回調(diào)時NotificationSettings.java的相應(yīng)回調(diào)函數(shù)都響應(yīng)仆邓。
現(xiàn)在看下/frameworks/base/core/java/android/preference/的SeekBarVolumizer.java
private final Context mContext;
private final Handler mHandler;
private final H mUiHandler = new H();
private final Callback mCallback;
private final Uri mDefaultUri;
private final AudioManager mAudioManager;
private final int mStreamType;
private final int mMaxStreamVolume;
private final Receiver mReceiver = new Receiver();
private final Observer mVolumeObserver;
private int mOriginalStreamVolume;
private Ringtone mRingtone;
private int mLastProgress = -1;
private SeekBar mSeekBar;
private int mVolumeBeforeMute = -1;
private static final int MSG_SET_STREAM_VOLUME = 0;
private static final int MSG_START_SAMPLE = 1;
private static final int MSG_STOP_SAMPLE = 2;
private static final int MSG_INIT_SAMPLE = 3;
private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
public SeekBarVolumizer(Context context, int streamType, Uri defaultUri,
Callback callback) {
mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mStreamType = streamType;
mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
thread.start();
mHandler = new Handler(thread.getLooper(), this);
mCallback = callback;
mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
mVolumeObserver = new Observer(mHandler);
mContext.getContentResolver().registerContentObserver(
System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
false, mVolumeObserver);
mReceiver.setListening(true);
//mDefaultUri為調(diào)整seekbar時聽到的調(diào)試音
if (defaultUri == null) {
if (mStreamType == AudioManager.STREAM_RING) {
defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
} else if (mStreamType == AudioManager.STREAM_NOTIFICATION) {
defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI;
} else {
defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
}
}
mDefaultUri = defaultUri;
mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
}
public void setSeekBar(SeekBar seekBar) {
if (mSeekBar != null) {
mSeekBar.setOnSeekBarChangeListener(null);
}
mSeekBar = seekBar;
mSeekBar.setOnSeekBarChangeListener(null);
mSeekBar.setMax(mMaxStreamVolume);
mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
mSeekBar.setOnSeekBarChangeListener(this);
}
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
//真正調(diào)節(jié)系統(tǒng)音量的地方
case MSG_SET_STREAM_VOLUME:
mAudioManager.setStreamVolume(mStreamType, mLastProgress,
AudioManager.FLAG_SHOW_UI_WARNINGS);
break;
case MSG_START_SAMPLE:
onStartSample();
break;
case MSG_STOP_SAMPLE:
onStopSample();
break;
case MSG_INIT_SAMPLE:
onInitSample();
break;
default:
Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
}
return true;
}
//初始化mRingtone,我們知道系統(tǒng)播放的鈴聲最后是通過mRingtone.play()來實現(xiàn)的
private void onInitSample() {
mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
if (mRingtone != null) {
mRingtone.setStreamType(mStreamType);
}
}
private void postStartSample() {
mHandler.removeMessages(MSG_START_SAMPLE);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
isSamplePlaying() ? CHECK_RINGTONE_PLAYBACK_DELAY_MS : 0);
}
//開始播放調(diào)試音
private void onStartSample() {
if (!isSamplePlaying()) {
if (mCallback != null) {
mCallback.onSampleStarting(this);
}
if (mRingtone != null) {
try {
mRingtone.play();
} catch (Throwable e) {
Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
}
}
}
}
void postStopSample() {
// remove pending delayed start messages
mHandler.removeMessages(MSG_START_SAMPLE);
mHandler.removeMessages(MSG_STOP_SAMPLE);
mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_SAMPLE));
}
//停止播放調(diào)試音
private void onStopSample() {
if (mRingtone != null) {
mRingtone.stop();
}
}
public void stop() {
postStopSample();
mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
mSeekBar.setOnSeekBarChangeListener(null);
mReceiver.setListening(false);
mHandler.getLooper().quitSafely();
}
public void revertVolume() {
mAudioManager.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
}
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromTouch) {
if (!fromTouch) {
return;
}
postSetVolume(progress);
}
void postSetVolume(int progress) {
// Do the volume changing separately to give responsive UI
mLastProgress = progress;
mHandler.removeMessages(MSG_SET_STREAM_VOLUME);
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_STREAM_VOLUME));
}
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onStopTrackingTouch(SeekBar seekBar) {
postStartSample();
}
//判斷是否正在播放鈴聲
public boolean isSamplePlaying() {
return mRingtone != null && mRingtone.isPlaying();
}
public void startSample() {
postStartSample();
}
public void stopSample() {
postStopSample();
}
public SeekBar getSeekBar() {
return mSeekBar;
}
public void changeVolumeBy(int amount) {
mSeekBar.incrementProgressBy(amount);
postSetVolume(mSeekBar.getProgress());
postStartSample();
mVolumeBeforeMute = -1;
}
public void muteVolume() {
if (mVolumeBeforeMute != -1) {
mSeekBar.setProgress(mVolumeBeforeMute);
postSetVolume(mVolumeBeforeMute);
postStartSample();
mVolumeBeforeMute = -1;
} else {
mVolumeBeforeMute = mSeekBar.getProgress();
mSeekBar.setProgress(0);
postStopSample();
postSetVolume(0);
}
}
public void onSaveInstanceState(VolumeStore volumeStore) {
if (mLastProgress >= 0) {
volumeStore.volume = mLastProgress;
volumeStore.originalVolume = mOriginalStreamVolume;
}
}
public void onRestoreInstanceState(VolumeStore volumeStore) {
if (volumeStore.volume != -1) {
mOriginalStreamVolume = volumeStore.originalVolume;
mLastProgress = volumeStore.volume;
postSetVolume(mLastProgress);
}
}
private final class H extends Handler {
private static final int UPDATE_SLIDER = 1;
@Override
public void handleMessage(Message msg) {
if (msg.what == UPDATE_SLIDER) {
if (mSeekBar != null) {
mSeekBar.setProgress(msg.arg1);
mLastProgress = mSeekBar.getProgress();
}
}
}
public void postUpdateSlider(int volume) {
obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget();
}
}
private final class Observer extends ContentObserver {
public Observer(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
if (mSeekBar != null && mAudioManager != null) {
final int volume = mAudioManager.getStreamVolume(mStreamType);
mUiHandler.postUpdateSlider(volume);
}
}
}
這里忽視其他所有操作關(guān)注一下主要流程伴鳖,我們知道這個類implements了OnSeekBarChangeListener节值,這個listener包含了當(dāng)值發(fā)生變化響應(yīng)的onProgressChanged和開始完成操作的onStartTrackingTouch和onStopTrackingTouch 所以當(dāng)開始拖動seekbar時onStartTrackingTouch響應(yīng)無操作,然后onProgressChanged發(fā)生變化postSetVolume(progress);從而調(diào)用最關(guān)鍵函數(shù)mAudioManager.setStreamVolume(mStreamType, mLastProgress, AudioManager.FLAG_SHOW_UI_WARNINGS);實現(xiàn)音量調(diào)整榜聂,然后調(diào)用onStopTrackingTouch搞疗,通過handler調(diào)用onStartSample函數(shù)來播放調(diào)試音,在這過程中通過和setting模塊綁定的 callback來對setting模塊進行響應(yīng)须肆,這就時和上層setting模塊互動的一個主要流程匿乃。