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
相关推荐
无极程序员39 分钟前
PHP常量
android·ide·android studio
萌面小侠Plus2 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农2 小时前
Android Profiler 内存分析
android
大风起兮云飞扬丶2 小时前
Android——多线程、线程通信、handler机制
android
L72562 小时前
Android的Handler
android
清风徐来辽2 小时前
Android HandlerThread 基础
android
HerayChen3 小时前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野3 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing11233 小时前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小黄人软件3 小时前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio