来电铃声和通话中的提示音

InCallTonePlayer

packages/services/Telecomm/src/com/android/server/telecom/InCallTonePlayer.java,该类用于通话中提示音的播放。

   public static class Factory {
        private final CallAudioManager mCallAudioManager;
        private final TelecomSystem.SyncRoot mLock;

        Factory(CallAudioManager callAudioManager, TelecomSystem.SyncRoot lock) {
            mCallAudioManager = callAudioManager;
            mLock = lock;
        }

        InCallTonePlayer createPlayer(int tone) {
            return new InCallTonePlayer(tone, mCallAudioManager, mLock);
        }
    }
使用工厂方法获取该类实例对象。

    /** The ID of the tone to play. */
    private final int mToneId;
mToneId是提示音id,构造函数中初始化,值定义有:

 public static final int TONE_INVALID = 0;
    public static final int TONE_BUSY = 1;
    public static final int TONE_CALL_ENDED = 2;
    ...
    public static final int TONE_VOICE_PRIVACY = 13;
    public static final int TONE_VIDEO_UPGRADE = 14;
由于InCallTonePlayer继承自Thread,所以它的运行在run方法中

public final class InCallTonePlayer extends Thread
 @Override
    public void run() {
        ToneGenerator toneGenerator = null;
        try {
            Log.d(this, "run(toneId = %s)", mToneId);

            final int toneType;  // Passed to ToneGenerator.startTone.
            final int toneVolume;  // Passed to the ToneGenerator constructor.
            final int toneLengthMillis;

            switch (mToneId) {
                case TONE_BUSY:
                    // TODO: CDMA-specific tones
                    toneType = ToneGenerator.TONE_SUP_BUSY;       //依据id获取tone的类型、音量和时长,可见tone类型是在framework中定义的
                    toneVolume = RELATIVE_VOLUME_HIPRI;
                    toneLengthMillis = 4000;
                    break;
                ...
                default:
                    throw new IllegalStateException("Bad toneId: " + mToneId);
            }

            int stream = AudioManager.STREAM_VOICE_CALL;
            if (mCallAudioManager.isBluetoothAudioOn()) {
                stream = AudioManager.STREAM_BLUETOOTH_SCO;   //设置音频stream,有蓝牙连接就切换到蓝牙设备播放
            
            ... 
            toneGenerator = new ToneGenerator(stream, toneVolume);  //初始化ToneGenerator
            ...


            synchronized (this) {
                if (mState != STATE_STOPPED) {
                    mState = STATE_ON;
                    toneGenerator.startTone(toneType);   //播放
                    try {
                        Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId,
                                toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
                        wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS);  //阻塞直到停止播放
                    } catch (InterruptedException e) {
                        Log.w(this, "wait interrupted", e);
                    }
                }
            }
            mState = STATE_OFF;
        } finally {
            if (toneGenerator != null) {  //资源释放
                toneGenerator.release();
            }
            cleanUpTonePlayer();
        }
    }
    void stopTone() {    //停止tone播放
        synchronized (this) {
            if (mState == STATE_ON) {
                Log.d(this, "Stopping the tone %d.", mToneId);
                notify();   //由于run中最后是调用wait阻塞,所以这里notify就是通知线程可以结束运行了。
            }
            mState = STATE_STOPPED;
        }
    }
tone音的原理,见frameworks/base/media/java/android/media/ToneGenerator.java中的定义:

    /**
     * Proprietary tone, prompt tone: 400Hz+1200Hz, 200ms ON
     *
     * @see #ToneGenerator(int, int)
     */
    public static final int  TONE_PROP_PROMPT = 27;
每个tone音是两个不同频率的音混合,响固定时间。

AsyncRingtonePlayer

packages/services/Telecomm/src/com/android/server/telecom/AsyncRingtonePlayer.java

    private static final int EVENT_PLAY = 1;
    private static final int EVENT_STOP = 2;
    private static final int EVENT_REPEAT = 3;
定义了三个消息常量,分别代表播放、停止和重复

    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((Uri) msg.obj);
                        break;
                    case EVENT_REPEAT:
                        handleRepeat();
                        break;
                    case EVENT_STOP:
                        handleStop();
                        break;
                }
            }
        };
    }
消息处理Handler初始化中可以看出使用了HandlerThread,这样就看出铃声的处理是在线程中运行,这也对应了Async的前缀。铃声的播放等操作就是对队列的消息操作,这个不再分析。

Ringer

packages/services/Telecomm/src/com/android/server/telecom/Ringer.java,负责来电时候铃声和振动处理

 private void startRingingOrCallWaiting(Call call) {   //响铃的入口
        Call foregroundCall = mCallsManager.getForegroundCall();
        Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);
      
        if (mRingingCalls.contains(foregroundCall)
            && !mCallsManager.hasActiveOrHoldingCall()) {   //普通来电状态
             
            ...

            AudioManager audioManager =
                    (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
                if (mState != STATE_RINGING) {
                    Log.event(call, Log.Events.START_RINGER);
                    mState = STATE_RINGING;
                }
                mCallAudioManager.setIsRinging(call, true);
                ...
                mRingtonePlayer.play(foregroundCall.getRingtone()); //使用AsyncRingtonePlayer响铃
                ...
            } else {
                Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
            }

            if (shouldVibrate(mContext) && !mIsVibrating) {   //振动
                mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
                        VIBRATION_ATTRIBUTES);
                mIsVibrating = true;
            }
        } else if (foregroundCall != null) {     //通话中来电   
            ...

            // All incoming calls are in background so play call waiting.
            stopRinging(call, "Stop for call-waiting");


            if (mState != STATE_CALL_WAITING) {
                Log.event(call, Log.Events.START_CALL_WAITING_TONE);
                mState = STATE_CALL_WAITING;
            }

            if (mCallWaitingPlayer == null) {
                mCallWaitingPlayer =
                        mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
                mCallWaitingPlayer.startTone();   //使用InCallTonePlayer响起提示音
            }
        }
    }

音量键和电源键停止响铃

系统按键会先在PhoneWindowManager中处理然后再发送到app窗口去处理,关于铃声的PhoneWindowManager会处理,所以不会发送到app再去处理,这个对要修改这个逻辑的同学来说不大友好,不过一般厂商也不改这些默认动作。
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        ...
        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_DOWN:
            case KeyEvent.KEYCODE_VOLUME_UP:
            case KeyEvent.KEYCODE_VOLUME_MUTE: {
                        ...
			TelecomManager telecomManager = getTelecommService();
                    			if (telecomManager != null) {
                        			if (telecomManager.isRinging()) {                           
                          			  telecomManager.silenceRinger();

        ...
}
音量键在来电时候按下会调用TelecomManager方法停止响铃。
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
    ...
    if (telecomManager != null) {
            if (telecomManager.isRinging()) {
                // Pressing Power while there's a ringing incoming
                // call should silence the ringer.
                telecomManager.silenceRinger();
            } else if ((mIncallPowerBehavior
                    & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
                    && telecomManager.isInCall() && interactive) {
                // Otherwise, if "Power button ends call" is enabled,
                // the Power button will hang up any current active call.
                hungUp = telecomManager.endCall();
            }
        }
    ...
}
电源键处理中如果是来电会停止响铃,如果是通话中而且INCALL_POWER_BUTTON_BEHAVIOR_HANGUP值大于0的话会挂断电话。

你可能感兴趣的:(android)