铃声的设置流程之前总结过,可以参考该文章:

Android音频相关(四)设置铃声流程总结

一、铃声播放准备

二、铃声播放

三、总结

本文主要介绍的下面标红的播放流程,SystemUI的播放流程我们在另一篇文章进行介绍。 

一、铃声播放准备

android 来电提示功能 安卓来电播报_铃声播放

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");
            }
        }
    }
  1. 这里调用了mLocalPlayer.setDataSource(mContext, mUri)的SetDataSource方法来处理
  2. 后面调用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对应的资源是否在播放完成后进行了释放(这个我们在另一片文章进行讨论)。