铃声的设置流程之前总结过,可以参考该文章:
Android音频相关(四)设置铃声流程总结
一、铃声播放准备
二、铃声播放
三、总结
本文主要介绍的下面标红的播放流程,SystemUI的播放流程我们在另一篇文章进行介绍。
一、铃声播放准备
1.来电后最先会通过telecom中的Ringer.java里面的方法 mRingtonePlayer.play(mRingtoneFactory, foregroundCall);来启动铃声
Ringer.java (packages\services\telecomm\src\com\android\server\telecom)
public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) {
if (foregroundCall == null) {
Log.wtf(this, "startRinging called with null foreground call.");
return false;
}
AudioManager audioManager =
(AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
boolean isVolumeOverZero = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
boolean shouldRingForContact = shouldRingForContact(foregroundCall.getContactUri());
boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(foregroundCall) == null);
boolean isSelfManaged = foregroundCall.isSelfManaged();
boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
// Acquire audio focus under any of the following conditions:
// 1. Should ring for contact and there's an HFP device attached
// 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
// present.
// 3. The call is self-managed.
boolean shouldAcquireAudioFocus =
isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
// Don't do call waiting operations or vibration unless these are false.
boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged;
if (endEarly) {
if (letDialerHandleRinging) {
Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING);
}
Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
"isSelfManaged=%s", isTheaterModeOn, letDialerHandleRinging, isSelfManaged);
return shouldAcquireAudioFocus;
}
stopCallWaiting();
if (isRingerAudible) {
mRingingCall = foregroundCall;
Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
// Because we wait until a contact info query to complete before processing a
// call (for the purposes of direct-to-voicemail), the information about custom
// ringtones should be available by the time this code executes. We can safely
// request the custom ringtone from the call and expect it to be current.
mRingtonePlayer.play(mRingtoneFactory, foregroundCall);
} else {
Log.i(this, "startRinging: skipping because ringer would not be audible. " +
"isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
isVolumeOverZero, shouldRingForContact, isRingtonePresent);
}
if (shouldVibrate(mContext, foregroundCall) && !mIsVibrating && shouldRingForContact) {
mVibratingCall = foregroundCall;
mVibrator.vibrate(mVibrationEffect, VIBRATION_ATTRIBUTES);
mIsVibrating = true;
} else if (mIsVibrating) {
Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "already vibrating");
}
return shouldAcquireAudioFocus;
}
2.上面的铃声主要调用的是AsyncRingtonePlayer的play方法来发送广播EVENT_PLAY
/** Plays the ringtone. */
public void play(RingtoneFactory factory, Call incomingCall) {
Log.d(this, "Posting play.");
SomeArgs args = SomeArgs.obtain();
args.arg1 = factory;
args.arg2 = incomingCall;
//这里我们可以看见发送了一个广播EVENT_PLAY
postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);
}
新建一个线程来接受message
/**
* Creates a new ringtone Handler running in its own thread.
*/
private Handler getNewHandler() {
Preconditions.checkState(mHandler == null);
HandlerThread thread = new HandlerThread("ringtone-player");
thread.start();
return new Handler(thread.getLooper()) {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case EVENT_PLAY:
handlePlay((SomeArgs) msg.obj);
break;
case EVENT_REPEAT:
handleRepeat();
break;
case EVENT_STOP:
handleStop();
break;
}
}
};
}
这里面会接受到EVENT_PLAY消息来调用handlePlay((SomeArgs) msg.obj)方法。
/**
* Starts the actual playback of the ringtone. Executes on ringtone-thread.
*/
private void handlePlay(SomeArgs args) {
RingtoneFactory factory = (RingtoneFactory) args.arg1;
Call incomingCall = (Call) args.arg2;
args.recycle();
// don't bother with any of this if there is an EVENT_STOP waiting.
if (mHandler.hasMessages(EVENT_STOP)) {
return;
}
// If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected. Do not play 如果铃声的URI是空的,这不播放铃声。
// anything.
if(Uri.EMPTY.equals(incomingCall.getRingtone())) {
mRingtone = null;
return;
}
ThreadUtil.checkNotOnMainThread();
Log.i(this, "Play ringtone.");
if (mRingtone == null) {
//factory.getRingtone中,初始化了默认的uri以及stream类型
mRingtone = factory.getRingtone(incomingCall);
if (mRingtone == null) {
Uri ringtoneUri = incomingCall.getRingtone();
String ringtoneUriString = (ringtoneUri == null) ? "null" :
ringtoneUri.toSafeString();
Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " +
"factory. Skipping ringing. Uri was: " + ringtoneUriString);
return;
}
}
handleRepeat();
}
这里插播一下,上面的mRingtone = factory.getRingtone(incomingCall);是通过RingtoneFactory来对Ringtone进行初始化的。
RingtoneFactory.java (packages\services\telecomm\src\com\android\server\telecom)
public Ringtone getRingtone(Call incomingCall) {
// Use the default ringtone of the work profile if the contact is a work profile contact.
Context userContext = isWorkContact(incomingCall) ?
getWorkProfileContextForUser(mCallsManager.getCurrentUserHandle()) :
getContextForUserHandle(mCallsManager.getCurrentUserHandle());
Uri ringtoneUri = incomingCall.getRingtone();
Ringtone ringtone = null;
if(ringtoneUri != null && userContext != null) {
// Ringtone URI is explicitly specified. First, try to create a Ringtone with that.
//这里通过调用RingtoneManager的getRingtone方法来来实现MediaPlayer的相关初始化
ringtone = RingtoneManager.getRingtone(userContext, ringtoneUri);
}
if(ringtone == null) {
// Contact didn't specify ringtone or custom Ringtone creation failed. Get default
// ringtone for user or profile.
Context contextToUse = hasDefaultRingtoneForUser(userContext) ? userContext : mContext;
Uri defaultRingtoneUri;
if (UserManager.get(contextToUse).isUserUnlocked(contextToUse.getUserId())) {
defaultRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(contextToUse,
RingtoneManager.TYPE_RINGTONE);
} else {
defaultRingtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
}
if (defaultRingtoneUri == null) {
return null;
}
ringtone = RingtoneManager.getRingtone(contextToUse, defaultRingtoneUri);
}
if (ringtone != null) {
ringtone.setStreamType(AudioManager.STREAM_RING);
}
return ringtone;
}
ringtone = RingtoneManager.getRingtone(userContext, ringtoneUri);这里通过调用RingtoneManager的getRingtone方法来来实现MediaPlayer的相关初始化
RingtoneManager.java (frameworks\base\media\java\android\media)
/**
* Returns a {@link Ringtone} for a given sound URI.
* <p>
* If the given URI cannot be opened for any reason, this method will
* attempt to fallback on another sound. If it cannot find any, it will
* return null.
*
* @param context A context used to query.
* @param ringtoneUri The {@link Uri} of a sound or ringtone.
* @return A {@link Ringtone} for the given URI, or null.
*/
public static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
// Don't set the stream type
return getRingtone(context, ringtoneUri, -1);
}
//FIXME bypass the notion of stream types within the class
/**
* Returns a {@link Ringtone} for a given sound URI on the given stream
* type. Normally, if you change the stream type on the returned
* {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
* an optimized route to avoid that.
*
* @param streamType The stream type for the ringtone, or -1 if it should
* not be set (and the default used instead).
* @see #getRingtone(Context, Uri)
*/
private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType) {
try {
final Ringtone r = new Ringtone(context, true);
if (streamType >= 0) {
//FIXME deprecated call
r.setStreamType(streamType);
}
//这里调用了Ringtone的setUri
r.setUri(ringtoneUri);
return r;
} catch (Exception ex) {
Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
}
return null;
}
r.setUri(ringtoneUri);这里调用了Ringtone的setUri,这里面的uri还分为本地和远程的,我们在后面会讲述。
Ringtone.java (frameworks\base\media\java\android\media)
到这里我们就看到了mLocalPlayer = new MediaPlayer();铃声的播放MediaPlayer的创建过程。
public void setUri(Uri uri) {
destroyLocalPlayer();
mUri = uri;
if (mUri == null) {
return;
}
// TODO: detect READ_EXTERNAL and specific content provider case, instead of relying on throwing
// try opening uri locally before delegating to remote player
mLocalPlayer = new MediaPlayer();
try {
mLocalPlayer.setDataSource(mContext, mUri);
mLocalPlayer.setAudioAttributes(mAudioAttributes);
synchronized (mPlaybackSettingsLock) {
applyPlaybackProperties_sync();
}
mLocalPlayer.prepare();
} catch (SecurityException | IOException e) {
destroyLocalPlayer();
if (!mAllowRemote) {
Log.w(TAG, "Remote playback not allowed: " + e);
}
}
if (LOGD) {
if (mLocalPlayer != null) {
Log.d(TAG, "Successfully created local player");
} else {
Log.d(TAG, "Problem opening; delegating to remote player");
}
}
}
- 这里调用了mLocalPlayer.setDataSource(mContext, mUri)的SetDataSource方法来处理
- 后面调用applyPlaybackProperties_sync()方法来进行远程调用和本地调用的播放器设置。
MediaPlayer.java (frameworks\base\media\java\android\media)
public void setDataSource(@NonNull Context context, @NonNull Uri uri,
@Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)
throws IOException {
if (context == null) {
throw new NullPointerException("context param can not be null.");
}
if (uri == null) {
throw new NullPointerException("uri param can not be null.");
}
if (cookies != null) {
CookieHandler cookieHandler = CookieHandler.getDefault();
if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
throw new IllegalArgumentException("The cookie handler has to be of CookieManager "
+ "type when cookies are provided.");
}
}
// The context and URI usually belong to the calling user. Get a resolver for that user
// and strip out the userId from the URI if present.
final ContentResolver resolver = context.getContentResolver();
final String scheme = uri.getScheme();
final String authority = ContentProvider.getAuthorityWithoutUserId(uri.getAuthority());
/// M: If scheme is null, try to get path from uri and setDataSource with path.
if (scheme == null || ContentResolver.SCHEME_FILE.equals(scheme)) {
setDataSource(uri.getPath());
return;
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
&& Settings.AUTHORITY.equals(authority)) {
// Try cached ringtone first since the actual provider may not be
// encryption aware, or it may be stored on CE media storage
final int type = RingtoneManager.getDefaultType(uri);
//获取铃声的两个URL
final Uri cacheUri = RingtoneManager.getCacheForType(type, context.getUserId());
final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
if (attemptDataSource(resolver, cacheUri)) {
return;
} else if (attemptDataSource(resolver, actualUri)) {
return;
} else {
setDataSource(uri.toString(), headers, cookies);
}
} else {
// Try requested Uri locally first, or fallback to media server
if (attemptDataSource(resolver, uri)) {
return;
} else {
setDataSource(uri.toString(), headers, cookies);
}
}
}
这里会先attemptDataSource(resolver, cacheUri)如果不成功的话,才会进入attemptDataSource(resolver, actualUri)。
applyPlaybackProperties_sync()远程调用和本地调用的一些其他设置。如音量等。
/**
* Must be called synchronized on mPlaybackSettingsLock
*/
private void applyPlaybackProperties_sync() {
if (mLocalPlayer != null) {
mLocalPlayer.setVolume(mVolume);
mLocalPlayer.setLooping(mIsLooping);
} else if (mAllowRemote && (mRemotePlayer != null)) {
try {
mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping);
} catch (RemoteException e) {
Log.w(TAG, "Problem setting playback properties: ", e);
}
} else {
Log.w(TAG,
"Neither local nor remote player available when applying playback properties");
}
}
二、铃声的播放过程
1.这里我们回到AsyncRingtonePlayer.java的handleplay()方法里面的handleRepeat();
AsyncRingtonePlayer.java (packages\services\telecomm\src\com\android\server\telecom)
/**
* Starts the actual playback of the ringtone. Executes on ringtone-thread.
*/
private void handlePlay(SomeArgs args) {
RingtoneFactory factory = (RingtoneFactory) args.arg1;
Call incomingCall = (Call) args.arg2;
args.recycle();
// don't bother with any of this if there is an EVENT_STOP waiting.
if (mHandler.hasMessages(EVENT_STOP)) {
return;
}
// If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected. Do not play
// anything.
if(Uri.EMPTY.equals(incomingCall.getRingtone())) {
mRingtone = null;
return;
}
ThreadUtil.checkNotOnMainThread();
Log.i(this, "Play ringtone.");
if (mRingtone == null) {
mRingtone = factory.getRingtone(incomingCall);
if (mRingtone == null) {
Uri ringtoneUri = incomingCall.getRingtone();
String ringtoneUriString = (ringtoneUri == null) ? "null" :
ringtoneUri.toSafeString();
Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " +
"factory. Skipping ringing. Uri was: " + ringtoneUriString);
return;
}
}
handleRepeat();
}
handleRepeat()这个里面会调用Ringtone的play方法。
private void handleRepeat() {
if (mRingtone == null) {
return;
}
if (mRingtone.isPlaying()) {
Log.d(this, "Ringtone already playing.");
} else {
//调用Ringtone的play方法
mRingtone.play();
Log.i(this, "Repeat ringtone.");
}
// Repost event to restart ringer in {@link RESTART_RINGER_MILLIS}.
synchronized(this) {
if (!mHandler.hasMessages(EVENT_REPEAT)) {
mHandler.sendEmptyMessageDelayed(EVENT_REPEAT, RESTART_RINGER_MILLIS);
}
}
}
2.进行播放器的选择和播放。
Ringtone.java (frameworks\base\media\java\android\media)
/**
* Plays the ringtone.
*/
public void play() {
if (mLocalPlayer != null) {
// do not play ringtones if stream volume is 0
// (typically because ringer mode is silent).
if (mAudioManager.getStreamVolume(
AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) {
startLocalPlayer();
}
} else if (mAllowRemote && (mRemotePlayer != null)) {
/// M: Avoid NullPointerException cause by mUri is null.
final Uri canonicalUri = (mUri == null ? null : mUri.getCanonicalUri());
final boolean looping;
final float volume;
synchronized (mPlaybackSettingsLock) {
looping = mIsLooping;
volume = mVolume;
}
try {
mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes, volume, looping);
} catch (RemoteException e) {
if (!playFallbackRingtone()) {
Log.w(TAG, "Problem playing ringtone: " + e);
}
}
} else {
if (!playFallbackRingtone()) {
Log.w(TAG, "Neither local nor remote playback available");
}
}
}
通过上面的代码我们会发现有两个播放器分别是startLocalPlayer()和mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes, volume, looping);这两个播放器。这个涉及到本地和远程播放流程,其判断依据为mAllowRemote的逻辑,而这个判断与ringtone对象的初始化有关,前面通过RingtoneManager的getRingtone的时候已经设置为true,因而创建了mRemotePlayer对象。
我们可以看见mAllowRemote和mRemotePlayer在创建对象的时候就已经被初始化了。
/** {@hide} */
public Ringtone(Context context, boolean allowRemote) {
mContext = context;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAllowRemote = allowRemote;
mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
mRemoteToken = allowRemote ? new Binder() : null;
}
3.这里面我们插播一下如何会调用起来mRemotePlayer播放器。
看英文注释 我们也能了解各大概try opening uri locally before delegating to remote player。首先会先传入系统内置的音频资源uri,这个时候在setUri的时候就会成功创建mLocalPlayer,这个时候走的是系统进程播放的流程,会调用本地播放方法。若传入的uri为外置存储的音频资源,这个时候在setUri的时候因为抛了SecurityException会执行destroyLocalPlayer,之后mLocalPlayer这个对象就为null,在执行play()方法的时候,就会进入远程播放的流程。
public void setUri(Uri uri) {
destroyLocalPlayer();
mUri = uri;
if (mUri == null) {
return;
}
// TODO: detect READ_EXTERNAL and specific content provider case, instead of relying on throwing
// try opening uri locally before delegating to remote player
mLocalPlayer = new MediaPlayer();
try {
mLocalPlayer.setDataSource(mContext, mUri);
mLocalPlayer.setAudioAttributes(mAudioAttributes);
synchronized (mPlaybackSettingsLock) {
applyPlaybackProperties_sync();
}
mLocalPlayer.prepare();
} catch (SecurityException | IOException e) {
destroyLocalPlayer();
if (!mAllowRemote) {
Log.w(TAG, "Remote playback not allowed: " + e);
}
}
if (LOGD) {
if (mLocalPlayer != null) {
Log.d(TAG, "Successfully created local player");
} else {
Log.d(TAG, "Problem opening; delegating to remote player");
}
}
}
因为篇幅的问题,远程播放我们就不在这里面展开了,欢迎大家查看远程播放展开篇的内容。
4.对于本地播放器来说startLocalPlayer()就进入了播放模式了
private void startLocalPlayer() {
if (mLocalPlayer == null) {
return;
}
synchronized (sActiveRingtones) {
sActiveRingtones.add(this);
}
mLocalPlayer.setOnCompletionListener(mCompletionListener);
mLocalPlayer.start();
}
三、总结
通过上面大概的流程可以总结如下:
1、播放铃声的时候,会根据传入铃声的uri不同而选择不同的播放器,内置资源通过系统进程播放,外置资源的通过systemui传入新的uri播放,本地和远程均异常时系统会尝试播放系统默认资源;
2、播放外置或内置铃声的判断在于设置数据源的安全异常,若关闭selinux的权限,这两种方式都可以通过系统进程播放;
3、通过RingtonePlayer远程播放铃声时,需要注意传入的token对应的资源是否在播放完成后进行了释放(这个我们在另一片文章进行讨论)。