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

Commit 08ddbc2b authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Add support for DTMF and RTP header extension communications.

Add support for:
- reporting of incoming DTMF tones from IMS stack.
- incoming/outgoing RTP header extension data.

Test: Added unit tests where possible.
Test: Added test intents to inject test data into framework for platform
testing.
Bug: 163085177
Change-Id: If34faeba0461c677a1381c82ead4a79c607bcf13
parent 730f3532
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -11764,6 +11764,7 @@ package android.telephony.ims {
    ctor public ImsCallProfile(int, int);
    ctor public ImsCallProfile(int, int, android.os.Bundle, android.telephony.ims.ImsStreamMediaProfile);
    method public int describeContents();
    method @NonNull public java.util.Set<android.telephony.ims.RtpHeaderExtensionType> getAcceptedRtpHeaderExtensionTypes();
    method public String getCallExtra(String);
    method public String getCallExtra(String, String);
    method public boolean getCallExtraBoolean(String);
@@ -11778,6 +11779,7 @@ package android.telephony.ims {
    method public int getEmergencyServiceCategories();
    method @NonNull public java.util.List<java.lang.String> getEmergencyUrns();
    method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile();
    method @NonNull public java.util.Set<android.telephony.ims.RtpHeaderExtensionType> getOfferedRtpHeaderExtensionTypes();
    method @NonNull public android.os.Bundle getProprietaryCallExtras();
    method public int getRestrictCause();
    method public int getServiceType();
@@ -11788,6 +11790,7 @@ package android.telephony.ims {
    method public boolean isVideoCall();
    method public boolean isVideoPaused();
    method public static int presentationToOir(int);
    method public void setAcceptedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>);
    method public void setCallExtra(String, String);
    method public void setCallExtraBoolean(String, boolean);
    method public void setCallExtraInt(String, int);
@@ -11798,6 +11801,7 @@ package android.telephony.ims {
    method public void setEmergencyServiceCategories(int);
    method public void setEmergencyUrns(@NonNull java.util.List<java.lang.String>);
    method public void setHasKnownUserIntentEmergency(boolean);
    method public void setOfferedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>);
    method public void updateCallExtras(android.telephony.ims.ImsCallProfile);
    method public void updateCallType(android.telephony.ims.ImsCallProfile);
    method public void updateMediaProfile(android.telephony.ims.ImsCallProfile);
@@ -11857,6 +11861,7 @@ package android.telephony.ims {
    method public void callSessionConferenceExtendReceived(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
    method public void callSessionConferenceExtended(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
    method public void callSessionConferenceStateUpdated(android.telephony.ims.ImsConferenceState);
    method public void callSessionDtmfReceived(char);
    method @Deprecated public void callSessionHandover(int, int, android.telephony.ims.ImsReasonInfo);
    method @Deprecated public void callSessionHandoverFailed(int, int, android.telephony.ims.ImsReasonInfo);
    method public void callSessionHeld(android.telephony.ims.ImsCallProfile);
@@ -11877,6 +11882,7 @@ package android.telephony.ims {
    method public void callSessionResumeFailed(android.telephony.ims.ImsReasonInfo);
    method public void callSessionResumeReceived(android.telephony.ims.ImsCallProfile);
    method public void callSessionResumed(android.telephony.ims.ImsCallProfile);
    method public void callSessionRtpHeaderExtensionsReceived(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtension>);
    method public void callSessionRttAudioIndicatorChanged(@NonNull android.telephony.ims.ImsStreamMediaProfile);
    method public void callSessionRttMessageReceived(String);
    method public void callSessionRttModifyRequestReceived(android.telephony.ims.ImsCallProfile);
@@ -12231,6 +12237,24 @@ package android.telephony.ims {
    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
  }
  public final class RtpHeaderExtension implements android.os.Parcelable {
    ctor public RtpHeaderExtension(@IntRange(from=1, to=14) int, @NonNull byte[]);
    method public int describeContents();
    method @NonNull public byte[] getExtensionData();
    method @IntRange(from=1, to=14) public int getLocalIdentifier();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RtpHeaderExtension> CREATOR;
  }
  public final class RtpHeaderExtensionType implements android.os.Parcelable {
    ctor public RtpHeaderExtensionType(@IntRange(from=1, to=14) int, @NonNull android.net.Uri);
    method public int describeContents();
    method @IntRange(from=1, to=14) public int getLocalIdentifier();
    method @NonNull public android.net.Uri getUri();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RtpHeaderExtensionType> CREATOR;
  }
  public class SipDelegateManager {
    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSupported() throws android.telephony.ims.ImsException;
  }
@@ -12347,6 +12371,7 @@ package android.telephony.ims.stub {
    method public void removeParticipants(String[]);
    method public void resume(android.telephony.ims.ImsStreamMediaProfile);
    method public void sendDtmf(char, android.os.Message);
    method public void sendRtpHeaderExtensions(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtension>);
    method public void sendRttMessage(String);
    method public void sendRttModifyRequest(android.telephony.ims.ImsCallProfile);
    method public void sendRttModifyResponse(boolean);
+25 −0
Original line number Diff line number Diff line
@@ -10646,6 +10646,7 @@ package android.telephony.ims {
    ctor public ImsCallProfile(int, int);
    ctor public ImsCallProfile(int, int, android.os.Bundle, android.telephony.ims.ImsStreamMediaProfile);
    method public int describeContents();
    method @NonNull public java.util.Set<android.telephony.ims.RtpHeaderExtensionType> getAcceptedRtpHeaderExtensionTypes();
    method public String getCallExtra(String);
    method public String getCallExtra(String, String);
    method public boolean getCallExtraBoolean(String);
@@ -10660,6 +10661,7 @@ package android.telephony.ims {
    method public int getEmergencyServiceCategories();
    method @NonNull public java.util.List<java.lang.String> getEmergencyUrns();
    method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile();
    method @NonNull public java.util.Set<android.telephony.ims.RtpHeaderExtensionType> getOfferedRtpHeaderExtensionTypes();
    method @NonNull public android.os.Bundle getProprietaryCallExtras();
    method public int getRestrictCause();
    method public int getServiceType();
@@ -10670,6 +10672,7 @@ package android.telephony.ims {
    method public boolean isVideoCall();
    method public boolean isVideoPaused();
    method public static int presentationToOir(int);
    method public void setAcceptedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>);
    method public void setCallExtra(String, String);
    method public void setCallExtraBoolean(String, boolean);
    method public void setCallExtraInt(String, int);
@@ -10680,6 +10683,7 @@ package android.telephony.ims {
    method public void setEmergencyServiceCategories(int);
    method public void setEmergencyUrns(@NonNull java.util.List<java.lang.String>);
    method public void setHasKnownUserIntentEmergency(boolean);
    method public void setOfferedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>);
    method public void updateCallExtras(android.telephony.ims.ImsCallProfile);
    method public void updateCallType(android.telephony.ims.ImsCallProfile);
    method public void updateMediaProfile(android.telephony.ims.ImsCallProfile);
@@ -10739,6 +10743,7 @@ package android.telephony.ims {
    method public void callSessionConferenceExtendReceived(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
    method public void callSessionConferenceExtended(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile);
    method public void callSessionConferenceStateUpdated(android.telephony.ims.ImsConferenceState);
    method public void callSessionDtmfReceived(char);
    method @Deprecated public void callSessionHandover(int, int, android.telephony.ims.ImsReasonInfo);
    method @Deprecated public void callSessionHandoverFailed(int, int, android.telephony.ims.ImsReasonInfo);
    method public void callSessionHeld(android.telephony.ims.ImsCallProfile);
@@ -10759,6 +10764,7 @@ package android.telephony.ims {
    method public void callSessionResumeFailed(android.telephony.ims.ImsReasonInfo);
    method public void callSessionResumeReceived(android.telephony.ims.ImsCallProfile);
    method public void callSessionResumed(android.telephony.ims.ImsCallProfile);
    method public void callSessionRtpHeaderExtensionsReceived(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtension>);
    method public void callSessionRttAudioIndicatorChanged(@NonNull android.telephony.ims.ImsStreamMediaProfile);
    method public void callSessionRttMessageReceived(String);
    method public void callSessionRttModifyRequestReceived(android.telephony.ims.ImsCallProfile);
@@ -11113,6 +11119,24 @@ package android.telephony.ims {
    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
  }
  public final class RtpHeaderExtension implements android.os.Parcelable {
    ctor public RtpHeaderExtension(@IntRange(from=1, to=14) int, @NonNull byte[]);
    method public int describeContents();
    method @NonNull public byte[] getExtensionData();
    method @IntRange(from=1, to=14) public int getLocalIdentifier();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RtpHeaderExtension> CREATOR;
  }
  public final class RtpHeaderExtensionType implements android.os.Parcelable {
    ctor public RtpHeaderExtensionType(@IntRange(from=1, to=14) int, @NonNull android.net.Uri);
    method public int describeContents();
    method @IntRange(from=1, to=14) public int getLocalIdentifier();
    method @NonNull public android.net.Uri getUri();
    method public void writeToParcel(@NonNull android.os.Parcel, int);
    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RtpHeaderExtensionType> CREATOR;
  }
  public class SipDelegateManager {
    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSupported() throws android.telephony.ims.ImsException;
  }
@@ -11229,6 +11253,7 @@ package android.telephony.ims.stub {
    method public void removeParticipants(String[]);
    method public void resume(android.telephony.ims.ImsStreamMediaProfile);
    method public void sendDtmf(char, android.os.Message);
    method public void sendRtpHeaderExtensions(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtension>);
    method public void sendRttMessage(String);
    method public void sendRttModifyRequest(android.telephony.ims.ImsCallProfile);
    method public void sendRttModifyResponse(boolean);
+81 −1
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import android.telecom.VideoProfile;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting;
import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories;
import android.telephony.ims.feature.MmTelFeature;
import android.util.ArraySet;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
@@ -37,7 +39,10 @@ import com.android.internal.telephony.util.TelephonyUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * A Parcelable object to handle the IMS call profile, which provides the service, call type, and
@@ -444,6 +449,10 @@ public final class ImsCallProfile implements Parcelable {
    /** Indicates if we have known the intent of the user for the call is emergency */
    private boolean mHasKnownUserIntentEmergency = false;

    private Set<RtpHeaderExtensionType> mOfferedRtpHeaderExtensionTypes = new ArraySet<>();

    private Set<RtpHeaderExtensionType> mAcceptedRtpHeaderExtensionTypes = new ArraySet<>();

    /**
     * Extras associated with this {@link ImsCallProfile}.
     * <p>
@@ -682,6 +691,8 @@ public final class ImsCallProfile implements Parcelable {
        out.writeBoolean(mHasKnownUserIntentEmergency);
        out.writeInt(mRestrictCause);
        out.writeInt(mCallerNumberVerificationStatus);
        out.writeArray(mOfferedRtpHeaderExtensionTypes.toArray());
        out.writeArray(mAcceptedRtpHeaderExtensionTypes.toArray());
    }

    private void readFromParcel(Parcel in) {
@@ -696,9 +707,16 @@ public final class ImsCallProfile implements Parcelable {
        mHasKnownUserIntentEmergency = in.readBoolean();
        mRestrictCause = in.readInt();
        mCallerNumberVerificationStatus = in.readInt();
        Object[] offered = in.readArray(RtpHeaderExtensionType.class.getClassLoader());
        mOfferedRtpHeaderExtensionTypes = Arrays.stream(offered)
                .map(o -> (RtpHeaderExtensionType) o).collect(Collectors.toSet());
        Object[] accepted = in.readArray(RtpHeaderExtensionType.class.getClassLoader());
        mAcceptedRtpHeaderExtensionTypes = Arrays.stream(accepted)
                .map(o -> (RtpHeaderExtensionType) o).collect(Collectors.toSet());
    }

    public static final @android.annotation.NonNull Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() {
    public static final @android.annotation.NonNull Creator<ImsCallProfile> CREATOR =
            new Creator<ImsCallProfile>() {
        @Override
        public ImsCallProfile createFromParcel(Parcel in) {
            return new ImsCallProfile(in);
@@ -1085,4 +1103,66 @@ public final class ImsCallProfile implements Parcelable {
    public boolean hasKnownUserIntentEmergency() {
        return mHasKnownUserIntentEmergency;
    }

    /**
     * For an incoming or outgoing call, indicates the {@link RtpHeaderExtensionType}s which the
     * caller is offering to make available.
     * <p>
     * For outgoing calls, an {@link ImsService} will reserve
     * {@link RtpHeaderExtensionType#getLocalIdentifier()} identifiers the telephony stack has
     * proposed to use and not use these same local identifiers.  The offered header extension
     * types for an outgoing call can be found in the
     * {@link ImsCallProfile#getOfferedRtpHeaderExtensionTypes()} and will be available to the
     * {@link ImsService} in {@link MmTelFeature#createCallSession(ImsCallProfile)}.
     * The {@link ImsService} sets the accepted {@link #setAcceptedRtpHeaderExtensionTypes(Set)}
     * when the SDP offer/accept process has completed.
     * <p>
     * According to RFC8285, RTP header extensions available to a call are determined using the
     * offer/accept phase of the SDP protocol (see RFC4566).
     *
     * @return the {@link RtpHeaderExtensionType}s which were offered by other end of the call.
     */
    public @NonNull Set<RtpHeaderExtensionType> getOfferedRtpHeaderExtensionTypes() {
        return mOfferedRtpHeaderExtensionTypes;
    }

    /**
     * Sets the offered {@link RtpHeaderExtensionType}s for this call.
     * <p>
     * According to RFC8285, RTP header extensions available to a call are determined using the
     * offer/accept phase of the SDP protocol (see RFC4566).
     *
     * @param rtpHeaderExtensions the {@link RtpHeaderExtensionType}s which are offered.
     */
    public void setOfferedRtpHeaderExtensionTypes(@NonNull Set<RtpHeaderExtensionType>
                    rtpHeaderExtensions) {
        mOfferedRtpHeaderExtensionTypes.clear();
        mOfferedRtpHeaderExtensionTypes.addAll(rtpHeaderExtensions);
    }

    /**
     * Gets the {@link RtpHeaderExtensionType}s which have been accepted by both ends of the call.
     * <p>
     * According to RFC8285, RTP header extensions available to a call are determined using the
     * offer/accept phase of the SDP protocol (see RFC4566).
     *
     * @return the {@link RtpHeaderExtensionType}s which were accepted by the other end of the call.
     */
    public @NonNull Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensionTypes() {
        return mAcceptedRtpHeaderExtensionTypes;
    }

    /**
     * Sets the accepted {@link RtpHeaderExtensionType}s for this call.
     * <p>
     * According to RFC8285, RTP header extensions available to a call are determined using the
     * offer/accept phase of the SDP protocol (see RFC4566).
     *
     * @param rtpHeaderExtensions
     */
    public void setAcceptedRtpHeaderExtensionTypes(@NonNull Set<RtpHeaderExtensionType>
            rtpHeaderExtensions) {
        mAcceptedRtpHeaderExtensionTypes.clear();
        mAcceptedRtpHeaderExtensionTypes.addAll(rtpHeaderExtensions);
    }
}
+74 −0
Original line number Diff line number Diff line
@@ -22,11 +22,16 @@ import android.os.Message;
import android.os.RemoteException;
import android.telephony.CallQuality;
import android.telephony.ims.aidl.IImsCallSessionListener;
import android.util.ArraySet;
import android.util.Log;

import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsVideoCallProvider;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Provides the call initiation/termination, and media exchange between two IMS endpoints.
 * It directly communicates with IMS service which implements the IMS protocol behavior.
@@ -467,12 +472,32 @@ public class ImsCallSession {
            // no-op
        }

        /**
         * Informs the framework of a DTMF digit which was received from the network.
         * <p>
         * According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833 sec 3.10</a>,
         * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to
         * 12 ~ 15.
         * @param digit the DTMF digit
         */
        public void callSessionDtmfReceived(char digit) {
            // no-op
        }

        /**
         * Called when the IMS service reports a change to the call quality.
         */
        public void callQualityChanged(CallQuality callQuality) {
            // no-op
        }

        /**
         * Called when the IMS service reports incoming RTP header extension data.
         */
        public void callSessionRtpHeaderExtensionsReceived(
                @NonNull Set<RtpHeaderExtension> extensions) {
            // no-op
        }
    }

    private final IImsCallSession miSession;
@@ -1118,6 +1143,31 @@ public class ImsCallSession {
        }
    }

    /**
     * Requests that {@code rtpHeaderExtensions} are sent as a header extension with the next
     * RTP packet sent by the IMS stack.
     * <p>
     * The {@link RtpHeaderExtensionType}s negotiated during SDP (Session Description Protocol)
     * signalling determine the {@link RtpHeaderExtension}s which can be sent using this method.
     * See RFC8285 for more information.
     * <p>
     * By specification, the RTP header extension is an unacknowledged transmission and there is no
     * guarantee that the header extension will be delivered by the network to the other end of the
     * call.
     * @param rtpHeaderExtensions The header extensions to be included in the next RTP header.
     */
    public void sendRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
        if (mClosed) {
            return;
        }

        try {
            miSession.sendRtpHeaderExtensions(
                    new ArrayList<RtpHeaderExtension>(rtpHeaderExtensions));
        } catch (RemoteException e) {
        }
    }

    /**
     * A listener type for receiving notification on IMS call session events.
     * When an event is generated for an {@link IImsCallSession},
@@ -1476,6 +1526,17 @@ public class ImsCallSession {
            }
        }

        /**
         * DTMF digit received.
         * @param dtmf The DTMF digit.
         */
        @Override
        public void callSessionDtmfReceived(char dtmf) {
            if (mListener != null) {
                mListener.callSessionDtmfReceived(dtmf);
            }
        }

        /**
         * Call quality updated
         */
@@ -1485,6 +1546,19 @@ public class ImsCallSession {
                mListener.callQualityChanged(callQuality);
            }
        }

        /**
         * RTP header extension data received.
         * @param extensions The header extension data.
         */
        @Override
        public void callSessionRtpHeaderExtensionsReceived(
                @NonNull List<RtpHeaderExtension> extensions) {
            if (mListener != null) {
                mListener.callSessionRtpHeaderExtensionsReceived(
                        new ArraySet<RtpHeaderExtension>(extensions));
            }
        }
    }

    /**
+57 −0
Original line number Diff line number Diff line
@@ -28,6 +28,10 @@ import android.telephony.ims.stub.ImsCallSessionImplBase;

import com.android.ims.internal.IImsCallSession;

import java.util.ArrayList;
import java.util.Objects;
import java.util.Set;

/**
 * Listener interface for notifying the Framework's {@link ImsCallSession} for updates to an ongoing
 * IMS call.
@@ -682,6 +686,59 @@ public class ImsCallSessionListener {
        }
    }

    /**
     * The {@link ImsService} calls this method to inform the framework of a DTMF digit which was
     * received from the network.
     * <p>
     * According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833 sec 3.10</a>,
     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15.
     * <p>
     * <em>Note:</em> Alpha DTMF digits are converted from lower-case to upper-case.
     *
     * @param dtmf The DTMF digit received, '0'-'9', *, #, A, B, C, or D.
     * @throws IllegalArgumentException If an invalid DTMF character is provided.
     */
    public void callSessionDtmfReceived(char dtmf) {
        if (!(dtmf >= '0' && dtmf <= '9'
                || dtmf >= 'A' && dtmf <= 'D'
                || dtmf >= 'a' && dtmf <= 'd'
                || dtmf == '*'
                || dtmf == '#')) {
            throw new IllegalArgumentException("DTMF digit must be 0-9, *, #, A, B, C, D");
        }
        try {
            mListener.callSessionDtmfReceived(Character.toUpperCase(dtmf));
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

    /**
     * The {@link ImsService} calls this method to inform the framework of RTP header extension data
     * which was received from the network.
     * <p>
     * The set of {@link RtpHeaderExtension} data are identified by local identifiers which were
     * negotiated during SDP signalling.  See RFC8285,
     * {@link ImsCallProfile#getAcceptedRtpHeaderExtensionTypes()} and
     * {@link RtpHeaderExtensionType} for more information.
     * <p>
     * By specification, the RTP header extension is an unacknowledged transmission and there is no
     * guarantee that the header extension will be delivered by the network to the other end of the
     * call.
     *
     * @param extensions The RTP header extension data received.
     */
    public void callSessionRtpHeaderExtensionsReceived(
            @NonNull Set<RtpHeaderExtension> extensions) {
        Objects.requireNonNull(extensions, "extensions are required.");
        try {
            mListener.callSessionRtpHeaderExtensionsReceived(
                    new ArrayList<RtpHeaderExtension>(extensions));
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

    /**
     * Notifies the result of transfer request.
     * @hide
Loading