This series of articles Master link: Thematic sub-directory Android Framework Class Audio Subsystem
Summary and description of key points in this chapter:
This chapter focuses on ➕ The process of adjusting the volume in the Setting interface in the process analysis in the upper left volume adjustment process of the mind map above is sufficient. It mainly explains how the volume value of the slider is transferred layer by layer.
1 Find the program entry from notification_settings.xml
In the Setting application, define the volume slider via the file (packages / apps / Settings / res / xml / notification_settings.xml):
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
android:title="@string/notification_settings"
android:key="notification_settings"
settings:keywords="@string/keywords_sounds_and_notifications">
<PreferenceCategory
android:key="sound"
android:title="@string/sound_settings" >
<!-- 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" />
...
</PreferenceCategory>
</PreferenceScreen>
The file defines multiple VolumeSeekBarPreference. Each VolumeSeekBarPreference here is bound to a SeekBar. In the binding function onBindView of VolumeSeekBarPreference, the corresponding SeekBar SeekBarChangeListener (a SeekBarVolumizer object) is set. The code is as follows:
@Override
protected void onBindView(View view) {
super.onBindView(view);
if (mStream == 0) {
Log.w(TAG, "No stream found, not binding volumizer");
return;
}
mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
mIconView = (ImageView) view.findViewById(com.android.internal.R.id.icon);
mSuppressionTextView = (TextView) view.findViewById(R.id.suppression_text);
init();
}
2 From the analysis of the init function to sending a message MSG_SET_DEVICE_VOLUME to AudioService
The init function is finally called here, and the code is implemented as follows:
private void init() {
if (mSeekBar == null) return;
getPreferenceManager().registerOnActivityStopListener(this);
final SeekBarVolumizer.Callback sbvc = new SeekBarVolumizer.Callback() {
@Override
public void onSampleStarting(SeekBarVolumizer sbv) {
if (mCallback != null) {
mCallback.onSampleStarting(sbv);
}
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
if (mCallback != null) {
mCallback.onStreamValueChanged(mStream, progress);
}
}
@Override
public void onMuted(boolean muted) {
if (mMuted == muted) return;
mMuted = muted;
updateIconView();
}
};
final Uri sampleUri = mStream == AudioManager.STREAM_MUSIC ? getMediaVolumeUri() : null;
if (mVolumizer == null) {
//创建SeekBarVolumizer
mVolumizer = new SeekBarVolumizer(getContext(), mStream, sampleUri, sbvc);
}
mVolumizer.start();
//设置mSeekBar
mVolumizer.setSeekBar(mSeekBar);
updateIconView();
mCallback.onStreamValueChanged(mStream, mSeekBar.getProgress());
updateSuppressionText();
}
When SeekBar is slid, onProgressRefresh of SeekBar object is called, the code is as follows:
public class SeekBar extends AbsSeekBar {
//...
@Override
void onProgressRefresh(float scale, boolean fromUser) {
super.onProgressRefresh(scale, fromUser);
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser);
}
}
//...
}
This function will call mOnSeekBarChangeListener.onProgressChanged to set the volume. This mOnSeekBarChangeListener is set in the setSeekBarfan method in SeekBarVolumizer. The code is as follows:
public void setSeekBar(SeekBar seekBar) {
if (mSeekBar != null) {
mSeekBar.setOnSeekBarChangeListener(null);
}
mSeekBar = seekBar;
mSeekBar.setOnSeekBarChangeListener(null);
mSeekBar.setMax(mMaxStreamVolume);
updateSeekBar();
mSeekBar.setOnSeekBarChangeListener(this);
}
The setOnSeekBarChangeListener here will be set to SeekBar's mOnSeekBarChangeListener in SeekBarVolumizer itself, so here is concerned about the onProgressChanged implementation of SeekBarVolumizer, the code is as follows:
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
if (fromTouch) {
postSetVolume(progress);
}
if (mCallback != null) {
mCallback.onProgressChanged(seekBar, progress, fromTouch);
}
}
Continue to analyze postSetVolume here, the code implementation is as follows:
private void postSetVolume(int progress) {
if (mHandler == null) return;
// 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));
}
Continue to analyze the message processing flow here, the handleMessage code corresponding to the handler is implemented as follows:
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_STREAM_VOLUME:
mAudioManager.setStreamVolume(mStreamType, mLastProgress,
AudioManager.FLAG_SHOW_UI_WARNINGS);
break;
//..
default:
Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
}
return true;
}
Here begins to enter the audio subsystem, the setStreamVolume code of mAudioManager is implemented as follows:
public void setStreamVolume(int streamType, int index, int flags) {
IAudioService service = getService();
try {
if (mUseMasterVolume) {
service.setMasterVolume(index, flags, mContext.getOpPackageName());
} else {
service.setStreamVolume(streamType, index, flags, mContext.getOpPackageName());
}
} catch (RemoteException e) {
Log.e(TAG, "Dead object in setStreamVolume", e);
}
}
The setStreamVolume in AUdioService is called here, and the code is implemented as follows:
public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
setStreamVolume(streamType, index, flags, callingPackage, Binder.getCallingUid());
}
Continue to analyze setStreamVolume, the code implementation is as follows:
private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
int uid) {
//...
synchronized (mSafeMediaVolumeState) {
// reset any pending volume command
//...
if (!checkSafeMediaVolume(streamTypeAlias, index, device)) {
mVolumeController.postDisplaySafeVolumeWarning(flags);
mPendingVolumeCommand = new StreamVolumeCommand(
streamType, index, flags, device);
} else {
//checkSafeMediaVolume为真时
onSetStreamVolume(streamType, index, flags, device);
index = mStreamStates[streamType].getIndex(device);
}
}
sendVolumeUpdate(streamType, oldIndex, index, flags);
}
Continue to analyze onSetStreamVolume here, the code implementation is as follows:
private void onSetStreamVolume(int streamType, int index, int flags, int device) {
setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, false);
//...
}
Continue to analyze setStreamVolumeInt, the code implementation is as follows:
private void setStreamVolumeInt(int streamType,
int index,
int device,
boolean force) {
VolumeStreamState streamState = mStreamStates[streamType];
if (streamState.setIndex(index, device) || force) {
//最终都会调用AudioService.java的代码发出MSG
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
}
}
Finally, MSG_SET_DEVICE_VOLUME was sent to mAudioHandler for processing. The processing flow was analyzed in Part 3 of the volume key processing flow for volume adjustment of the Android Framework audio subsystem (14) .
Note: Each SeekBar corresponds to a stream. When you slide SeekBar, the volume of the stream is set, and the volume of other streams that belong to the same alias (the same group) is also set.