发布于2021-05-29 22:21 阅读(1018) 评论(0) 点赞(7) 收藏(4)
1.AudioService主要在java层主要有三个方面的作用:
(1) 音量管理
(2) 音频设备管理
(3) AudioFocus(音频焦点)机制
2.源码路径:
./frameworks/base/media/java/android/media/AudioManager.java
./frameworks/base/services/core/java/com/android/server/audio/AudioService.java
./frameworks/base/services/core/java/com/android/server/audio/MediaFocusControl.java
./frameworks/base/services/core/java/com/android/server/audio/FocusRequester.java
./frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
./frameworks/base/media/java/android/media/AudioSystem.java
./frameworks/base/services/java/com/android/server/SystemServer.java
./frameworks/base/services/core/java/com/android/server/WiredAccessoryManager.java
3.类图关系如下
由上图可知:
AudioService继承由IAudioService.aidl自动生成的IAudioService.Stub类,并实现IAudioService.Stub对应的相关方法,此处AudioService位于Bn端,即服务端。
AudioManager是AudioService在客户端的一个代理,位于Bp端。对AudioManager的一些方法调用最终几乎都会调用到AudioManager端。
AudioService的功能实现依赖AudioSystem类,AudioSystem.java没有实例化,它是java层到native层的代理。AudioService将通过它与AudioPolicyService以及AudioFlinger进行交互.
上面的图以及描述主要是参考了博主阿拉神农的博客,博客链接:https://blog.csdn.net/innost/article/details/47419025
3.1 音量管理
在android9.0 系统,音量按键的的音量管理主要由PhoneWindosManager服务来拦截按键的按下以及释放。当我们按下声音按键的时候UML流程图大概如下:
从上面的图中:
AudioService.java中:主要通过adjustStreamVolume来实现音量的控制
代码路径:./frameworks/base/services/core/java/com/android/server/audio/AudioService.java
protected void adjustStreamVolume(int streamType, int direction, int flags,
String callingPackage, String caller, int uid) {
//(1)设置音量,通过Hanlder的方式
.......
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
.......
//(2)更新ui
sendVolumeUpdate(stream, index, index, flags);
}
(1)设置音量
代码路径:./frameworks/base/services/core/java/com/android/server/audio/AudioService.java
public void handleMessage(Message msg) {
switch (msg.what) {
//(1)上面的sendMsg,这里handler处理MSG_SET_DEVICE_VOLUME
//setDeviceVolume最终调用AudioSystem的setStreamVolumeIndex进行音量控制
case MSG_SET_DEVICE_VOLUME:
setDeviceVolume((VolumeStreamState) msg.obj, msg.arg1);
break;
case MSG_SET_ALL_VOLUMES:
setAllVolumes((VolumeStreamState) msg.obj);
break;
//(2)将音量值设置保留到SettingProvider中,方便开机进行读取设置音量
case MSG_PERSIST_VOLUME:
persistVolume((VolumeStreamState) msg.obj, msg.arg1);
break;
......
}
(2)UI更新
最终会调用 mController.volumeChanged(streamType, flags);来更新ui,mController是IVolumeController类,其他应用可以通过AudioManager的setVolumeController提供的api来赋值mController,并且重写volumeChanged方法,然后当音量变化的的时候便可以进行回调通知Ui更新。
代码路径:
./frameworks/base/services/core/java/com/android/server/audio/AudioService.java
// UI update and Broadcast Intent
protected void sendVolumeUpdate(int streamType, int oldIndex, int index, int flags) {
streamType = mStreamVolumeAlias[streamType];
if (streamType == AudioSystem.STREAM_MUSIC) {
flags = (flags);
}
//调用postVolumeChanged来通知UI更新
mVolumeController.postVolumeChanged(streamType, flags);
}
public void postVolumeChanged(int streamType, int flags) {
if (mController == null)
return;
try {
//调用volumeChanged来通知已经注册VolumeController回调的apk来更新UI。
mController.volumeChanged(streamType, flags);
} catch (RemoteException e) {
Log.w(TAG, "Error calling volumeChanged", e);
}
}
相关代码路径:
3.2 音频设备管理
AudioService除了设置音量,还管理音频的一些插入以及移除,以耳机为例检测耳机的插入过程。
(1)耳机的监听服务:
android 9.0 系统中,在SystemServer的startOtherServices方法中启动了有线设备的接入监听服务WiredAccessoryManager。
代码路径:
./frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {
traceBeginAndSlog("StartWiredAccessoryManager");
...
try {
// Listen for wired headset changes
inputManager.setWiredAccessoryCallbacks(
new WiredAccessoryManager(context, inputManager));
} catch (Throwable e) {
reportWtf("starting WiredAccessoryManager", e);
}
traceEnd();
...
}
WiredAccessoryManager中通过WiredAccessoryObserver来监听有线设备的插入以及移除。
代码路径:
./frameworks/base/services/core/java/com/android/server/WiredAccessoryManager.java
public WiredAccessoryManager(Context context, InputManagerService inputManager) {
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager");
mWakeLock.setReferenceCounted(false);
mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
mInputManager = inputManager;
mUseDevInputEventForAudioJack =
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
//启动有线的接入监听
mObserver = new WiredAccessoryObserver();
}
WiredAccessoryObserver的实现
代码路径:
./frameworks/base/services/core/java/com/android/server/WiredAccessoryManager.java
class WiredAccessoryObserver extends UEventObserver {
private final List<UEventInfo> mUEventInfo;
...
...
// Monitor h2w
if (!mUseDevInputEventForAudioJack) {
uei = new UEventInfo(NAME_H2W, BIT_HEADSET, BIT_HEADSET_NO_MIC, BIT_LINEOUT);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
Slog.w(TAG, "This kernel does not have wired headset support");
}
}
// Monitor USB
uei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL, 0);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
Slog.w(TAG, "This kernel does not have usb audio support");
}
// Monitor HDMI
//
// If the kernel has support for the "hdmi_audio" switch, use that. It will be
// signalled only when the HDMI driver has a video mode configured, and the downstream
// sink indicates support for audio in its EDID.
//
// If the kernel does not have an "hdmi_audio" switch, just fall back on the older
// "hdmi" switch instead.
uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0, 0);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0, 0);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
Slog.w(TAG, "This kernel does not have HDMI audio support");
}
}
// Monitor DP
uei = new UEventInfo(NAME_DP, BIT_HDMI_AUDIO, 0, 0);
if (uei.checkSwitchExists()) {
retVal.add(uei);
} else {
Slog.w(TAG, "This kernel does not have dp audio support");
}
return retVal;
}
//当有kernel有onEvent事件的时候会调用onUEvent来进行设备插入的处理
//例如耳机的插入主要是/devices/virtual/switch/的节点变化
@Override
public void onUEvent(UEventObserver.UEvent event) {
if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
try {
String devPath = event.get("DEVPATH");
String name = event.get("SWITCH_NAME");
int state = Integer.parseInt(event.get("SWITCH_STATE"));
synchronized (mLock) {
updateStateLocked(devPath, name, state);//更新耳机便哈
}
} catch (NumberFormatException e) {
Slog.e(TAG, "Could not parse switch state from event " + event);
}
}
private void updateStateLocked(String devPath, String name, int state) {
for (int i = 0; i < mUEventInfo.size(); ++i) {
UEventInfo uei = mUEventInfo.get(i);
if (devPath.equals(uei.getDevPath())) {
updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state));
return;
}
}
}
...
...
}
上面WiredAccessoryObserver会通过监听/devices/virtual/switch/的节点变化,当节点有变化的时候,onUEvent会通过以下调用过程:
updateStateLocked---->updateLocked
---->mHandler.sendMessage(MSG_NEW_DEVICE_STATE)
----> setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);
---->setDeviceStateLocked
----> mAudioManager.setWiredDeviceConnectionState(inDevice, state, “”, headsetName);
最终调用了AudioService的setWiredDeviceConnectionState来进行上层耳机插入的处理:
在AudioService中调用流程:
---->setWiredDeviceConnectionState------(通过handler处理)
----->onSetWiredDeviceConnectionState
----->sendDeviceConnectionIntent
代码路径:
./frameworks/base/services/core/java/com/android/server/audio/AudioService.java
private void onSetWiredDeviceConnectionState(int device, int state, String address,
String deviceName, String caller) {
......
......
synchronized (mConnectedDevices) {
if ((state == 0) && ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0)) {
setBluetoothA2dpOnInt(true, "onSetWiredDeviceConnectionState state 0");
}
if (!handleDeviceConnection(state == 1, device, address, deviceName)) {
// change of connection state failed, bailout
return;
}
if (state != 0) {
if ((device & DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG) != 0) {
setBluetoothA2dpOnInt(false, "onSetWiredDeviceConnectionState state not 0");
}
//added by custom begin.定制化部分开始
//这里添加了自己当时需求的客制化部分,主要就是像苹果手机那样,
//插入调低音量为50
if ((device == AudioSystem.DEVICE_OUT_WIRED_HEADSET ||
device == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE)) {
VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
//50% of music volume
int halfIndex = streamState.getMaxIndex()/2;
streamState.setIndex(halfIndex, device, caller);
sendMsg(mAudioHandler,
MSG_SET_DEVICE_VOLUME,
SENDMSG_QUEUE,
device,
0,
streamState,
0);
//open safe media volume state
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
Slog.i(TAG,"vsoonbsp add,set 50% of music volume after headset insert,halfIndex="+halfIndex+" mSafeMediaVolumeState="+mSafeMediaVolumeState);
}
//added by custom end.定制化部分结束
if ((device & mSafeMediaVolumeDevices) != 0) {
sendMsg(mAudioHandler,
MSG_CHECK_MUSIC_ACTIVE,
SENDMSG_REPLACE,
0,
0,
caller,
MUSIC_ACTIVE_POLL_PERIOD_MS);
}
// Television devices without CEC service apply software volume on HDMI output
if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
if(isAtv() || isTablet()){
mFixedVolumeDevices = 0;
} else {
mFixedVolumeDevices |= AudioSystem.DEVICE_OUT_HDMI;
}
checkAllFixedVolumeDevices();
if (mHdmiManager != null) {
synchronized (mHdmiManager) {
if (mHdmiPlaybackClient != null) {
mHdmiCecSink = false;
mHdmiPlaybackClient.queryDisplayStatus(mHdmiDisplayStatusCallback);
}
}
}
}
if ((device & AudioSystem.DEVICE_OUT_HDMI) != 0) {
sendEnabledSurroundFormats(mContentResolver, true);
}
if(isAtv() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
if(mBitstreamSetting != null) {
mBitstreamSetting.hdmiAutoUpdate();
}
}
} else {
if (isPlatformTelevision() && ((device & AudioSystem.DEVICE_OUT_HDMI) != 0)) {
if (mHdmiManager != null) {
synchronized (mHdmiManager) {
mHdmiCecSink = false;
}
}
}
}
//发送耳机插入广播,通知其他感兴趣的系统模块以及apk,Intent.ACTION_HEADSET_PLUG
sendDeviceConnectionIntent(device, state, address, deviceName);
updateAudioRoutes(device, state);
}
}
(1)什么是音频焦点管理策略
场景1:手机正在播放音乐,突然电话来电,这时候音乐播放声音会被静音,而只留电话声音。
场景2:手机正在播放音乐,这时候如果导航应用播报,音乐播放音量会减小,等待导航播报结束后,音乐播放音量会恢复。
上面两个场景便用到了android的音频焦点管理,所以音频焦点策略就是在一个时间内只能允许一个音频播放实例拥有焦点,每个音频实例播放之前都应该向AudioService申请焦点,申请成功才开始播放;当一个音频实例正在播放的过程中,此时焦点被其他音频播放实例抢占,这时候正在播放的的音频实例会丢失焦点,失去焦点的音频播放实例应该根据实际情况来进行静音,暂停播放或者适当减小音量等操作,等被抢占的焦点被归还的时候再把之前的音频播放状态恢复。
这里注意两点:
①音频焦点策略只是android提供的一个机制,并且建议我们去使用的一种机制,并不是系统本身会强制采取的机制,如果各个应用都没有采用音频焦点策略管理机制,那么所有应用一起混合播放出来的音频声音,那将会可能是乱七八糟的声音。
②通话的时候,通话相关的音频模块也会申请音频焦点,其音频焦点的优先级是最高的,所以其可以从任何拥有音频焦点的音频播放实例中抢走音频焦点。
(2)音频焦点使用的Demo
demo实例来自于网上博主:renshuguo123723
博客链接:https://blog.csdn.net/renshuguo123723/article/details/85207284
public void play(){
//在开始播放前,先申请AudioFocus,注意传入的参数
int result = mAudioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);
//只有成功申请到AudioFocus之后才能开始播放
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
mMediaPlayer.start();
else//申请失败,如果系统没有问题,这一定是正在通话过程中,所以,还是不要播放了
showMessageCannotStartPlaybackDuringACall();
}
public void stop() {
//停止播放时,需要释放AudioFocus
mAudioManager.abandonAudioFocus(mAudioFocusListener);
}
private onAudioFocusChangeListener mAudioFocusListener = newOnAudioFocusChangeListener(){
//当AudioFocus发生变化时,这个函数将会被调用。其中参数focusChange指示发生了什么变化
public void onAudioFocusChange(int focusChange){
switch( focusChange) {
/*AudioFocus被长期夺走,需要中止播放,并释放AudioFocus,这种情况对应于
抢走AudioFocus的申请者使用了AUDIOFOCUS_GAIN*/
case AUDIOFOCUS_LOSS:
stop();
break;
/*AudioFocus被临时夺走,不久就会被归还,只需要暂停,AudioFocus被归还后
再恢复播放 ;这对应于抢走AudioFocus的申请者使用了AUDIOFOCUS_GAIN_TRANSIENT*/
case AUDIOFOCUS_LOSS_TRANSIENT:
saveCurrentPlayingState();
pause();
break;
/*AudioFocus被临时夺走,允许不暂停,所以降低音量 ,这对应于抢走AudioFocus的
回放实例使用了AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK*/
case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
saveCurrentPlayingState();
setVolume(getVolume()/2);
break;
/*AudioFocus被归还,这时需要恢复被夺走前的播放状态*/
case AUDIOFOCUS_GAIN:
restorePlayingState();
break;
}
}
};
(3)焦点回调实现分析
从上面的demo实例中可知,想要知道焦点的变化,
①实现焦点变化的回调方法
②调用AudioManager中requestAudioFocus的申请音频焦点。
那么音频焦点是如何被调用的呢?下面通过一张图来解析描述:
如图
①一些应用调用AudioManager中requestAudioFocus来申请焦点,requestAudioFocus而最终会调用实现如下代码,该方法中会将回调OnAudioFocusChangeListener l对像以及其他参数封装在AudioFocusRequest类型对象afr里面。
代码路径:
./frameworks/base/media/java/android/media/AudioManager.java
public int requestAudioFocus(OnAudioFocusChangeListener l,
@NonNull AudioAttributes requestAttributes,
int durationHint,
int flags,
AudioPolicy ap) throws IllegalArgumentException {
......
......
/**该步骤(1)主要是将所有音频申请焦点的信息通过builder的封装到一个
*AudioFocusRequest类型的对象afr
*afr最重要一点包含了焦点变化时的OnAudioFocusChangeListener类型回调对象l
*/
final AudioFocusRequest afr = new AudioFocusRequest.Builder(durationHint)
.setOnAudioFocusChangeListenerInt(l, null /* no Handler for this legacy API */)
.setAudioAttributes(requestAttributes)
.setAcceptsDelayedFocusGain((flags & AUDIOFOCUS_FLAG_DELAY_OK)
== AUDIOFOCUS_FLAG_DELAY_OK)
.setWillPauseWhenDucked((flags & AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS)
== AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS)
.setLocksFocus((flags & AUDIOFOCUS_FLAG_LOCK) == AUDIOFOCUS_FLAG_LOCK)
.build();
//利用封装好的对象afr申请焦点
return requestAudioFocus(afr, ap);
}
代码路径:
./frameworks/base/media/java/android/media/AudioManager.java
public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
.......
.......
/*
*注册上面封装好的AudioFocusRequest类型对象afr,注意里包含了
*OnAudioFocusChangeListener类型的回调对象l
*/
registerAudioFocusRequest(afr);
final IAudioService service = getService();
.......
final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
final BlockingFocusResultReceiver focusReceiver;
synchronized (mFocusRequestsLock) {
try {
/**
*调用AudioService的方法调用
*/
// TODO status contains result and generation counter for ext policy
status = service.requestAudioFocus(afr.getAudioAttributes(),
afr.getFocusGain(), mICallBack,
mAudioFocusDispatcher,
clientId,
getContext().getOpPackageName() /* package name */, afr.getFlags(),
ap != null ? ap.cb() : null,
sdk);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
// default path with no external focus policy
return status;
}
......
}
②③是将上面封装的AudioFocusRequest对象afr
与hanlder h又封装在FocusRequestInfo类型fri对象里面,然后将该对象fri储存在key-value的Map里面。
代码路径:
./frameworks/base/media/java/android/media/AudioManager.java
public void registerAudioFocusRequest(@NonNull AudioFocusRequest afr) {
final Handler h = afr.getOnAudioFocusChangeListenerHandler();
/*
*步骤(2):afr与hanlder h封装成FocusRequestInfo类型fri对象里面
*/
final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :
new ServiceEventHandlerDelegate(h).getHandler());
final String key = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
/*
*步骤(3):将fri放到Map表里面
*/
mAudioFocusIdListenerMap.put(key, fri);
}
④⑤从上面看出,最终是将应用注册的onAudioFocusChangeListener类型的回调对象l(这里用参数l来表示)包含在FocusRequestInfo类型的fri对象里面,并且将fri放到一个Map里面。到这里可能有疑惑,放到Map里面,AudioManager如何调用回调对像l来通知应用呢?其实AudioManager中有一个mAudioFocusDispatcher对象,该对象的回调方法dispatchAudioFocusChange会回调onAudioFocusChangeListener l的回调方法onAudioFocusChange。如下:
代码路径:
./frameworks/base/media/java/android/media/AudioManager.java
代码路径:
private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {
@Override
public void dispatchAudioFocusChange(int focusChange, String id) {
/**
*步骤(4):该函数主要获取上面(3)步骤放置到Map里面的FocusRequestInfo对象
*/
final FocusRequestInfo fri = findFocusRequestInfo(id);
if (fri != null) {
/*步骤(5):
*这一步便是上面步骤(1)有把回调对象OnAudioFocusChangeListener封装在了
*AudioFocusRequest类型的对象里面,
*fri.mRequest.getOnAudioFocusChangeListener返回的是注册进去的
* OnAudioFocusChangeListener类型的回调对象。
*/
final OnAudioFocusChangeListener listener =
fri.mRequest.getOnAudioFocusChangeListener();
if (listener != null) {
final Handler h = (fri.mHandler == null) ?
mServiceEventHandlerDelegate.getHandler() : fri.mHandler;
/*
*通过handler发送MSSG_FOCUS_CHANGE信息
*/
final Message m = h.obtainMessage(
MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/,
id/*obj*/);
h.sendMessage(m);
}
}
}
接收MSSG_FOCUS_CHANGE信息,并进行调用onAudioFocusChangeListener l的回调方法onAudioFocusChange来通知应用。
代码路径:
./frameworks/base/media/java/android/media/AudioManager.java
private class ServiceEventHandlerDelegate {
private final Handler mHandler;
ServiceEventHandlerDelegate(Handler handler) {
Looper looper;
if (handler == null) {
if ((looper = Looper.myLooper()) == null) {
looper = Looper.getMainLooper();
}
} else {
looper = handler.getLooper();
}
if (looper != null) {
// implement the event handler delegate to receive events from audio service
mHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
/*
*接收MSSG_FOCUS_CHANGE信息,并进行回调onAudioFocusChangeListener l通知应用。
*/
case MSSG_FOCUS_CHANGE: {
final FocusRequestInfo fri = findFocusRequestInfo((String)msg.obj);
if (fri != null) {
final OnAudioFocusChangeListener listener =
fri.mRequest.getOnAudioFocusChangeListener();
if (listener != null) {
Log.d(TAG, "dispatching onAudioFocusChange("
+ msg.arg1 + ") to " + msg.obj);
/*
*这里调用onAudioFocusChangeListener里面的onAudioFocusChange
*/
listener.onAudioFocusChange(msg.arg1);
}
}
} break;
那么AudioService是如何可以回调 mAudioFocusDispatcher的呢?
(1)mAudioFocusDispatcher是在AudioManager调用AudioService在AudioManager客户端的代理方法requestAudioFocus的时候通过参数形式将mAudioFocusDispatcher传入。
代码路径:
./frameworks/base/media/java/android/media/AudioManager.java
synchronized (mFocusRequestsLock) {
try {
// TODO status contains result and generation counter for ext policy
/*
*r调用AudioService在AudioManager客户端的代理方法requestAudioFocus
*此处传入mAudioFocusDispatcher对像
*/
status = service.requestAudioFocus(afr.getAudioAttributes(),
afr.getFocusGain(), mICallBack,
mAudioFocusDispatcher,
clientId,
getContext().getOpPackageName() /* package name */, afr.getFlags(),
ap != null ? ap.cb() : null,
sdk);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
(2)然后在AudioService端流程如下:最终会调用mAudioFocusDispatcher中的回调方法dispatchAudioFocusChange。
源码路径:
./frameworks/base/services/core/java/com/android/server/audio/AudioService.java
./frameworks/base/services/core/java/com/android/server/audio/MediaFocusControl.java
./frameworks/base/services/core/java/com/android/server/audio/FocusRequester.java
原文链接:https://blog.csdn.net/Ian22l/article/details/117257393
作者:我是一个射手
链接:http://www.javaheidong.com/blog/article/207681/9c9dcac6317d30531626/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!