Android Radio2.0——电台动态列表(六)

ops/2024/10/18 3:30:38/

        前面文章已经介绍了自动搜索的功能,但是比较依赖底层的接口,对于不依赖等层接口的修改,如何实现自动搜索功能呢?这里我们来看一下能不能通过现有的功能实现一个自动搜索获取有效电台列表的功能。大致思路如下:

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() 方法获取电台列表的过程,对于更多操作都是建立在成功获取到电台列表后进行,所以下一篇我们处理电台动态列表的相关回调。


http://www.ppmy.cn/ops/109117.html

相关文章

Spring Boot 部署方案!打包 + Shell 脚本详解

本篇和大家分享的是springboot打包并结合shell脚本命令部署&#xff0c;重点在分享一个shell程序启动工具&#xff0c;希望能便利工作&#xff1b; profiles指定不同环境的配置 maven-assembly-plugin打发布压缩包 分享shenniu_publish.sh程序启动工具 linux上使用shenniu_p…

ArcGIS小技巧:图斑变化分析

在做规划的过程中&#xff0c;经常会有这么个需求&#xff0c;用地方案确定后&#xff0c;需求找出规划用地和三调现状用地之间具体有哪些变化。 一方面可以用作具体规划内容的分析&#xff0c;另一方面也可以避免因为误操作而导致的错误图斑的出现。 以下图为例&#xff0c;…

sliding window 滑动窗口——从LeetCode题海中总结常见套路

滑动窗口开山鼻祖:LeetCode3.无重复字符的最长子串 以前只会暴力瞎几把模拟,看一下心酸又励志的优化过程: 还有通过所有测试用例的超时: class Solution { public:int lengthOfLongestSubstring(string s) {if (s.empty())return 0;// if (s.size() == 1)// return 1;/…

oracle19.3单机升级到Oracle19.22

1.补丁包、opatch准备 -rw-r--r-- 1 oracle oinstall 1817908992 9月 10 14:25 p35943157_190000_Linux-x86-64.zip -rw-r--r-- 1 oracle oinstall 133535622 9月 10 14:22 p6880880_190000_Linux-x86-64.zip2.解压补丁包和opatch包 先将原有opatch备份 [oraclecyptdg ~]$…

011. Oracle-约束

我 的 个 人 主 页&#xff1a;&#x1f449;&#x1f449; 失心疯的个人主页 &#x1f448;&#x1f448; 入 门 教 程 推 荐 &#xff1a;&#x1f449;&#x1f449; Python零基础入门教程合集 &#x1f448;&#x1f448; 虚 拟 环 境 搭 建 &#xff1a;&#x1f449;&…

使用VSCode 安装SAP Fiori 开发所需插件

文章目录 1.0 SAP Fiori Tools - Extension Pack2.0 SAPUI5 Extension 1.0 SAP Fiori Tools - Extension Pack 此插件集成了SAP Fiori 的多个插件&#xff0c;安装这个将包含其他Fiori安装插件 SAP Fiori工具-扩展包 SAP Fiori Tools简化了SAP Fiori元素应用程序的开发。包括…

Visual Studio 在 .NET MAUI 安装期间无法安装 OpenJDK v8 - 访问被拒绝

优质博文&#xff1a;IT-BLOG-CN 问题 我一直在 Windows 计算机上设置 Visual Studio 以进行 .NET MAUI 开发&#xff0c;但在设置过程中一直遇到问题。具体问题涉及 OpenJDK v8 无法安装。这是我看到的情况&#xff1a; Couldnt install OpenJDKv8我尝试过几种方法来解决这…

在职研生活学习--20240906

文章目录 报到的一天&#xff0c;是“摩羯”将以超强台风级登陆广东的一天。 0913&#xff0c;背着大包出门&#xff0c;风儿甚是喧嚣&#xff0c;雨伞差点弯了&#xff0c;赶上0947的三灶东高铁前往广州中转。 1200&#xff0c;到广州南站&#xff0c;干个饭先。 1300广…