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

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

Add call streaming related API.

Bug: 262412844
Test: build, cts test
Change-Id: Ib9013291ba5bac4f94fb2919fb2eccb8aa25acb8
parent b377c388
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -41086,6 +41086,7 @@ package android.telecom {
    field public static final int ROUTE_BLUETOOTH = 2; // 0x2
    field public static final int ROUTE_EARPIECE = 1; // 0x1
    field public static final int ROUTE_SPEAKER = 8; // 0x8
    field public static final int ROUTE_STREAMING = 16; // 0x10
    field public static final int ROUTE_WIRED_HEADSET = 4; // 0x4
    field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
  }
@@ -41097,6 +41098,7 @@ package android.telecom {
    method public void rejectCall(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
    method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
    method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
    method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
  }
  public final class CallEndpoint implements android.os.Parcelable {
@@ -41132,6 +41134,8 @@ package android.telecom {
  public interface CallEventCallback {
    method public void onAnswer(int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
    method public void onCallAudioStateChanged(@NonNull android.telecom.CallAudioState);
    method public void onCallStreamingFailed(int);
    method public void onCallStreamingStarted(@NonNull java.util.function.Consumer<java.lang.Boolean>);
    method public void onDisconnect(@NonNull java.util.function.Consumer<java.lang.Boolean>);
    method public void onReject(@NonNull java.util.function.Consumer<java.lang.Boolean>);
    method public void onSetActive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
@@ -41197,6 +41201,18 @@ package android.telecom {
    method public android.telecom.CallScreeningService.CallResponse.Builder setSkipNotification(boolean);
  }
  public abstract class CallStreamingService extends android.app.Service {
    ctor public CallStreamingService();
    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
    method public void onCallStreamingStarted(@NonNull android.telecom.StreamingCall);
    method public void onCallStreamingStateChanged(int);
    method public void onCallStreamingStopped();
    field public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";
    field public static final int STREAMING_FAILED_ALREADY_STREAMING = 1; // 0x1
    field public static final int STREAMING_FAILED_NO_SENDER = 2; // 0x2
    field public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3; // 0x3
  }
  public abstract class Conference extends android.telecom.Conferenceable {
    ctor public Conference(android.telecom.PhoneAccountHandle);
    method public final boolean addConnection(android.telecom.Connection);
@@ -41662,6 +41678,7 @@ package android.telecom {
    field public static final int CAPABILITY_RTT = 4096; // 0x1000
    field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
    field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
    field public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 524288; // 0x80000
    field public static final int CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS = 262144; // 0x40000
    field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
    field public static final int CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS = 65536; // 0x10000
@@ -41851,6 +41868,22 @@ package android.telecom {
    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StatusHints> CREATOR;
  }
  public final class StreamingCall implements android.os.Parcelable {
    ctor public StreamingCall(@NonNull android.content.ComponentName, @NonNull String, @NonNull android.net.Uri, @NonNull android.os.Bundle);
    method public int describeContents();
    method @NonNull public android.net.Uri getAddress();
    method @NonNull public android.content.ComponentName getComponentName();
    method @NonNull public String getDisplayName();
    method @NonNull public android.os.Bundle getExtras();
    method public int getState();
    method public void setStreamingState(int);
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR;
    field public static final int STATE_DISCONNECTED = 3; // 0x3
    field public static final int STATE_HOLDING = 2; // 0x2
    field public static final int STATE_STREAMING = 1; // 0x1
  }
  public class TelecomManager {
    method public void acceptHandover(android.net.Uri, int, android.telecom.PhoneAccountHandle);
    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall();
+12 −3
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.os.Parcelable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -58,6 +57,9 @@ public final class CallAudioState implements Parcelable {
    /** Direct the audio stream through the device's speakerphone. */
    public static final int ROUTE_SPEAKER       = 0x00000008;

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

    /**
     * Direct the audio stream through the device's earpiece or wired headset if one is
     * connected.
@@ -70,7 +72,7 @@ public final class CallAudioState implements Parcelable {
     * @hide
     **/
    public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |
            ROUTE_SPEAKER;
            ROUTE_SPEAKER | ROUTE_STREAMING;

    private final boolean isMuted;
    private final int route;
@@ -189,8 +191,12 @@ public final class CallAudioState implements Parcelable {
     */
    @CallAudioRoute
    public int getSupportedRouteMask() {
        if (route == ROUTE_STREAMING) {
            return ROUTE_STREAMING;
        } else {
            return supportedRouteMask;
        }
    }

    /**
     * @return The {@link BluetoothDevice} through which audio is being routed.
@@ -232,6 +238,9 @@ public final class CallAudioState implements Parcelable {
        if ((route & ROUTE_SPEAKER) == ROUTE_SPEAKER) {
            listAppend(buffer, "SPEAKER");
        }
        if ((route & ROUTE_STREAMING) == ROUTE_STREAMING) {
            listAppend(buffer, "STREAMING");
        }

        return buffer.toString();
    }
+32 −0
Original line number Diff line number Diff line
@@ -190,6 +190,38 @@ public final class CallControl implements AutoCloseable {
        }
    }

    /**
     * Request start a call streaming session. On receiving valid request, telecom will bind to
     * the {@link CallStreamingService} implemented by a general call streaming sender. So that the
     * call streaming sender can perform streaming local device audio to another remote device and
     * control the call during streaming.
     *
     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
     *                 will be called on.
     * @param callback that will be completed on the Telecom side that details success or failure
     *                 of the requested operation.
     *
     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
     *                 rejected the incoming call.
     *
     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to
     *                 reject the incoming call.  A {@link CallException} will be passed that
     *                 details why the operation failed.
     */
    public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
            @NonNull OutcomeReceiver<Void, CallException> callback) {
        if (mServerInterface != null) {
            try {
                mServerInterface.startCallStreaming(mCallId,
                        new CallControlResultReceiver("startCallStreaming", executor, callback));
            } catch (RemoteException e) {
                throw e.rethrowAsRuntimeException();
            }
        } else {
            throw new IllegalStateException(INTERFACE_ERROR_MSG);
        }
    }

    /**
     * This method should be called after
     * {@link CallControl#disconnect(DisconnectCause, Executor, OutcomeReceiver)} or
+18 −0
Original line number Diff line number Diff line
@@ -100,4 +100,22 @@ public interface CallEventCallback {
     * @param callAudioState that is currently being used
     */
    void onCallAudioStateChanged(@NonNull CallAudioState callAudioState);

    /**
     * Telecom is informing the client to set the call in streaming.
     *
     * @param wasCompleted The {@link Consumer} to be completed. If the client can stream the
     *                     call on their end, {@link Consumer#accept(Object)} should be called with
     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
     *                     should be called with {@link Boolean#FALSE}.
     */
    void onCallStreamingStarted(@NonNull Consumer<Boolean> wasCompleted);

    /**
     * Telecom is informing the client user requested call streaming but the stream can't be
     * started.
     *
     * @param reason Code to indicate the reason of this failure
     */
    void onCallStreamingFailed(@CallStreamingService.StreamingFailedReason int reason);
}
+184 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.telecom;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;

import androidx.annotation.Nullable;

import com.android.internal.telecom.ICallStreamingService;
import com.android.internal.telecom.IStreamingCallAdapter;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * This service is implemented by an app that wishes to provide functionality for a general call
 * streaming sender for voip calls.
 *
 * TODO: add doc of how to be the general streaming sender
 *
 */
public abstract class CallStreamingService extends Service {
    /**
     * The {@link android.content.Intent} that must be declared as handled by the service.
     */
    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
    public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";

    private static final int MSG_SET_STREAMING_CALL_ADAPTER = 1;
    private static final int MSG_CALL_STREAMING_STARTED = 2;
    private static final int MSG_CALL_STREAMING_STOPPED = 3;
    private static final int MSG_CALL_STREAMING_CHANGED_CHANGED = 4;

    /** Default Handler used to consolidate binder method calls onto a single thread. */
    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            if (mStreamingCallAdapter == null && msg.what != MSG_SET_STREAMING_CALL_ADAPTER) {
                return;
            }

            switch (msg.what) {
                case MSG_SET_STREAMING_CALL_ADAPTER:
                    mStreamingCallAdapter = new StreamingCallAdapter(
                            (IStreamingCallAdapter) msg.obj);
                    break;
                case MSG_CALL_STREAMING_STARTED:
                    mCall = (StreamingCall) msg.obj;
                    mCall.setAdapter(mStreamingCallAdapter);
                    CallStreamingService.this.onCallStreamingStarted(mCall);
                    break;
                case MSG_CALL_STREAMING_STOPPED:
                    mCall = null;
                    mStreamingCallAdapter = null;
                    CallStreamingService.this.onCallStreamingStopped();
                    break;
                case MSG_CALL_STREAMING_CHANGED_CHANGED:
                    int state = (int) msg.obj;
                    mCall.setStreamingState(state);
                    CallStreamingService.this.onCallStreamingStateChanged(state);
                    break;
                default:
                    break;
            }
        }
    };

    @Nullable
    @Override
    public IBinder onBind(@NonNull Intent intent) {
        return new CallStreamingServiceBinder();
    }

    /** Manages the binder calls so that the implementor does not need to deal with it. */
    private final class CallStreamingServiceBinder extends ICallStreamingService.Stub {
        @Override
        public void setStreamingCallAdapter(IStreamingCallAdapter streamingCallAdapter)
                throws RemoteException {
            mHandler.obtainMessage(MSG_SET_STREAMING_CALL_ADAPTER, mStreamingCallAdapter)
                    .sendToTarget();
        }

        @Override
        public void onCallStreamingStarted(StreamingCall call) throws RemoteException {
            mHandler.obtainMessage(MSG_CALL_STREAMING_STARTED, call).sendToTarget();
        }

        @Override
        public void onCallStreamingStopped() throws RemoteException {
            mHandler.obtainMessage(MSG_CALL_STREAMING_STOPPED).sendToTarget();
        }

        @Override
        public void onCallStreamingStateChanged(int state) throws RemoteException {
            mHandler.obtainMessage(MSG_CALL_STREAMING_CHANGED_CHANGED, state).sendToTarget();
        }
    }

    /**
     * Call streaming request reject reason used with
     * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
     * call streaming request because there's an ongoing streaming call on this device.
     */
    public static final int STREAMING_FAILED_ALREADY_STREAMING = 1;

    /**
     * Call streaming request reject reason used with
     * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
     * call streaming request because telecom can't find existing general streaming sender on this
     * device.
     */
    public static final int STREAMING_FAILED_NO_SENDER = 2;

    /**
     * Call streaming request reject reason used with
     * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
     * call streaming request because telecom can't bind to the general streaming sender app.
     */
    public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3;

    private StreamingCallAdapter mStreamingCallAdapter;
    private StreamingCall mCall;

    /**
     * @hide
     */
    @IntDef(prefix = {"STREAMING_FAILED"},
            value = {
                    STREAMING_FAILED_ALREADY_STREAMING,
                    STREAMING_FAILED_NO_SENDER,
                    STREAMING_FAILED_SENDER_BINDING_ERROR
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface StreamingFailedReason {};

    /**
     * Called when a {@code StreamingCall} has been added to this call streaming session. The call
     * streaming sender should start to intercept the device audio using audio records and audio
     * tracks from Audio frameworks.
     *
     * @param call a newly added {@code StreamingCall}.
     */
    public void onCallStreamingStarted(@NonNull StreamingCall call) {
    }

    /**
     * Called when a current {@code StreamingCall} has been removed from this call streaming
     * session. The call streaming sender should notify the streaming receiver that the call is
     * stopped streaming and stop the device audio interception.
     */
    public void onCallStreamingStopped() {
    }

    /**
     * Called when the streaming state of current {@code StreamingCall} changed. General streaming
     * sender usually get notified of the holding/unholding from the original owner voip app of the
     * call.
     */
    public void onCallStreamingStateChanged(@StreamingCall.StreamingCallState int state) {
    }
}
Loading