Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f0e883f1 authored by Grace Jia's avatar Grace Jia
Browse files

Implement call streaming related APIs.

Bug: 262412844
Test: Unit tests and CTS tests
Change-Id: I832b5f6a89a20824f8885cefe56803e0096fcee0
parent b9694ff9
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
@@ -156,6 +156,7 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        void onCallHoldFailed(Call call);
        void onCallSwitchFailed(Call call);
        void onConnectionEvent(Call call, String event, Bundle extras);
        void onCallStreamingStateChanged(Call call, boolean isStreaming);
        void onExternalCallChanged(Call call, boolean isExternalCall);
        void onRttInitiationFailure(Call call, int reason);
        void onRemoteRttRequest(Call call, int requestId);
@@ -243,6 +244,8 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
        @Override
        public void onConnectionEvent(Call call, String event, Bundle extras) {}
        @Override
        public void onCallStreamingStateChanged(Call call, boolean isStreaming) {}
        @Override
        public void onExternalCallChanged(Call call, boolean isExternalCall) {}
        @Override
        public void onRttInitiationFailure(Call call, int reason) {}
@@ -534,6 +537,11 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,

    private boolean mIsTransactionalCall = false;

    /**
     * Indicates whether this call is streaming.
     */
    private boolean mIsStreaming = false;

    /**
     * Indicates whether the {@link PhoneAccount} associated with an self-managed call want to
     * expose the call to an {@link android.telecom.InCallService} which declares the metadata
@@ -4390,4 +4398,43 @@ public class Call implements CreateConnectionResponse, EventManager.Loggable,
            mDisconnectFuture = null;
        }
    }

    public boolean isStreaming() {
        synchronized (mLock) {
            return mIsStreaming;
        }
    }

    public void startStreaming() {
        if (!mIsTransactionalCall) {
            throw new UnsupportedOperationException(
                    "Can't streaming call created by non voip apps");
        }

        synchronized (mLock) {
            if (mIsStreaming) {
                // ignore
                return;
            }

            mIsStreaming = true;
            for (Listener listener : mListeners) {
                listener.onCallStreamingStateChanged(this, true /** isStreaming */);
            }
        }
    }

    public void stopStreaming() {
        synchronized (mLock) {
            if (!mIsStreaming) {
                // ignore
                return;
            }

            mIsStreaming = false;
            for (Listener listener : mListeners) {
                listener.onCallStreamingStateChanged(this, false /** isStreaming */);
            }
        }
    }
}
+33 −0
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ public class CallAudioManager extends CallsManagerListenerBase {
    private final RingbackPlayer mRingbackPlayer;
    private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;

    private Call mStreamingCall;
    private Call mForegroundCall;
    private boolean mIsTonePlaying = false;
    private boolean mIsDisconnectedTonePlaying = false;
@@ -78,6 +79,7 @@ public class CallAudioManager extends CallsManagerListenerBase {
        mRingingCalls = new LinkedHashSet<>(1);
        mHoldingCalls = new LinkedHashSet<>(1);
        mAudioProcessingCalls = new LinkedHashSet<>(1);
        mStreamingCall = null;
        mCalls = new HashSet<>();
        mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
            put(CallState.CONNECTING, mActiveDialingOrConnectingCalls);
@@ -221,6 +223,36 @@ public class CallAudioManager extends CallsManagerListenerBase {
        }
    }

    /**
     * Handles the changes to the streaming state of a call.
     * @param call The call
     * @param isStreaming {@code true} if the call is streaming, {@code false} otherwise
     */
    @Override
    public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
        if (isStreaming) {
            if (mStreamingCall == null) {
                mStreamingCall = call;
                mCallAudioModeStateMachine.sendMessageWithArgs(
                        CallAudioModeStateMachine.START_CALL_STREAMING,
                        makeArgsForModeStateMachine());
            } else {
                Log.w(LOG_TAG, "Unexpected streaming call request for call %s while call "
                        + "s is streaming.", call.getId(), mStreamingCall.getId());
            }
        } else {
            if (mStreamingCall == call) {
                mStreamingCall = null;
                mCallAudioModeStateMachine.sendMessageWithArgs(
                        CallAudioModeStateMachine.STOP_CALL_STREAMING,
                        makeArgsForModeStateMachine());
            } else {
                Log.w(LOG_TAG, "Unexpected call streaming stop request for call %s while this call "
                        + "is not streaming.", call.getId());
            }
        }
    }

    /**
     * Determines if {@link CallAudioManager} should do any audio routing operations for a call.
     * We ignore child calls of a conference and external calls for audio routing purposes.
@@ -759,6 +791,7 @@ public class CallAudioManager extends CallsManagerListenerBase {
                .setHasHoldingCalls(mHoldingCalls.size() > 0)
                .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0)
                .setIsTonePlaying(mIsTonePlaying)
                .setIsStreaming(mStreamingCall != null)
                .setForegroundCallIsVoip(
                        mForegroundCall != null && isCallVoip(mForegroundCall))
                .setSession(Log.createSubsession()).build();
+114 −9
Original line number Diff line number Diff line
@@ -50,17 +50,19 @@ public class CallAudioModeStateMachine extends StateMachine {
        public boolean hasAudioProcessingCalls;
        public boolean isTonePlaying;
        public boolean foregroundCallIsVoip;
        public boolean isStreaming;
        public Session session;

        private MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls,
                boolean hasHoldingCalls, boolean hasAudioProcessingCalls, boolean isTonePlaying,
                boolean foregroundCallIsVoip, Session session) {
                boolean foregroundCallIsVoip, boolean isStreaming, Session session) {
            this.hasActiveOrDialingCalls = hasActiveOrDialingCalls;
            this.hasRingingCalls = hasRingingCalls;
            this.hasHoldingCalls = hasHoldingCalls;
            this.hasAudioProcessingCalls = hasAudioProcessingCalls;
            this.isTonePlaying = isTonePlaying;
            this.foregroundCallIsVoip = foregroundCallIsVoip;
            this.isStreaming = isStreaming;
            this.session = session;
        }

@@ -73,6 +75,7 @@ public class CallAudioModeStateMachine extends StateMachine {
                    ", hasAudioProcessingCalls=" + hasAudioProcessingCalls +
                    ", isTonePlaying=" + isTonePlaying +
                    ", foregroundCallIsVoip=" + foregroundCallIsVoip +
                    ", isStreaming=" + isStreaming +
                    ", session=" + session +
                    '}';
        }
@@ -84,6 +87,7 @@ public class CallAudioModeStateMachine extends StateMachine {
            private boolean mHasAudioProcessingCalls;
            private boolean mIsTonePlaying;
            private boolean mForegroundCallIsVoip;
            private boolean mIsStreaming;
            private Session mSession;

            public Builder setHasActiveOrDialingCalls(boolean hasActiveOrDialingCalls) {
@@ -121,9 +125,15 @@ public class CallAudioModeStateMachine extends StateMachine {
                return this;
            }

            public Builder setIsStreaming(boolean isStraeming) {
                mIsStreaming = isStraeming;
                return this;
            }

            public MessageArgs build() {
                return new MessageArgs(mHasActiveOrDialingCalls, mHasRingingCalls, mHasHoldingCalls,
                        mHasAudioProcessingCalls, mIsTonePlaying, mForegroundCallIsVoip, mSession);
                        mHasAudioProcessingCalls, mIsTonePlaying, mForegroundCallIsVoip,
                        mIsStreaming, mSession);
            }
        }
    }
@@ -138,7 +148,8 @@ public class CallAudioModeStateMachine extends StateMachine {
    public static final int ENTER_RING_FOCUS_FOR_TESTING = 4;
    public static final int ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING = 5;
    public static final int ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING = 6;
    public static final int ABANDON_FOCUS_FOR_TESTING = 7;
    public static final int ENTER_STREAMING_FOCUS_FOR_TESTING = 7;
    public static final int ABANDON_FOCUS_FOR_TESTING = 8;

    public static final int NO_MORE_ACTIVE_OR_DIALING_CALLS = 1001;
    public static final int NO_MORE_RINGING_CALLS = 1002;
@@ -161,6 +172,9 @@ public class CallAudioModeStateMachine extends StateMachine {
    // to release focus for other apps to take over.
    public static final int AUDIO_OPERATIONS_COMPLETE = 6001;

    public static final int START_CALL_STREAMING = 7001;
    public static final int STOP_CALL_STREAMING = 7002;

    public static final int RUN_RUNNABLE = 9001;

    private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
@@ -183,6 +197,8 @@ public class CallAudioModeStateMachine extends StateMachine {
        put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
        put(RINGER_MODE_CHANGE, "RINGER_MODE_CHANGE");
        put(AUDIO_OPERATIONS_COMPLETE, "AUDIO_OPERATIONS_COMPLETE");
        put(START_CALL_STREAMING, "START_CALL_STREAMING");
        put(STOP_CALL_STREAMING, "STOP_CALL_STREAMING");

        put(RUN_RUNNABLE, "RUN_RUNNABLE");
    }};
@@ -193,6 +209,7 @@ public class CallAudioModeStateMachine extends StateMachine {
            AudioProcessingFocusState.class.getSimpleName();
    public static final String CALL_STATE_NAME = SimCallFocusState.class.getSimpleName();
    public static final String RING_STATE_NAME = RingingFocusState.class.getSimpleName();
    public static final String STREAMING_STATE_NAME = StreamingFocusState.class.getSimpleName();
    public static final String COMMS_STATE_NAME = VoipCallFocusState.class.getSimpleName();

    private class BaseState extends State {
@@ -214,6 +231,9 @@ public class CallAudioModeStateMachine extends StateMachine {
                case ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING:
                    transitionTo(mAudioProcessingFocusState);
                    return HANDLED;
                case ENTER_STREAMING_FOCUS_FOR_TESTING:
                    transitionTo(mStreamingFocusState);
                    return HANDLED;
                case ABANDON_FOCUS_FOR_TESTING:
                    transitionTo(mUnfocusedState);
                    return HANDLED;
@@ -280,6 +300,9 @@ public class CallAudioModeStateMachine extends StateMachine {
                            " Args are: \n" + args.toString());
                    transitionTo(mOtherFocusState);
                    return HANDLED;
                case START_CALL_STREAMING:
                    transitionTo(mStreamingFocusState);
                    return HANDLED;
                case TONE_STARTED_PLAYING:
                    // This shouldn't happen either, but perform the action anyway.
                    Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
@@ -353,6 +376,9 @@ public class CallAudioModeStateMachine extends StateMachine {
                    Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
                            + args.toString());
                    return HANDLED;
                case START_CALL_STREAMING:
                    transitionTo(mStreamingFocusState);
                    return HANDLED;
                case AUDIO_OPERATIONS_COMPLETE:
                    Log.i(LOG_TAG, "Abandoning audio focus: now AUDIO_PROCESSING");
                    mAudioManager.abandonAudioFocusForCall();
@@ -625,6 +651,79 @@ public class CallAudioModeStateMachine extends StateMachine {
                    Log.w(LOG_TAG, "Should not be seeing AUDIO_OPERATIONS_COMPLETE in a focused"
                            + " state");
                    return HANDLED;
                case START_CALL_STREAMING:
                    transitionTo(mStreamingFocusState);
                    return HANDLED;
                default:
                    // The forced focus switch commands are handled by BaseState.
                    return NOT_HANDLED;
            }
        }
    }

    private class StreamingFocusState extends BaseState {
        @Override
        public void enter() {
            Log.i(LOG_TAG, "Audio focus entering streaming state");
            mAudioManager.setMode(AudioManager.MODE_CALL_REDIRECT);
            mMostRecentMode = AudioManager.MODE_NORMAL;
            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
            mCallAudioManager.getCallAudioRouteStateMachine().sendMessageWithSessionInfo(
                    CallAudioRouteStateMachine.STREAMING_FORCE_ENABLED);
        }

        private void preExit() {
            mCallAudioManager.getCallAudioRouteStateMachine().sendMessageWithSessionInfo(
                    CallAudioRouteStateMachine.STREAMING_FORCE_DISABLED);
        }

        @Override
        public boolean processMessage(Message msg) {
            if (super.processMessage(msg) == HANDLED) {
                return HANDLED;
            }
            MessageArgs args = (MessageArgs) msg.obj;
            switch (msg.what) {
                case NO_MORE_ACTIVE_OR_DIALING_CALLS:
                    // Do nothing.
                    return HANDLED;
                case NO_MORE_RINGING_CALLS:
                    // Do nothing.
                    return HANDLED;
                case NO_MORE_HOLDING_CALLS:
                    // Do nothing.
                    return HANDLED;
                case NO_MORE_AUDIO_PROCESSING_CALLS:
                    // Do nothing.
                    return HANDLED;
                case NEW_ACTIVE_OR_DIALING_CALL:
                    // Only possible for emergency call
                    BaseState destState = calculateProperStateFromArgs(args);
                    if (destState != this) {
                        preExit();
                        transitionTo(destState);
                    }
                    return HANDLED;
                case NEW_RINGING_CALL:
                    // Only possible for emergency call
                    preExit();
                    transitionTo(mRingingFocusState);
                    return HANDLED;
                case NEW_HOLDING_CALL:
                    // Do nothing.
                    return HANDLED;
                case NEW_AUDIO_PROCESSING_CALL:
                    // Do nothing.
                    return HANDLED;
                case START_CALL_STREAMING:
                    // Can happen as a duplicate message
                    return HANDLED;
                case TONE_STARTED_PLAYING:
                    // Do nothing.
                    return HANDLED;
                case STOP_CALL_STREAMING:
                    transitionTo(calculateProperStateFromArgs(args));
                    return HANDLED;
                default:
                    // The forced focus switch commands are handled by BaseState.
                    return NOT_HANDLED;
@@ -707,6 +806,7 @@ public class CallAudioModeStateMachine extends StateMachine {
    private final BaseState mSimCallFocusState = new SimCallFocusState();
    private final BaseState mVoipCallFocusState = new VoipCallFocusState();
    private final BaseState mAudioProcessingFocusState = new AudioProcessingFocusState();
    private final BaseState mStreamingFocusState = new StreamingFocusState();
    private final BaseState mOtherFocusState = new OtherFocusState();

    private final AudioManager mAudioManager;
@@ -745,6 +845,7 @@ public class CallAudioModeStateMachine extends StateMachine {
        addState(mSimCallFocusState);
        addState(mVoipCallFocusState);
        addState(mAudioProcessingFocusState);
        addState(mStreamingFocusState);
        addState(mOtherFocusState);
        setInitialState(mUnfocusedState);
        start();
@@ -754,6 +855,7 @@ public class CallAudioModeStateMachine extends StateMachine {
                .setHasHoldingCalls(false)
                .setIsTonePlaying(false)
                .setForegroundCallIsVoip(false)
                .setIsStreaming(false)
                .setSession(Log.createSubsession())
                .build());
    }
@@ -807,12 +909,15 @@ public class CallAudioModeStateMachine extends StateMachine {
        // switch to the appropriate focus.
        // Otherwise abandon focus.

        // The order matters here. If there are active calls, holding focus for them takes priority.
        // After that, we want to prioritize holding calls over ringing calls so that when a
        // call-waiting call gets answered, there's no transition in and out of the ringing focus
        // state. After that, we want tones since we actually hold focus during them, then the
        // audio processing state because that will release focus.
        if (args.hasActiveOrDialingCalls) {
        // The order matters here. If there is streaming call, holding streaming route for them
        // takes priority. After that, holding focus for active calls takes priority. After that, we
        // want to prioritize holding calls over ringing calls so that when a call-waiting call gets
        // answered, there's no transition in and out of the ringing focus state. After that, we
        // want tones since we actually hold focus during them, then the audio processing state
        // because that will release focus.
        if (args.isStreaming) {
            return mSimCallFocusState;
        } else if (args.hasActiveOrDialingCalls) {
            if (args.foregroundCallIsVoip) {
                return mVoipCallFocusState;
            } else {
+81 −0
Original line number Diff line number Diff line
@@ -108,6 +108,9 @@ public class CallAudioRouteStateMachine extends StateMachine {
    /** Direct the audio stream through the device's speakerphone. */
    public static final int ROUTE_SPEAKER       = CallAudioState.ROUTE_SPEAKER;

    /** Direct the audio stream through another device. */
    public static final int ROUTE_STREAMING     = CallAudioState.ROUTE_STREAMING;

    /** Valid values for msg.what */
    public static final int CONNECT_WIRED_HEADSET = 1;
    public static final int DISCONNECT_WIRED_HEADSET = 2;
@@ -129,6 +132,10 @@ public class CallAudioRouteStateMachine extends StateMachine {
    public static final int SPEAKER_ON = 1006;
    public static final int SPEAKER_OFF = 1007;

    // Messages denoting that the streaming route switch request was sent.
    public static final int STREAMING_FORCE_ENABLED = 1008;
    public static final int STREAMING_FORCE_DISABLED = 1009;

    public static final int USER_SWITCH_EARPIECE = 1101;
    public static final int USER_SWITCH_BLUETOOTH = 1102;
    public static final int USER_SWITCH_HEADSET = 1103;
@@ -545,6 +552,8 @@ public class CallAudioRouteStateMachine extends StateMachine {
                case DISCONNECT_DOCK:
                    // Nothing to do here
                    return HANDLED;
                case STREAMING_FORCE_ENABLED:
                    transitionTo(mStreamingState);
                default:
                    return NOT_HANDLED;
            }
@@ -629,6 +638,9 @@ public class CallAudioRouteStateMachine extends StateMachine {
                        mCallAudioManager.notifyAudioOperationsComplete();
                    }
                    return HANDLED;
                case STREAMING_FORCE_ENABLED:
                    transitionTo(mStreamingState);
                    return HANDLED;
                default:
                    return NOT_HANDLED;
            }
@@ -1106,6 +1118,9 @@ public class CallAudioRouteStateMachine extends StateMachine {
                case DISCONNECT_DOCK:
                    // Nothing to do here
                    return HANDLED;
                case STREAMING_FORCE_ENABLED:
                    transitionTo(mStreamingState);
                    return HANDLED;
                default:
                    return NOT_HANDLED;
            }
@@ -1331,6 +1346,68 @@ public class CallAudioRouteStateMachine extends StateMachine {
                case DISCONNECT_DOCK:
                    sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
                    return HANDLED;
                case STREAMING_FORCE_ENABLED:
                    transitionTo(mStreamingState);
                    return HANDLED;
               default:
                    return NOT_HANDLED;
            }
        }
    }

    class StreamingState extends AudioState {
        @Override
        public void enter() {
            super.enter();
            updateSystemAudioState();
        }

        @Override
        public void updateSystemAudioState() {
            updateInternalCallAudioState();
            setSystemAudioState(mCurrentCallAudioState);
        }

        @Override
        public boolean isActive() {
            return true;
        }

        @Override
        public int getRouteCode() {
            return CallAudioState.ROUTE_STREAMING;
        }

        @Override
        public boolean processMessage(Message msg) {
            if (super.processMessage(msg) == HANDLED) {
                return HANDLED;
            }
            switch (msg.what) {
                case SWITCH_EARPIECE:
                case USER_SWITCH_EARPIECE:
                case SPEAKER_OFF:
                    // Nothing to do here
                    return HANDLED;
                case SPEAKER_ON:
                    // fall through
                case BT_AUDIO_CONNECTED:
                case SWITCH_BLUETOOTH:
                case USER_SWITCH_BLUETOOTH:
                case SWITCH_HEADSET:
                case USER_SWITCH_HEADSET:
                case SWITCH_SPEAKER:
                case USER_SWITCH_SPEAKER:
                    return HANDLED;
                case SWITCH_FOCUS:
                    if (msg.arg1 == NO_FOCUS) {
                        reinitialize();
                        mCallAudioManager.notifyAudioOperationsComplete();
                    }
                    return HANDLED;
                case STREAMING_FORCE_DISABLED:
                    reinitialize();
                    return HANDLED;
                default:
                    return NOT_HANDLED;
            }
@@ -1399,6 +1476,7 @@ public class CallAudioRouteStateMachine extends StateMachine {
    private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
    private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
    private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
    private final StreamingState mStreamingState = new StreamingState();

    /**
     * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
@@ -1495,6 +1573,7 @@ public class CallAudioRouteStateMachine extends StateMachine {
        addState(mQuiescentHeadsetRoute);
        addState(mQuiescentBluetoothRoute);
        addState(mQuiescentSpeakerRoute);
        addState(mStreamingState);


        mStateNameToRouteCode = new HashMap<>(8);
@@ -1507,12 +1586,14 @@ public class CallAudioRouteStateMachine extends StateMachine {
        mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
        mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
        mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER);
        mStateNameToRouteCode.put(mStreamingState.getName(), ROUTE_STREAMING);

        mRouteCodeToQuiescentState = new HashMap<>(4);
        mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute);
        mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute);
        mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute);
        mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute);
        mRouteCodeToQuiescentState.put(ROUTE_STREAMING, mStreamingState);
    }

    public void setCallAudioManager(CallAudioManager callAudioManager) {
+378 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading