CallAudioManager.java revision 59c77e691e566373cbb6d06b3909c1126035d982
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.telecom;
18
19import android.annotation.NonNull;
20import android.media.IAudioService;
21import android.media.ToneGenerator;
22import android.telecom.CallAudioState;
23import android.telecom.VideoProfile;
24import android.util.SparseArray;
25
26import com.android.internal.annotations.VisibleForTesting;
27import com.android.internal.util.IndentingPrintWriter;
28
29import java.util.Collection;
30import java.util.HashSet;
31import java.util.Set;
32import java.util.LinkedHashSet;
33
34public class CallAudioManager extends CallsManagerListenerBase {
35
36    public interface AudioServiceFactory {
37        IAudioService getAudioService();
38    }
39
40    private final String LOG_TAG = CallAudioManager.class.getSimpleName();
41
42    private final LinkedHashSet<Call> mActiveDialingOrConnectingCalls;
43    private final LinkedHashSet<Call> mRingingCalls;
44    private final LinkedHashSet<Call> mHoldingCalls;
45    private final Set<Call> mCalls;
46    private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls;
47
48    private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
49    private final CallAudioModeStateMachine mCallAudioModeStateMachine;
50    private final CallsManager mCallsManager;
51    private final InCallTonePlayer.Factory mPlayerFactory;
52    private final Ringer mRinger;
53    private final RingbackPlayer mRingbackPlayer;
54    private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
55
56    private Call mForegroundCall;
57    private boolean mIsTonePlaying = false;
58    private InCallTonePlayer mHoldTonePlayer;
59
60    public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine,
61            CallsManager callsManager,
62            CallAudioModeStateMachine callAudioModeStateMachine,
63            InCallTonePlayer.Factory playerFactory,
64            Ringer ringer,
65            RingbackPlayer ringbackPlayer,
66            DtmfLocalTonePlayer dtmfLocalTonePlayer) {
67        mActiveDialingOrConnectingCalls = new LinkedHashSet<>();
68        mRingingCalls = new LinkedHashSet<>();
69        mHoldingCalls = new LinkedHashSet<>();
70        mCalls = new HashSet<>();
71        mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
72            put(CallState.CONNECTING, mActiveDialingOrConnectingCalls);
73            put(CallState.ACTIVE, mActiveDialingOrConnectingCalls);
74            put(CallState.DIALING, mActiveDialingOrConnectingCalls);
75            put(CallState.RINGING, mRingingCalls);
76            put(CallState.ON_HOLD, mHoldingCalls);
77        }};
78
79        mCallAudioRouteStateMachine = callAudioRouteStateMachine;
80        mCallAudioModeStateMachine = callAudioModeStateMachine;
81        mCallsManager = callsManager;
82        mPlayerFactory = playerFactory;
83        mRinger = ringer;
84        mRingbackPlayer = ringbackPlayer;
85        mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
86
87        mPlayerFactory.setCallAudioManager(this);
88        mCallAudioModeStateMachine.setCallAudioManager(this);
89    }
90
91    @Override
92    public void onCallStateChanged(Call call, int oldState, int newState) {
93        if (call.getParentCall() != null) {
94            // No audio management for calls in a conference.
95            return;
96        }
97        Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
98                CallState.toString(oldState), CallState.toString(newState));
99
100        if (mCallStateToCalls.get(oldState) != null) {
101            mCallStateToCalls.get(oldState).remove(call);
102        }
103        if (mCallStateToCalls.get(newState) != null) {
104            mCallStateToCalls.get(newState).add(call);
105        }
106
107        updateForegroundCall();
108        if (newState == CallState.DISCONNECTED) {
109            playToneForDisconnectedCall(call);
110        }
111
112        onCallLeavingState(call, oldState);
113        onCallEnteringState(call, newState);
114    }
115
116    @Override
117    public void onCallAdded(Call call) {
118        if (call.getParentCall() != null) {
119            return; // Don't do audio handling for calls in a conference.
120        }
121
122        if (mCalls.contains(call)) {
123            Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId());
124            return; // No guarantees that the same call won't get added twice.
125        }
126
127        Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(),
128                CallState.toString(call.getState()));
129
130        if (mCallStateToCalls.get(call.getState()) != null) {
131            mCallStateToCalls.get(call.getState()).add(call);
132        }
133        updateForegroundCall();
134        mCalls.add(call);
135
136        onCallEnteringState(call, call.getState());
137    }
138
139    @Override
140    public void onCallRemoved(Call call) {
141        if (call.getParentCall() != null) {
142            return; // Don't do audio handling for calls in a conference.
143        }
144
145        if (!mCalls.contains(call)) {
146            return; // No guarantees that the same call won't get removed twice.
147        }
148
149        Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
150                CallState.toString(call.getState()));
151
152        if (mCallStateToCalls.get(call.getState()) != null) {
153            mCallStateToCalls.get(call.getState()).remove(call);
154        }
155
156        updateForegroundCall();
157        mCalls.remove(call);
158
159        onCallLeavingState(call, call.getState());
160
161        if (mCallsManager.getCalls().isEmpty()) {
162            Log.v(this, "all calls removed, resetting system audio to default state");
163            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
164                    CallAudioRouteStateMachine.REINITIALIZE);
165        }
166    }
167
168    @Override
169    public void onIncomingCallAnswered(Call call) {
170        // This is called after the UI answers the call, but before the connection service
171        // sets the call to active. Only thing to handle for mode here is the audio speedup thing.
172
173        if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
174            if (mForegroundCall == call) {
175                Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " +
176                        "an active in-call audio state before connection service has " +
177                        "connected the call.");
178                if (mCallStateToCalls.get(call.getState()) != null) {
179                    mCallStateToCalls.get(call.getState()).remove(call);
180                }
181                mActiveDialingOrConnectingCalls.add(call);
182                mCallAudioModeStateMachine.sendMessage(
183                        CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
184                        makeArgsForModeStateMachine());
185            }
186        }
187
188        if (mRingingCalls.size() == 0) {
189            mRinger.stopRinging();
190            mRinger.stopCallWaiting();
191        }
192    }
193
194    @Override
195    public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
196        if (videoProfile == null) {
197            return;
198        }
199
200        if (call != mForegroundCall) {
201            // We only play tones for foreground calls.
202            return;
203        }
204
205        int previousVideoState = call.getVideoState();
206        int newVideoState = videoProfile.getVideoState();
207        Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
208                .videoStateToString(newVideoState));
209
210        boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
211                VideoProfile.isReceptionEnabled(newVideoState);
212
213        if (isUpgradeRequest) {
214            mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
215        }
216    }
217
218    /**
219     * Play or stop a call hold tone for a call.  Triggered via
220     * {@link Connection#sendConnectionEvent(String)} when the
221     * {@link Connection#EVENT_ON_HOLD_TONE_START} event or
222     * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the
223     *
224     * @param call The call which requested the hold tone.
225     */
226    @Override
227    public void onHoldToneRequested(Call call) {
228        maybePlayHoldTone();
229    }
230
231    @Override
232    public void onIsVoipAudioModeChanged(Call call) {
233        if (call != mForegroundCall) {
234            return;
235        }
236        mCallAudioModeStateMachine.sendMessage(
237                CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
238                makeArgsForModeStateMachine());
239    }
240
241    @Override
242    public void onRingbackRequested(Call call, boolean shouldRingback) {
243        if (call == mForegroundCall && shouldRingback) {
244            mRingbackPlayer.startRingbackForCall(call);
245        } else {
246            mRingbackPlayer.stopRingbackForCall(call);
247        }
248    }
249
250    @Override
251    public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
252        // This gets called after the UI rejects a call but before the CS processes the rejection.
253        // Will get called before the state change from ringing to not ringing.
254
255        if (mRingingCalls.size() == 0 || call == mRingingCalls.iterator().next()) {
256            mRinger.stopRinging();
257            mRinger.stopCallWaiting();
258        }
259    }
260
261    @Override
262    public void onIsConferencedChanged(Call call) {
263        // This indicates a conferencing change, which shouldn't impact any audio mode stuff.
264        Call parentCall = call.getParentCall();
265        if (parentCall == null) {
266            // Indicates that the call should be tracked for audio purposes. Treat it as if it were
267            // just added.
268            Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" +
269                            " now be tracked by CallAudioManager.");
270            onCallAdded(call);
271        } else {
272            // The call joined a conference, so stop tracking it.
273            if (mCallStateToCalls.get(call.getState()) != null) {
274                mCallStateToCalls.get(call.getState()).remove(call);
275            }
276
277            updateForegroundCall();
278            mCalls.remove(call);
279        }
280    }
281
282    public CallAudioState getCallAudioState() {
283        return mCallAudioRouteStateMachine.getCurrentCallAudioState();
284    }
285
286    public Call getPossiblyHeldForegroundCall() {
287        return mForegroundCall;
288    }
289
290    public Call getForegroundCall() {
291        if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
292            return mForegroundCall;
293        }
294        return null;
295    }
296
297    void toggleMute() {
298        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
299                CallAudioRouteStateMachine.TOGGLE_MUTE);
300    }
301
302    void mute(boolean shouldMute) {
303        Log.v(this, "mute, shouldMute: %b", shouldMute);
304
305        // Don't mute if there are any emergency calls.
306        if (mCallsManager.hasEmergencyCall()) {
307            shouldMute = false;
308            Log.v(this, "ignoring mute for emergency call");
309        }
310
311        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute
312                ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
313    }
314
315    /**
316     * Changed the audio route, for example from earpiece to speaker phone.
317     *
318     * @param route The new audio route to use. See {@link CallAudioState}.
319     */
320    void setAudioRoute(int route) {
321        Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
322        switch (route) {
323            case CallAudioState.ROUTE_BLUETOOTH:
324                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
325                        CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH);
326                return;
327            case CallAudioState.ROUTE_SPEAKER:
328                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
329                        CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);
330                return;
331            case CallAudioState.ROUTE_WIRED_HEADSET:
332                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
333                        CallAudioRouteStateMachine.USER_SWITCH_HEADSET);
334                return;
335            case CallAudioState.ROUTE_EARPIECE:
336                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
337                        CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
338                return;
339            case CallAudioState.ROUTE_WIRED_OR_EARPIECE:
340                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
341                        CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE);
342                return;
343            default:
344                Log.wtf(this, "Invalid route specified: %d", route);
345        }
346    }
347
348    void silenceRingers() {
349        for (Call call : mRingingCalls) {
350            call.silence();
351        }
352
353        mRingingCalls.clear();
354        mRinger.stopRinging();
355        mRinger.stopCallWaiting();
356        mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
357                makeArgsForModeStateMachine());
358    }
359
360    @VisibleForTesting
361    public void startRinging() {
362        mRinger.startRinging(mForegroundCall);
363    }
364
365    @VisibleForTesting
366    public void startCallWaiting() {
367        mRinger.startCallWaiting(mRingingCalls.iterator().next());
368    }
369
370    @VisibleForTesting
371    public void stopRinging() {
372        mRinger.stopRinging();
373    }
374
375    @VisibleForTesting
376    public void stopCallWaiting() {
377        mRinger.stopCallWaiting();
378    }
379
380    @VisibleForTesting
381    public void setCallAudioRouteFocusState(int focusState) {
382        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
383                CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
384    }
385
386    void dump(IndentingPrintWriter pw) {
387        pw.println("Active dialing, or connecting calls:");
388        pw.increaseIndent();
389        dumpCallsInCollection(pw, mActiveDialingOrConnectingCalls);
390        pw.decreaseIndent();
391
392        pw.println("Ringing calls:");
393        pw.increaseIndent();
394        dumpCallsInCollection(pw, mRingingCalls);
395        pw.decreaseIndent();
396
397        pw.println("Holding calls:");
398        pw.increaseIndent();
399        dumpCallsInCollection(pw, mHoldingCalls);
400        pw.decreaseIndent();
401
402        pw.println("Foreground call:");
403        pw.println(mForegroundCall);
404    }
405
406    @VisibleForTesting
407    public void setIsTonePlaying(boolean isTonePlaying) {
408        mIsTonePlaying = isTonePlaying;
409        mCallAudioModeStateMachine.sendMessage(
410                isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
411                        : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
412                makeArgsForModeStateMachine());
413    }
414
415    private void onCallLeavingState(Call call, int state) {
416        switch (state) {
417            case CallState.ACTIVE:
418            case CallState.CONNECTING:
419                onCallLeavingActiveDialingOrConnecting();
420                break;
421            case CallState.RINGING:
422                onCallLeavingRinging();
423                break;
424            case CallState.ON_HOLD:
425                onCallLeavingHold();
426                break;
427            case CallState.DIALING:
428                stopRingbackForCall(call);
429                onCallLeavingActiveDialingOrConnecting();
430        }
431    }
432
433    private void onCallEnteringState(Call call, int state) {
434        switch (state) {
435            case CallState.ACTIVE:
436            case CallState.CONNECTING:
437                onCallEnteringActiveDialingOrConnecting();
438                break;
439            case CallState.RINGING:
440                onCallEnteringRinging();
441                break;
442            case CallState.ON_HOLD:
443                onCallEnteringHold();
444                break;
445            case CallState.DIALING:
446                onCallEnteringActiveDialingOrConnecting();
447                playRingbackForCall(call);
448                break;
449        }
450    }
451
452    private void onCallLeavingActiveDialingOrConnecting() {
453        if (mActiveDialingOrConnectingCalls.size() == 0) {
454            mCallAudioModeStateMachine.sendMessage(
455                    CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
456                    makeArgsForModeStateMachine());
457        }
458    }
459
460    private void onCallLeavingRinging() {
461        if (mRingingCalls.size() == 0) {
462            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
463                    makeArgsForModeStateMachine());
464        }
465    }
466
467    private void onCallLeavingHold() {
468        if (mHoldingCalls.size() == 0) {
469            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
470                    makeArgsForModeStateMachine());
471        }
472    }
473
474    private void onCallEnteringActiveDialingOrConnecting() {
475        if (mActiveDialingOrConnectingCalls.size() == 1) {
476            mCallAudioModeStateMachine.sendMessage(
477                    CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
478                    makeArgsForModeStateMachine());
479        }
480    }
481
482    private void onCallEnteringRinging() {
483        if (mRingingCalls.size() == 1) {
484            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
485                    makeArgsForModeStateMachine());
486        }
487    }
488
489    private void onCallEnteringHold() {
490        if (mHoldingCalls.size() == 1) {
491            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL,
492                    makeArgsForModeStateMachine());
493        }
494    }
495
496    private void updateForegroundCall() {
497        Call oldForegroundCall = mForegroundCall;
498        if (mActiveDialingOrConnectingCalls.size() > 0) {
499            // Give preference for connecting calls over active/dialing for foreground-ness.
500            Call possibleConnectingCall = null;
501            for (Call call : mActiveDialingOrConnectingCalls) {
502                if (call.getState() == CallState.CONNECTING) {
503                    possibleConnectingCall = call;
504                }
505            }
506            mForegroundCall = possibleConnectingCall == null ?
507                    mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
508        } else if (mRingingCalls.size() > 0) {
509            mForegroundCall = mRingingCalls.iterator().next();
510        } else if (mHoldingCalls.size() > 0) {
511            mForegroundCall = mHoldingCalls.iterator().next();
512        } else {
513            mForegroundCall = null;
514        }
515
516        if (mForegroundCall != oldForegroundCall) {
517            mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
518            maybePlayHoldTone();
519        }
520    }
521
522    @NonNull
523    private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
524        return new CallAudioModeStateMachine.MessageArgs(
525                mActiveDialingOrConnectingCalls.size() > 0,
526                mRingingCalls.size() > 0,
527                mHoldingCalls.size() > 0,
528                mIsTonePlaying,
529                mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(),
530                Log.createSubsession());
531    }
532
533    private void playToneForDisconnectedCall(Call call) {
534        if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
535            Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
536                    " and there is another call.");
537            return;
538        }
539
540        if (call.getDisconnectCause() != null) {
541            int toneToPlay = InCallTonePlayer.TONE_INVALID;
542
543            Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
544
545            switch(call.getDisconnectCause().getTone()) {
546                case ToneGenerator.TONE_SUP_BUSY:
547                    toneToPlay = InCallTonePlayer.TONE_BUSY;
548                    break;
549                case ToneGenerator.TONE_SUP_CONGESTION:
550                    toneToPlay = InCallTonePlayer.TONE_CONGESTION;
551                    break;
552                case ToneGenerator.TONE_CDMA_REORDER:
553                    toneToPlay = InCallTonePlayer.TONE_REORDER;
554                    break;
555                case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
556                    toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
557                    break;
558                case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
559                    toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
560                    break;
561                case ToneGenerator.TONE_SUP_ERROR:
562                    toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
563                    break;
564                case ToneGenerator.TONE_PROP_PROMPT:
565                    toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
566                    break;
567            }
568
569            Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
570
571            if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
572                mPlayerFactory.createPlayer(toneToPlay).startTone();
573            }
574        }
575    }
576
577    private void playRingbackForCall(Call call) {
578        if (call == mForegroundCall && call.isRingbackRequested()) {
579            mRingbackPlayer.startRingbackForCall(call);
580        }
581    }
582
583    private void stopRingbackForCall(Call call) {
584        mRingbackPlayer.stopRingbackForCall(call);
585    }
586
587    /**
588     * Determines if a hold tone should be played and then starts or stops it accordingly.
589     */
590    private void maybePlayHoldTone() {
591        if (shouldPlayHoldTone()) {
592            if (mHoldTonePlayer == null) {
593                mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
594                mHoldTonePlayer.start();
595            }
596        } else {
597            if (mHoldTonePlayer != null) {
598                mHoldTonePlayer.stopTone();
599                mHoldTonePlayer = null;
600            }
601        }
602    }
603
604    /**
605     * Determines if a hold tone should be played.
606     * A hold tone should be played only if foreground call is equals with call which is
607     * remotely held.
608     *
609     * @return {@code true} if the the hold tone should be played, {@code false} otherwise.
610     */
611    private boolean shouldPlayHoldTone() {
612        Call foregroundCall = getForegroundCall();
613        // If there is no foreground call, no hold tone should play.
614        if (foregroundCall == null) {
615            return false;
616        }
617
618        // If another call is ringing, no hold tone should play.
619        if (mCallsManager.hasRingingCall()) {
620            return false;
621        }
622
623        // If the foreground call isn't active, no hold tone should play. This might happen, for
624        // example, if the user puts a remotely held call on hold itself.
625        if (!foregroundCall.isActive()) {
626            return false;
627        }
628
629        return foregroundCall.isRemotelyHeld();
630    }
631
632    private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
633        for (Call call : calls) {
634            if (call != null) pw.println(call.getId());
635        }
636    }
637}