1. 构造函数
关于CarAudioService的启动过程,我们就不在这里描述了,首先看CarAudioService的构造函数,Android10.0与Android9.0相比,这里多了mUidToZoneMap,这是一个Map的集合,主要是与音区相关。
public CarAudioService(Context context) {
mContext = context;
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
mPersistMasterMuteState = mContext.getResources().getBoolean(
R.bool.audioPersistMasterMuteState);
mUidToZoneMap = new HashMap<>();
}
2. init()函数
构造函数结束之后,就是init()函数,init函数里面,首先是通过AudioManager.getDevices获取到输出设备,并存储在deviceInfos 数组中,然后根据type类型是否是TYPE_BUS来对deviceInfos数组进行筛选,把筛选的结果放在busToCarAudioDeviceInfo,拿到busToCarAudioDeviceInfo之后,就调用setupDynamicRouting方法,进行动态路由的选择。
流程图如下:
/**
* Dynamic routing and volume groups are set only if
* {@link #mUseDynamicRouting} is {@code true}. Otherwise, this service runs in legacy mode.
*/
@Override
public void init() {
synchronized (mImplLock) {
if (mUseDynamicRouting) {
// Enumerate all output bus device ports
AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
AudioManager.GET_DEVICES_OUTPUTS);
if (deviceInfos.length == 0) {
Log.e(CarLog.TAG_AUDIO, "No output device available, ignore");
return;
}
SparseArray busToCarAudioDeviceInfo = new SparseArray<>();
for (AudioDeviceInfo info : deviceInfos) {
Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
info.getId(), info.getAddress(), info.getType()));
if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
// See also the audio_policy_configuration.xml,
// the bus number should be no less than zero.
if (carInfo.getBusNumber() >= 0) {
busToCarAudioDeviceInfo.put(carInfo.getBusNumber(), carInfo);
Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
}
}
}
setupDynamicRouting(busToCarAudioDeviceInfo);
} else {
Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
setupLegacyVolumeChangedListener();
}
// Restore master mute state if applicable
if (mPersistMasterMuteState) {
boolean storedMasterMute = Settings.Global.getInt(mContext.getContentResolver(),
VOLUME_SETTINGS_KEY_MASTER_MUTE, 0) != 0;
setMasterMute(storedMasterMute, 0);
}
}
}
2.1 setupDynamicRouting()函数
setupDynamicRouting做的事情比较多,我们分布进行梳理
1.读取CarAudio的配置
2.创建CarAudioZonesHelper,同步当前音量
3.通过usage创建动态路由
private void setupDynamicRouting(SparseArray busToCarAudioDeviceInfo) {
//创建AudioPolicy
final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
builder.setLooper(Looper.getMainLooper());
//读取Car Audio的配置
mCarAudioConfigurationPath = getAudioConfigurationPath();
if (mCarAudioConfigurationPath != null) {
try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
//创建CarAudioZonesHelper
CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mContext, inputStream,
busToCarAudioDeviceInfo);
mCarAudioZones = zonesHelper.loadAudioZones();
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException("Failed to parse audio zone configuration", e);
}
} else {
// In legacy mode, context -> bus mapping is done by querying IAudioControl HAL.
final IAudioControl audioControl = getAudioControl();
if (audioControl == null) {
throw new RuntimeException(
"Dynamic routing requested but audioControl HAL not available");
}
CarAudioZonesHelperLegacy legacyHelper = new CarAudioZonesHelperLegacy(mContext,
R.xml.car_volume_groups, busToCarAudioDeviceInfo, audioControl);
mCarAudioZones = legacyHelper.loadAudioZones();
}
for (CarAudioZone zone : mCarAudioZones) {
if (!zone.validateVolumeGroups()) {
throw new RuntimeException("Invalid volume groups configuration");
}
// Ensure HAL gets our initial value
zone.synchronizeCurrentGainIndex();
Log.v(CarLog.TAG_AUDIO, "Processed audio zone: " + zone);
}
// Setup dynamic routing rules by usage
final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
dynamicRouting.setupAudioDynamicRouting(builder);
// Attach the {@link AudioPolicyVolumeCallback}
builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
if (sUseCarAudioFocus) {
// Configure our AudioPolicy to handle focus events.
// This gives us the ability to decide which audio focus requests to accept and bypasses
// the framework ducking logic.
mFocusHandler = new CarZonesAudioFocus(mAudioManager,
mContext.getPackageManager(),
mCarAudioZones);
builder.setAudioPolicyFocusListener(mFocusHandler);
builder.setIsAudioFocusPolicy(true);
}
mAudioPolicy = builder.build();
if (sUseCarAudioFocus) {
// Connect the AudioPolicy and the focus listener
mFocusHandler.setOwningPolicy(this, mAudioPolicy);
}
int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
if (r != AudioManager.SUCCESS) {
throw new RuntimeException("registerAudioPolicy failed " + r);
}
}
2.1.1 getAudioConfigurationPath()
首先加载vendor/etc下的car_audio_configuration.xml,如果没有则会加载system/etc下的car_audio_configuration.xml,找到之后则会返回路径,然后后面根据path进行解析,然后会创建CarAudioZonesHelper
// CarAudioService reads configuration from the following paths respectively.
// If the first one is found, all others are ignored.
// If no one is found, it fallbacks to car_volume_groups.xml resource file.
private static final String[] AUDIO_CONFIGURATION_PATHS = new String[] {
"/vendor/etc/car_audio_configuration.xml",
"/system/etc/car_audio_configuration.xml"
};
/**
* Read from {@link #AUDIO_CONFIGURATION_PATHS} respectively.
* @return File path of the first hit in {@link #AUDIO_CONFIGURATION_PATHS}
*/
@Nullable
private String getAudioConfigurationPath() {
for (String path : AUDIO_CONFIGURATION_PATHS) {
File configuration = new File(path);
if (configuration.exists()) {
return path;
}
}
return null;
}
下面我们看一下car_audio_configuration.xml,后面会有专门的章节分析该配置文件
2.1.2 CarAudioZonesHelper()
CarAudioZonesHelper()的构造函数比较简单,创建完毕之后,调用loadAudioZones()函数,这里主要是对传入的path进行解析
解析过程如下:
1.先找name和isPrimary,在CarAudioZone中isPrimary只能有一个,如果isPrimary为true,则id是CarAudioManager.PRIMARY_AUDIO_ZONE,否则id是mNextSecondaryZoneId,因为mNextSecondaryZoneId初始为1,也就是说primary为true的id=0,其他的从1开始累加
2.根据zone标签,我们知道最终会创建多少个CarAudioZone。每个CarAudioZone包含一个VolumeGroups的集合,而集合的size是由
CarAudioZonesHelper(Context context, @NonNull InputStream inputStream,
@NonNull SparseArray busToCarAudioDeviceInfo) {
mContext = context;
mInputStream = inputStream;
mBusToCarAudioDeviceInfo = busToCarAudioDeviceInfo;
mNextSecondaryZoneId = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
mPortIds = new HashSet<>();
}
CarAudioZone[] loadAudioZones() throws IOException, XmlPullParserException {
List carAudioZones = new ArrayList<>();
parseCarAudioZones(carAudioZones, mInputStream);
return carAudioZones.toArray(new CarAudioZone[0]);
}
上面根据配置文件,解析出CarAudioZone,我们看CarAudioZone里面都有什么,通过CarAudioZone的构造函数来看,CarAudioZone中会有一个CarVolumeGroups,这是CarVolumeGroup的集合
private final int mId;
private final String mName;
private final List mVolumeGroups;
private final List mPhysicalDisplayAddresses;
CarAudioZone(int id, String name) {
mId = id;
mName = name;
mVolumeGroups = new ArrayList<>();
mPhysicalDisplayAddresses = new ArrayList<>();
}
下面我们看一下CarVolumeGroup里面都有什么
mZoneId就是前面分析的zoneId
mId是我们每次mVolumeGroups .add的时候传入的从0开始累加的一个数,也就是list的索引
mStoredGainIndex 是数据库存储的值
/**
* Constructs a {@link CarVolumeGroup} instance
* @param context {@link Context} instance
* @param zoneId Audio zone this volume group belongs to
* @param id ID of this volume group
*/
CarVolumeGroup(Context context, int zoneId, int id) {
mContentResolver = context.getContentResolver();
mZoneId = zoneId;
mId = id;
mStoredGainIndex = Settings.Global.getInt(mContentResolver,
CarAudioService.getVolumeSettingsKeyForGroup(mZoneId, mId), -1);
}
busNumber和context在CarAudioZonesHelper有个map关系,有了
busNumber还可以通过mBusToCarAudioDeviceInfo.get(busNumber)找到CarAudioDeviceInfo。这样我们就可以做CarVolumeGroup的bind了
static {
CONTEXT_NAME_MAP = new HashMap<>();
CONTEXT_NAME_MAP.put("music", ContextNumber.MUSIC);
CONTEXT_NAME_MAP.put("navigation", ContextNumber.NAVIGATION);
CONTEXT_NAME_MAP.put("voice_command", ContextNumber.VOICE_COMMAND);
CONTEXT_NAME_MAP.put("call_ring", ContextNumber.CALL_RING);
CONTEXT_NAME_MAP.put("call", ContextNumber.CALL);
CONTEXT_NAME_MAP.put("alarm", ContextNumber.ALARM);
CONTEXT_NAME_MAP.put("notification", ContextNumber.NOTIFICATION);
CONTEXT_NAME_MAP.put("system_sound", ContextNumber.SYSTEM_SOUND);
}
private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)
throws XmlPullParserException, IOException {
final CarVolumeGroup group = new CarVolumeGroup(mContext, zoneId, groupId);
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
parseVolumeGroupContexts(parser, group,
CarAudioDeviceInfo.parseDeviceAddress(address));
} else {
skip(parser);
}
}
return group;
}
private void parseVolumeGroupContexts(
XmlPullParser parser, CarVolumeGroup group, int busNumber)
throws XmlPullParserException, IOException {
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (TAG_CONTEXT.equals(parser.getName())) {
group.bind(
parseContextNumber(parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME)),
busNumber, mBusToCarAudioDeviceInfo.get(busNumber));
}
// Always skip to upper level since we're at the lowest.
skip(parser);
}
}
在bind函数中,因为group下的音量都是一个步长,所以步长只赋值一次。mContextToBus把contextNumber和busNumber存入map,mBusToCarAudioDeviceInfo则是把busNumber和info保存下来,这样就可以找到context与device之间的对应关系,一个device下不管有多少个context,volume对应的都是一次赋值。
/**
* Binds the context number to physical bus number and audio device port information.
* Because this may change the groups min/max values, thus invalidating an index computed from
* a gain before this call, all calls to this function must happen at startup before any
* set/getGainIndex calls.
*
* @param contextNumber Context number as defined in audio control HAL
* @param busNumber Physical bus number for the audio device port
* @param info {@link CarAudioDeviceInfo} instance relates to the physical bus
*/
void bind(int contextNumber, int busNumber, CarAudioDeviceInfo info) {
if (mBusToCarAudioDeviceInfo.size() == 0) {
mStepSize = info.getAudioGain().stepValue();
} else {
Preconditions.checkArgument(
info.getAudioGain().stepValue() == mStepSize,
"Gain controls within one group must have same step value");
}
mContextToBus.put(contextNumber, busNumber);
mBusToCarAudioDeviceInfo.put(busNumber, info);
if (info.getDefaultGain() > mDefaultGain) {
// We're arbitrarily selecting the highest bus default gain as the group's default.
mDefaultGain = info.getDefaultGain();
}
if (info.getMaxGain() > mMaxGain) {
mMaxGain = info.getMaxGain();
}
if (info.getMinGain() < mMinGain) {
mMinGain = info.getMinGain();
}
if (mStoredGainIndex < getMinGainIndex() || mStoredGainIndex > getMaxGainIndex()) {
// We expected to load a value from last boot, but if we didn't (perhaps this is the
// first boot ever?), then use the highest "default" we've seen to initialize
// ourselves.
mCurrentGainIndex = getIndexForGain(mDefaultGain);
} else {
// Just use the gain index we stored last time the gain was set (presumably during our
// last boot cycle).
mCurrentGainIndex = mStoredGainIndex;
}
}
本篇文章先分析到这里,接下来我们会分析动态路由部分,也就是setupAudioDynamicRouting()
// Setup dynamic routing rules by usage
final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);
dynamicRouting.setupAudioDynamicRouting(builder);