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

产品需求

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

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

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

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

Framework层接口

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

java 复制代码
/** 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来调用服务的具体实现

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方法

java 复制代码
//动态变更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服务构造初始化时设置最大音量值

java 复制代码
	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.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调用

java 复制代码
 /**
  * 设置最大音量百分比
  * @param percent 0~10,10%递进
  */
 public void setMaxVolumePercent(int percent){
     Log.i(TAG, "setMaxVolumePercent() percent = "+ percent);
     //保存最大音量百分比设置
     Settings.Global.putString(context.getContentResolver(), "persist.sys.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

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值有没有按预期的变化即可。

shell 复制代码
- 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
相关推荐
千里马学框架15 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台15 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone15 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc16 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo17 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770018 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼18 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
开发笔记-阿牛19 小时前
蓝牙音乐灯拆解,又一次拆到蓝牙音乐灯芯片CK6865L
人工智能·单片机·嵌入式硬件·音视频·音频
AFinalStone19 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen19 小时前
属性动画原理与高级动画实现
android·kotlin·canvas