前面文章已经介绍了自动搜索的功能,但是比较依赖底层的接口,对于不依赖等层接口的修改,如何实现自动搜索功能呢?这里我们来看一下能不能通过现有的功能实现一个自动搜索获取有效电台列表的功能。大致思路如下:
1)通过 getDynamicProgramList() 方法获取动态列表。
2)按照动态列表的内容,循环调用 scan() 方法执行向上调台,直到列表中的内容搜索完成。
3)根据 RadioManager.ProgramInfo.getSignalStrength() 判断信号质量,生成一个有效电台列表。
4)回调监听扫描状态及有效电台列表的变化。
可以看到,这里的关键就是 getDynamicProgramList() 方法获取电台的动态列表,所以这里我们就来先分析一下电台动态列表的获取流程。虽然前面我们介绍过 Android 9.0 获取流程,这里我们还是来看一下在 Android 11 中的获取流程。
一、接口介绍
通过前面的文章分析,这个动态列表我认为就是生产厂商配置的一个该区域所有电台的列表,我们调台的过程就是一个个的查看对应列表中的电台频率是否可以正常播放。
1、RadioTuner
源码位置:/frameworks/base/core/java/android/hardware/radio/RadioTuner.java
getDynamicProgramList
/*** 获取发现的无线电台的动态列表** 列表对象异步更新,使用addListCallback获取更新寄存器* 当返回的对象不再使用时,必须关闭它** @param 筛选列表,或为null以获取完整列表* @return 动态程序列表对象,在使用后关闭它,如果调谐器不支持程序列表,则关闭它*/
public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {`return null;
}
对应的接口同样是在 RadioTuner 中,这里的参数是 ProgramList.Filter 类型,我们具体来看一下。
2、ProgramList.Filter
源码位置:/frameworks/base/core/java/android/hardware/radio/ProgramList.java
/*** 程序列表过滤器的构造函数** @param identifierTypes 筛选器的标识符类型列表* @param identifiers 筛选器的标识符列表* @param includeCategories 是否包含非可调谐条目(即类别)* @param excludeModifications 是否排除修改过的项目*/
public Filter(@NonNull Set<Integer> identifierTypes, @NonNull Set<ProgramSelector.Identifier> identifiers, boolean includeCategories, boolean excludeModifications) {mIdentifierTypes = Objects.requireNonNull(identifierTypes);mIdentifiers = Objects.requireNonNull(identifiers);mIncludeCategories = includeCategories;mExcludeModifications = excludeModifications;mVendorFilter = null;
}
这里前两个参数通过上面的代码可能比较好理解,我们主要看一下后两个参数:
- identifierTypes: 一个整数集合,表示要筛选的标识符类型。每个整数代表一种标识符类型。
- identifiers:一个 ProgramSelector.Identifier 集合,表示具体的标识符实例。
- includeCategories:设置 true,表示在筛选节目列表时包含非可调谐条目(例如 DAB ),这意味着不仅筛选具体的频率,还会包含类别信息,例如 DAB 集中的各个频道。
- excludeModifications:设置 true,表示在筛选节目列表时排除已修改的项目。这意味着如果某些项目已经被修改过(例如已删除或更新),这些项目不会被包含在筛选结果中。
示例代码
Set<Integer> identifierTypes = new HashSet<>();
identifierTypes.add(1); // 假设 1 表示 FM 频率类型Set<ProgramSelector.Identifier> identifiers = new HashSet<>();
identifiers.add(new ProgramSelector.Identifier(1, 87500)); // 假设 87500 表示默认的 FM 频率Filter filter = new Filter(identifierTypes, identifiers, true, true);
接下来看一下 getDynamicProgramList() 方法的实现,在 TunerAdapter 中。
二、获取动态列表
1、TunerAdapter
源码位置:/frameworks/base/core/java/android/hardware/radio/TunerAdapter.java
getDynamicProgramList
@Override
public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {synchronized (mTuner) {……ProgramList list = new ProgramList();// 设置 list 作为观察者mCallback.setProgramListObserver(list, () -> {try {// 停止节目列表更新mTuner.stopProgramListUpdates();}……});try {// 启动节目列表更新mTuner.startProgramListUpdates(filter);}……return list;}
}
这里其实是与 Android 9.0 中的代码相同,这里我们直接看 startProgramListUpdates() 方法。
2、TunerSession
源码位置:/frameworks/base/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
startProgramListUpdates
@Override
public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException {// 如果提供的filter为null,则创建一个最广泛的过滤器对象if (filter == null) {filter = new ProgramList.Filter(new HashSet<Integer>(),new HashSet<android.hardware.radio.ProgramSelector.Identifier>(), true, false);}synchronized (mLock) {// 检查是否已经关闭checkNotClosedLocked();mProgramInfoCache = new ProgramInfoCache(filter);}// 调用模块更新方法mModule.onTunerSessionProgramListFilterChanged(this);
}
这里最终调用 RadioModule 中的 onTunerSessionProgramListFilterChanged() 方法。这里与 Android 9.0 中还是有些区别的,中间少了 RadioModule 中的调用。
3、RadioModule
源码位置:/frameworks/base/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
onTunerSessionProgramListFilterChanged
void onTunerSessionProgramListFilterChanged(@Nullable TunerSession session) {synchronized (mLock) {onTunerSessionProgramListFilterChangedLocked(session);}
}
onTunerSessionProgramListFilterChangedLocked
private ITunerSession mHalTunerSession;private void onTunerSessionProgramListFilterChangedLocked(@Nullable TunerSession session) {android.hardware.radio.ProgramList.Filter newFilter = buildUnionOfTunerSessionFiltersLocked();if (newFilter == null) {// 没有剩余的AIDL客户端,停止更新if (mUnionOfAidlProgramFilters == null) {return;}mUnionOfAidlProgramFilters = null;try {// 停止 HAL 的节目列表更新mHalTunerSession.stopProgramListUpdates();}……return;}// 过滤器内容不变,直接更新if (newFilter.equals(mUnionOfAidlProgramFilters)) {if (session != null) {// 更新客户端信息session.updateProgramInfoFromHalCache(mProgramInfoCache);}return;}mUnionOfAidlProgramFilters = newFilter;try {// 启动HAL的节目列表更新int halResult = mHalTunerSession.startProgramListUpdates(Convert.programFilterToHal(newFilter));// 处理返回的结果Convert.throwOnError("startProgramListUpdates", halResult);} catch (RemoteException ex) {Slog.e(TAG, "mHalTunerSession.startProgramListUpdates() failed: ", ex);}
}
这里调用 Hal 层的节目列表更新函数,与前面文章中 Android 9.0 的调用流程就相同了,这里就不做过多介绍了。
三、实例代码
1、接口封装
private RadioTuner mRadioTuner;/*** 开始自动搜索*/
public void startRadioAutosSeek() {synchronized (mLock) {Log.i(TAG, "CONTROL_ACTION_SEEKUP mLock");// 设置静音mRadioTuner.setMute(true);// 开始自动搜索startAutoStore()}
}
这里在搜索过程中,首先对电台进行静音操作,然后调用一个封装的电台自动搜索方法。
startAutoStore
// 默认FM和AM的开始频率
private final static int DEFAULT_FM_REQ = 87500;
private static final int DEFAULT_AM_REQ = 531;private ProgramList mProgramList = null;private void startAutoStore() {// 确定开始搜索的频率int value = (mCurrentBand == BAND_TYPE_FM) ? DEFAULT_FM_REQ : DEFAULT_AM_REQ;Set<Integer> idTypes = new HashSet<Integer>();idTypes.add(1);// 创建ProgramSelector.Identifier对象,指定搜索的初始频率ProgramSelector.Identifier id = new ProgramSelector.Identifier(1, value);Set<ProgramSelector.Identifier> ids = new HashSet<ProgramSelector.Identifier>();ids.add(id);// 筛选符合要求的节目列表ProgramList.Filter filter = new ProgramList.Filter(idTypes, ids, true, true);// 获取动态节目列表mProgramList = mRadioTuner.getDynamicProgramList(filter);if (mProgramList == null) {// 不支持。默认电台列表为空return;}// 设置动态列表变化监听mProgramList.registerListCallback(mListListener);// 设置动态列表完成监听mProgramList.addOnCompleteListener(mCompleteListener);// 设置动态列表取消监听mProgramList.setOnCloseListener(mCloseListener);
}
这里通过 getDynamicProgramList() 方法获取电台列表的过程,对于更多操作都是建立在成功获取到电台列表后进行,所以下一篇我们处理电台动态列表的相关回调。