Android动态设置系统音量最大值

产品需求

  • 通过设定最大音量限制大屏声音输出,设置完后需要立即生效且需要记忆;
  • 以10%为递增状态,设置最大音量后,无论最大音量调节至多少百分比,音量条始终显示为100%(比如最大音量设置为80%,即侧边音量条依然可以显示充满,但是音量只有原来的80%)。

从技术角度上理解为:Framework层需要提供一个动态设置系统音量最大值的开放接口供应用层调用,应用apk通过设置最大音量百分比后系统声音效果需要立即生效,同时原生音量条的最大值范围也要变化。

需要涉及3个地方的修改:

  • 添加Framework层接口方法
  • 应用层apk调用新增的接口
  • SystemUI音量条监听最大值变化

Framework层接口

1、以Android 13为例,如果修改系统默认的STREAM_MUSIC音量值,通常是修改AudioService.java下MAX_STREAM_VOLUME数组对应的值15。但我们需要动态修改这个值且不需要重启服务立即生效。

/** Maximum volume index values for audio streams */
protected static int[] MAX_STREAM_VOLUME = new int[] {
    5,  // STREAM_VOICE_CALL
    7,  // STREAM_SYSTEM
    7,  // STREAM_RING
    15, // STREAM_MUSIC
    7,  // STREAM_ALARM
    7,  // STREAM_NOTIFICATION
	......
};

2、代码实现
/frameworks/base/media/java/android/media/AudioManager.java
/frameworks/base/media/java/android/media/IAudioService.aidl
/frameworks/base/services/core/java/com/android/server/audio/AudioService.java

IAudioService.aidl主要是声明一个新的系统服务方法,然后通过AudioManager.java来调用服务的具体实现

public void setStreamMaxVolume(int streamType, int maxVolume) {
        final IAudioService service = getService();
        try {
            service.setStreamMaxVolume(streamType, maxVolume);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

AudioService.java具体实现setStreamMaxVolume方法

//动态变更STREAM_MUSIC最大音量值接口
public void setStreamMaxVolume(int streamType, int maxVolume) {
       ensureValidStreamType(streamType);
	   if (streamType == AudioSystem.STREAM_MUSIC) {
			if (maxVolume <= 15 && maxVolume >= 0) {
				int preMaxVolume = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
				//更新MAX_STREAM_VOLUME数组值同时更新mIndexMax
				MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = maxVolume;
				mStreamStates[streamType].updateMaxIndex(maxVolume);
				//发送一个MAX_VOLUME_CHANGED_ACTION广播用于通知最大音量值变化,该系统广播需要声明在frameworks/base/core/res/AndroidManifest.xml里
				Intent intent = new Intent("com.ist.media.MAX_VOLUME_CHANGED_ACTION");
				intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
				intent.putExtra("preMaxVolume", preMaxVolume);
				intent.putExtra("maxVolume", maxVolume);
	            sendBroadcastToAll(intent);
			}
	  }
}

//关键:该方法加在内部class VolumeStreamState中,VolumeStreamState保存了运行时的音量信息,这里有个流音量index的换算关系,更新MAX_STREAM_VOLUME数组的同时也需要更新mIndexMax       
//mIndexMax = MAX_STREAM_VOLUME[streamType] * 10;
public void updateMaxIndex(int index) {
    mIndexMax = index * 10;
}

设置后需要有记忆性,即重启后AudioService服务构造初始化时设置最大音量值

	int maxMusicVolume = SystemProperties.getInt("ro.config.media_vol_steps", -1);
   	if (maxMusicVolume != -1) {
       	MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = maxMusicVolume;
   	} else {
	//add by XXX 初始化STREAM_MUSIC最大音量值
	String ptsStr = mSettings.getGlobalString(mContentResolver, "persist.sys.hht.max_volume_percent");//上层通过该Settings值来保存音量设置的百分比
	if (TextUtils.isEmpty(ptsStr)) {
		ptsStr = "10";
	}
	int maxPercent = Integer.parseInt(ptsStr);
	maxMusicVolume = Math.round((float) maxPercent / 10 * 15);
	MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC] = maxMusicVolume;
	Log.i(TAG, "init maxMusicVolume = " + maxMusicVolume);
	//end by XXX
}

应用层apk调用

 /**
  * 设置最大音量百分比
  * @param percent 0~10,10%递进
  */
 public void setMaxVolumePercent(int percent){
     Log.i(TAG, "setMaxVolumePercent() percent = "+ percent);
     //保存最大音量百分比设置
     Settings.Global.putString(context.getContentResolver(), "persist.sys.hht.max_volume_percent", String.valueOf(percent));
     //最大音量值设置后需要立即生效
     if (mAudioManager != null) {
         int currentMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);//获取系统最大音量值
         int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);//当前音量值
         int maxMusicVolume = Math.round((float) percent / 10 * 15);
         //调用新增的系统接口方法
         mAudioManager.setStreamMaxVolume(AudioManager.STREAM_MUSIC, maxMusicVolume);
         Log.i(TAG, "setStreamMaxVolume = " + maxMusicVolume);
         if (currentVolume > maxMusicVolume) { //若当前音量值大于最大音量值则取较小值
             mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, maxMusicVolume, 0);
         }
     }
 }

SystemUI音量条监听最大值变化

/frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java

final String action = intent.getAction();
boolean changed = false;
if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
    final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
    final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
    final int oldLevel = intent
            .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1);
    if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream
            + " level=" + level + " oldLevel=" + oldLevel);
    changed = updateStreamLevelW(stream, level);
}
......
//add by XXX 侧边音量条最大值即时更新
else if (action.equals("com.ist.media.MAX_VOLUME_CHANGED_ACTION")) {
    Log.d(TAG, "onReceive com.ist.media.MAX_VOLUME_CHANGED_ACTION");
	final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
	final int preMaxVolume = intent.getIntExtra("preMaxVolume", -1);
	final int maxVolume = intent.getIntExtra("maxVolume", -1);
	Log.i(TAG, "onReceive preMaxVolume = " + preMaxVolume + ", maxVolume = " + maxVolume);
	if (preMaxVolume != maxVolume) {
		changed = true;
		getState();
	}
}
//end by XXX
if (changed) {
 	mCallbacks.onStateChanged(mState);
}

dumpsys audio调试

通过adb shell dumpsys audio命令查看音频系统的状态信息。
Stream volumes (device: index):查看各类型流的音量值。其中Muted为是否静音,Min为最小值,Max为最大值,Current为各输出设备的当前音量,Devices为当前输出设备。
调试时,我们关注STREAM_MUSIC的Max值有没有按预期的变化即可。

- STREAM_MUSIC:
   Muted: false
   Muted Internally: false
   Min: 0
   Max: 15
   streamVolume:8
   Current: 2 (speaker): 8, 400 (hdmi): 8, 80000 (spdif): 8, 100000 (fm_transmitter): 8, 4000000 (usb_headset): 2, 10000000 (echo_canceller): 8, 40000000 (default): 3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/777791.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mac 安装nvm的教程

在macOS上切换Node.js版本&#xff0c;可以使用nvm&#xff08;Node Version Manager&#xff09;。以下是安装nvm和切换Node.js版本的步骤&#xff1a; 安装nvm 下载方式 终端复制输入&#xff1a; curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.…

Odoo14使用hiPrint实现打印功能

使用hiPrint代替odoo原生的打印功能 可以实现快速自定义修改打印模板&#xff0c;无需每次都调整打印模板 无论是表单分页还是各种需求&#xff0c;都能满足 目录 1 使用命令创建新的模块&#xff0c;无用的demo文件可以删除掉 2 新建“打印模板”&#xff0c;用于保存打印…

MySQL表的增删改查(CRUD)

MySQL表的增删改查&#xff08;CRUD&#xff09; 文章目录 MySQL表的增删改查&#xff08;CRUD&#xff09;1. Create1.1 单行数据 全列插入1.2 多行数据 指定列插入1.3 插入否则更新1.4 替换 2. Retrieve2.1 SELECT 列2.1.1 全列查询2.1.2 指定列查询2.1.3 查询字段为表达式…

嵌入式系统中状态机实现详解

嵌入式开发中系统经常处于某种状态,如何处理呢?接下来分析一下,状态机的实现无非就是 3 个要素:状态、事件、响应。转换成具体的行为就 3 句话。 发生了什么事? 现在系统处在什么状态? 在这样的状态下发生了这样的事,系统要干什么? 用 C 语言实现状态机主要有 3 种方法…

JVM专题之垃圾收集算法

标记清除算法 第一步:标记 (找出内存中需要回收的对象,并且把它们标记出来) 第二步:清除 (清除掉被标记需要回收的对象,释放出对应的内存空间) 缺点: 标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需 要分配较大对象时,无法找到…

spark on k8s两种方式的原理与对比

spark on k8s两种方式的原理与对比 1、spark on k8s 方式 spark-submit可以直接用来向 Kubernetes 集群提交 Spark 应用&#xff0c;提交机制如下&#xff1a; 1、Spark 创建一个在Kubernetes pod中运行的 Spark 驱动程序。 2、驱动程序创建在 Kubernetes Pod 中运行的执行器…

阿里云服务器配置、搭建(针对Spring boot和MySQL项目)

这是一篇极其详细且痛苦的文章&#xff0c;还是在两位后端的大佬手把手教导下、以及我找遍全网所有资料、问了N遍AI、甚至直接申请阿里云工单一对一询问客服一整天、连续清空再上传反复30多次整个项目jar包......总结出来的终极要人命踩坑的问题总结 一、首先购买服务器 其实不…

25.【C语言】循环结构之for 上

1.基本使用 类比while 在while循环中&#xff0c;有三个不可或缺的部分&#xff1a;初始化&#xff0c;判断部分&#xff0c;调整部分 int i 0;//初始化 while (i < 10)//判断部分 {……i;//调整部分 }三个部分太分散&#xff0c;用for循环可集为一体&#xff0c;简洁 …

dotnet ef工具使用

设置工具安装目录 dotnet tool install dotnetsay --tool-path G:\dotnet-tools安装 dotnet tool install --global dotnet-ef更新 dotnet tool update --global dotnet-ef查看版本 dotnet ef --version创建迁移文件 # 只有一个dbcontext dotnet ef migrations add init #…

Android Camera2 集成人脸识别算法

这可能是全网唯一一篇介绍Android Camera2接口集成人脸算法的文章了~ 写在前面&#xff1a; 说起人脸识别&#xff0c;相信大家都不会感到陌生&#xff0c;在我们平时的工作生活中&#xff0c;人脸打卡、刷脸支付等等已经是应用的非常广泛了&#xff0c;人脸识别也给我们的生活…

基于若依的文件上传、下载

基于若依实现文件上传、下载 文章目录 基于若依实现文件上传、下载1、前端实现-文件上传1.1 通用上传分析1.2 修改实现上传接口 2、后端实现-文件上传3、后端实现-文件下载4、前端实现-文件下载 官网其实也写了&#xff0c;但是我是自己改造封装了一下&#xff0c;再次迈向全栈…

kafka系列之消费后不提交offset情况的分析总结

概述 每当我们调用Kafka的poll()方法或者使用KafkaListener(其实底层也是poll()方法)时&#xff0c;它都会返回之前被写入Kafka的记录&#xff0c;即我们组中的消费者还没有读过的记录。 这意味着我们有一种方法可以跟踪该组消费者读取过的记录。 如前所述&#xff0c;Kafka的一…

一种一维时间序列信号变化/事件/异常检测方法(MATLAB)

随着工业物联网、大数据和人工智能的发展&#xff0c;传统工业正在向数字化和智能化升级&#xff0c;从而创造了大量的时间序列数据。通过分析这些数据&#xff0c;可以提供准确可靠的信息服务和决策依据&#xff0c;促进制造业的转型升级。工业物联网在传统工业向“工业 4.0”…

CASS如何输入距离和坐标绘制图形

1、软件版本 这里使用CASS7.0软件进行操作。如下&#xff1a; 2、 操作 输入&#xff1a;500,45【表示距离500米&#xff0c;方向45度】。 点击回车&#xff0c;完成绘制。

V3.76 ViVaCut高级版本!已经开启永久订阅!一款专业视频剪辑软件

在短视频和社交媒体盛行的今天&#xff0c;视频内容的创作和分享已经成为人们日常生活的一部分。为了满足广大视频创作者的需求&#xff0c;一款专业视频剪辑软件应运而生&#xff0c;以其强大的功能和用户友好的界面&#xff0c;为用户提供了全新的视频编辑体验。他提供了众多…

JWT(Json Web Token)在.NET Core中的使用

登录成功时生成JWT字符串目录 JWT是什么&#xff1f; JWT的优点&#xff1a; JWT在.NET Core 中的使用 JWT是什么&#xff1f; JWT把登录信息&#xff08;也称作令牌&#xff09;保存在客户端为了防止客户端的数据造假&#xff0c;保存在客户端的令牌经过了签名处理&#xf…

TikTok马来西亚直播网络怎么配置?

TikTok是一款全球流行的社交媒体应用&#xff0c;在东南亚地区拥有大量用户。在马来西亚这个多元化的国家&#xff0c;配置高效稳定的直播网络对TikTok的运营至关重要。 配置马来西亚直播网络的必要性 广泛的地理覆盖&#xff1a;马来西亚包括大片陆地和众多岛屿&#xff0c;网…

加入运动健康数据开放平台,共赢鸿蒙未来

HarmonyOS SDK运动健康服务&#xff08;Health Service Kit&#xff09;是为华为生态应用打造的基于华为帐号和用户授权的运动健康数据开放平台。在获取用户授权后&#xff0c;开发者可以使用运动健康服务提供的开放能力获取运动健康数据&#xff0c;基于多种类型数据构建运动健…

大数据Spark 面经

1: Spark 整体架构 Spark 是新一代的大数据处理引擎&#xff0c;支持批处理和流处理&#xff0c;也还支持各种机器学习和图计算&#xff0c;它就是一个Master-worker 架构&#xff0c;所以整个的架构就如下所示&#xff1a; 2: Spark 任务提交命令 一般我们使用shell 命令提…