| /* |
| * Copyright (C) 2020 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 com.android.internal.telephony.d2d; |
| |
| import android.annotation.NonNull; |
| import android.net.Uri; |
| import android.os.Handler; |
| import android.telecom.Log; |
| import android.telephony.ims.ImsCallProfile; |
| import android.telephony.ims.RtpHeaderExtension; |
| import android.telephony.ims.RtpHeaderExtensionType; |
| import android.util.ArraySet; |
| |
| import com.android.internal.telephony.BiMap; |
| |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Implements a transport protocol which makes use of RTP header extensions to communicate between |
| * two devices. |
| * <p> |
| * Two types of device to device communications are supported, |
| * {@link #DEVICE_STATE_RTP_HEADER_EXTENSION} messages which communicate attributes such as the |
| * device service level and battery, and {@link #CALL_STATE_RTP_HEADER_EXTENSION} which communicates |
| * information pertaining to an ongoing call. |
| * <p> |
| * When the ImsService negotiates the media for a call using SDP it will also potentially negotiate |
| * the supported RTP header extension types which both parties of the call agree upon. When a call |
| * first begins if the accepted RTP header extension URIs matches the two well defined URIs we |
| * define here, then we can assume the other party supports device to device communication using |
| * RTP header extensions. |
| * <p> |
| * It is, however, possible that network signalling causes a failure to successfully negotiate the |
| * RTP header extension types which both sides agree upon. In this case we will wait for a short |
| * period of time to see if we receive an RTP header extension which corresponds to one of the |
| * local identifiers we would have expected to negotiation. If such an RTP header extension is |
| * received in a timely manner, we can assume that the other party is capable of using RTP header |
| * extensions to communicate even though the RTP extension type URIs did not successfully negotiate. |
| * If we do not receive a valid extension in this case, we will consider negotiation for this |
| * transport to have failed. |
| */ |
| public class RtpTransport implements TransportProtocol, RtpAdapter.Callback { |
| /** |
| * {@link Uri} identifier for an RTP header extension used to communicate device state during |
| * calls. |
| */ |
| public static Uri DEVICE_STATE_RTP_HEADER_EXTENSION = |
| Uri.parse("http://develop.android.com/122020/d2dcomm#device-state"); |
| |
| /** |
| * {@link Uri} identifier for an RTP header extension used to communication call state during |
| * calls. |
| */ |
| public static Uri CALL_STATE_RTP_HEADER_EXTENSION = |
| Uri.parse("http://develop.android.com/122020/d2dcomm#call-state"); |
| |
| /** |
| * Default local identifier for device state RTP header extensions. |
| */ |
| public static int DEVICE_STATE_LOCAL_IDENTIFIER = 10; |
| |
| /** |
| * Default local identifier for call state RTP header extensions. |
| */ |
| public static int CALL_STATE_LOCAL_IDENTIFIER = 11; |
| |
| /** |
| * {@link RtpHeaderExtensionType} for device state communication. |
| */ |
| public static RtpHeaderExtensionType DEVICE_STATE_RTP_HEADER_EXTENSION_TYPE = |
| new RtpHeaderExtensionType(DEVICE_STATE_LOCAL_IDENTIFIER, |
| DEVICE_STATE_RTP_HEADER_EXTENSION); |
| |
| /** |
| * {@link RtpHeaderExtensionType} for call state communication. |
| */ |
| public static RtpHeaderExtensionType CALL_STATE_RTP_HEADER_EXTENSION_TYPE = |
| new RtpHeaderExtensionType(CALL_STATE_LOCAL_IDENTIFIER, |
| CALL_STATE_RTP_HEADER_EXTENSION); |
| |
| |
| /** |
| * See {@link #generateRtpHeaderExtension(Communicator.Message)} for more information; indicates |
| * the offset of the parameter value in the RTP header extension payload. |
| */ |
| public static final int RTP_PARAMETER_BIT_OFFSET = 4; |
| |
| /** |
| * RTP header extension bits set for {@link Communicator#MESSAGE_CALL_RADIO_ACCESS_TYPE} msg. |
| */ |
| public static final byte RTP_CALL_STATE_MSG_RADIO_ACCESS_TYPE_BITS = 0b0001; |
| |
| /** |
| * RTP header extension bits set for {@link Communicator#MESSAGE_CALL_AUDIO_CODEC} msg. |
| */ |
| public static final byte RTP_CALL_STATE_MSG_CODEC_BITS = 0b0010; |
| |
| /** |
| * RTP header extension bits set for {@link Communicator#MESSAGE_DEVICE_BATTERY_STATE} msg. |
| */ |
| public static final byte RTP_DEVICE_STATE_MSG_BATTERY_BITS = 0b0001; |
| |
| /** |
| * RTP header extension bits set for {@link Communicator#MESSAGE_DEVICE_NETWORK_COVERAGE} msg. |
| */ |
| public static final byte RTP_DEVICE_STATE_MSG_NETWORK_COVERAGE_BITS = 0b0010; |
| |
| /** |
| * Provides a mapping between the various {@code Communicator#MESSAGE_*} values and their bit |
| * representation in an RTP header extension payload. Used to translate outgoing message types |
| * to an RTP payload and to interpret incoming RTP payloads and translate them back into message |
| * types. |
| */ |
| private static final BiMap<Integer, Byte> CALL_STATE_MSG_TYPE_TO_RTP_BITS = new BiMap<>(); |
| private static final BiMap<Integer, Byte> DEVICE_STATE_MSG_TYPE_TO_RTP_BITS = new BiMap<>(); |
| static { |
| CALL_STATE_MSG_TYPE_TO_RTP_BITS.put( |
| Communicator.MESSAGE_CALL_RADIO_ACCESS_TYPE, |
| RTP_CALL_STATE_MSG_RADIO_ACCESS_TYPE_BITS); |
| CALL_STATE_MSG_TYPE_TO_RTP_BITS.put( |
| Communicator.MESSAGE_CALL_AUDIO_CODEC, |
| RTP_CALL_STATE_MSG_CODEC_BITS); |
| DEVICE_STATE_MSG_TYPE_TO_RTP_BITS.put( |
| Communicator.MESSAGE_DEVICE_BATTERY_STATE, |
| RTP_DEVICE_STATE_MSG_BATTERY_BITS); |
| DEVICE_STATE_MSG_TYPE_TO_RTP_BITS.put( |
| Communicator.MESSAGE_DEVICE_NETWORK_COVERAGE, |
| RTP_DEVICE_STATE_MSG_NETWORK_COVERAGE_BITS); |
| } |
| |
| /** |
| * RTP header extension bits set for {@link Communicator#MESSAGE_CALL_RADIO_ACCESS_TYPE} data |
| * payload. Corresponds to {@link Communicator#RADIO_ACCESS_TYPE_LTE}. |
| */ |
| public static final byte RTP_RAT_VALUE_LTE_BITS = 0b0001 << RTP_PARAMETER_BIT_OFFSET; |
| |
| /** |
| * RTP header extension bits set for {@link Communicator#MESSAGE_CALL_RADIO_ACCESS_TYPE} data |
| * payload. Corresponds to {@link Communicator#RADIO_ACCESS_TYPE_IWLAN}. |
| */ |
| public static final byte RTP_RAT_VALUE_WLAN_BITS = 0b0010 << RTP_PARAMETER_BIT_OFFSET; |
| |
| /** |
| * RTP header extension bits set for {@link Communicator#MESSAGE_CALL_RADIO_ACCESS_TYPE} data |
| * payload. Corresponds to {@link Communicator#RADIO_ACCESS_TYPE_NR}. |
| */ |
| public static final byte RTP_RAT_VALUE_NR_BITS = 0b0011 << RTP_PARAMETER_BIT_OFFSET; |
| |
| /** |
| * Provides a mapping between the various {@code Communicator#RADIO_ACCESS_TYPE_*} values and |
| * their bit representation in an RTP header extension payload. |
| */ |
| private static final BiMap<Integer, Byte> RAT_VALUE_TO_RTP_BITS = new BiMap<>(); |
| static { |
| RAT_VALUE_TO_RTP_BITS.put( |
| Communicator.RADIO_ACCESS_TYPE_IWLAN, RTP_RAT_VALUE_WLAN_BITS); |
| RAT_VALUE_TO_RTP_BITS.put( |
| Communicator.RADIO_ACCESS_TYPE_LTE, RTP_RAT_VALUE_LTE_BITS); |
| RAT_VALUE_TO_RTP_BITS.put( |
| Communicator.RADIO_ACCESS_TYPE_NR, RTP_RAT_VALUE_NR_BITS); |
| } |
| |
| /** |
| * RTP header extension bits set for {@link Communicator#MESSAGE_CALL_AUDIO_CODEC} data |
| * payload. Corresponds to {@link Communicator#AUDIO_CODEC_EVS}. |
| */ |
| public static final byte RTP_CODEC_VALUE_EVS_BITS = 0b0001 << RTP_PARAMETER_BIT_OFFSET; |
| public static final byte RTP_CODEC_VALUE_AMR_WB_BITS = 0b0010 << RTP_PARAMETER_BIT_OFFSET; |
| public static final byte RTP_CODEC_VALUE_AMR_NB_BITS = 0b0011 << RTP_PARAMETER_BIT_OFFSET; |
| |
| /** |
| * Provides a mapping between the various {@code Communicator#AUDIO_CODEC_*} values and |
| * their bit representation in an RTP header extension payload. |
| */ |
| private static final BiMap<Integer, Byte> CODEC_VALUE_TO_RTP_BITS = new BiMap<>(); |
| static { |
| CODEC_VALUE_TO_RTP_BITS.put( |
| Communicator.AUDIO_CODEC_EVS, RTP_CODEC_VALUE_EVS_BITS); |
| CODEC_VALUE_TO_RTP_BITS.put( |
| Communicator.AUDIO_CODEC_AMR_WB, RTP_CODEC_VALUE_AMR_WB_BITS); |
| CODEC_VALUE_TO_RTP_BITS.put( |
| Communicator.AUDIO_CODEC_AMR_NB, RTP_CODEC_VALUE_AMR_NB_BITS); |
| } |
| |
| public static final byte RTP_BATTERY_STATE_LOW_BITS = 0b0000 << RTP_PARAMETER_BIT_OFFSET; |
| public static final byte RTP_BATTERY_STATE_GOOD_BITS = 0b0001 << RTP_PARAMETER_BIT_OFFSET; |
| public static final byte RTP_BATTERY_STATE_CHARGING_BITS = 0b0011 << RTP_PARAMETER_BIT_OFFSET; |
| |
| /** |
| * Provides a mapping between the various {@code Communicator#BATTERY_STATE_*} values and |
| * their bit representation in an RTP header extension payload. |
| */ |
| private static final BiMap<Integer, Byte> BATTERY_STATE_VALUE_TO_RTP_BITS = new BiMap<>(); |
| static { |
| BATTERY_STATE_VALUE_TO_RTP_BITS.put( |
| Communicator.BATTERY_STATE_LOW, RTP_BATTERY_STATE_LOW_BITS); |
| BATTERY_STATE_VALUE_TO_RTP_BITS.put( |
| Communicator.BATTERY_STATE_GOOD, RTP_BATTERY_STATE_GOOD_BITS); |
| BATTERY_STATE_VALUE_TO_RTP_BITS.put( |
| Communicator.BATTERY_STATE_CHARGING, RTP_BATTERY_STATE_CHARGING_BITS); |
| } |
| |
| public static final byte RTP_NETWORK_COVERAGE_POOR_BITS = 0b0000 << RTP_PARAMETER_BIT_OFFSET; |
| public static final byte RTP_NETWORK_COVERAGE_GOOD_BITS = 0b0001 << RTP_PARAMETER_BIT_OFFSET; |
| |
| /** |
| * Provides a mapping between the various {@code Communicator#COVERAGE_*} values and |
| * their bit representation in an RTP header extension payload. |
| */ |
| private static final BiMap<Integer, Byte> NETWORK_COVERAGE_VALUE_TO_RTP_BITS = new BiMap<>(); |
| static { |
| NETWORK_COVERAGE_VALUE_TO_RTP_BITS.put( |
| Communicator.COVERAGE_POOR, RTP_NETWORK_COVERAGE_POOR_BITS); |
| NETWORK_COVERAGE_VALUE_TO_RTP_BITS.put( |
| Communicator.COVERAGE_GOOD, RTP_NETWORK_COVERAGE_GOOD_BITS); |
| } |
| |
| /** |
| * Indicates that the transport is not yet ready for use and negotiation has not yet completed. |
| */ |
| public static final int PROTOCOL_STATUS_NEGOTIATION_REQUIRED = 1; |
| |
| /** |
| * Indicates that the agreed upon RTP header extension types (see |
| * {@link RtpAdapter#getAcceptedRtpHeaderExtensions()}) did not specify both the |
| * {@link #DEVICE_STATE_RTP_HEADER_EXTENSION} and {@link #CALL_STATE_RTP_HEADER_EXTENSION |
| * RTP header extension types. We are not going to wait a short time to see if we receive an |
| * incoming RTP packet with one of the local identifiers we'd normally expect associated with |
| * these header extension {@link Uri}s. If we do receive a valid RTP header extension we will |
| * move to the {@link #PROTOCOL_STATUS_NEGOTIATION_COMPLETE} status. If we do not receive a |
| * valid RTP header extension we move to {@link #PROTOCOL_STATUS_NEGOTIATION_FAILED} and report |
| * the failure via {@link Callback#onNegotiationFailed(TransportProtocol) }. |
| */ |
| public static final int PROTOCOL_STATUS_NEGOTIATION_WAITING_ON_PACKET = 2; |
| |
| /** |
| * Indicates we either agreed upon the required header extensions, or received a valid packet |
| * despite not having agreed upon required header extensions. The transport is ready to use at |
| * this point. |
| */ |
| public static final int PROTOCOL_STATUS_NEGOTIATION_COMPLETE = 3; |
| |
| /** |
| * Indicates protocol negotiation failed. |
| */ |
| public static final int PROTOCOL_STATUS_NEGOTIATION_FAILED = 4; |
| |
| /** |
| * Callback which is used to report back to the {@link Communicator} about the status of |
| * protocol negotiation and incoming messages. |
| */ |
| private TransportProtocol.Callback mCallback; |
| |
| /** |
| * Adapter which abstracts out the details of sending/receiving RTP header extensions. |
| */ |
| private final RtpAdapter mRtpAdapter; |
| |
| /** |
| * Configuration adapter for timeouts related to protocol setup. |
| */ |
| private final Timeouts.Adapter mTimeoutsAdapter; |
| |
| /** |
| * Handler for posting future events. |
| */ |
| private final Handler mHandler; |
| |
| /** |
| * {@code true} if the carrier supports negotiating the RTP header extensions using SDP. |
| * If {@code true}, we can expected the |
| * {@link ImsCallProfile#getAcceptedRtpHeaderExtensionTypes()} to contain the SDP negotiated RTP |
| * header extensions. If {@code false} we will assume the protocol is negotiated only after |
| * receiving an RTP header extension of the expected type. |
| */ |
| private final boolean mIsSdpNegotiationSupported; |
| |
| /** |
| * Protocol status. |
| */ |
| private int mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_REQUIRED; |
| |
| /** |
| * The supported RTP header extension types. |
| */ |
| private ArraySet<RtpHeaderExtensionType> mSupportedRtpHeaderExtensionTypes = new ArraySet<>(); |
| |
| /** |
| * Initializes the {@link RtpTransport}. |
| * @param rtpAdapter Adapter for abstract send/receive of RTP header extension data. |
| * @param timeoutsAdapter Timeouts adapter for dealing with time based configurations. |
| * @param handler Handler for posting future events. |
| * @param isSdpNegotiationSupported Indicates whether SDP negotiation |
| */ |
| public RtpTransport(RtpAdapter rtpAdapter, Timeouts.Adapter timeoutsAdapter, Handler handler, |
| boolean isSdpNegotiationSupported) { |
| mRtpAdapter = rtpAdapter; |
| mTimeoutsAdapter = timeoutsAdapter; |
| mHandler = handler; |
| mIsSdpNegotiationSupported = isSdpNegotiationSupported; |
| } |
| |
| /** |
| * Sets the Callback for this transport. Used to report back received messages. |
| * @param callback The callback. |
| */ |
| @Override |
| public void setCallback(Callback callback) { |
| mCallback = callback; |
| } |
| |
| /** |
| * Begin transport protocol negotiation at the request of the framework. |
| * |
| * If the {@link #DEVICE_STATE_RTP_HEADER_EXTENSION} and |
| * {@link #CALL_STATE_RTP_HEADER_EXTENSION} extension {@link Uri}s are part of the accepted |
| * extension types, we consider the negotiation successful. |
| * |
| * TODO: If they are not in the accepted extensions, we will wait a short period of time to |
| * receive a valid RTP header extension we recognize. |
| */ |
| @Override |
| public void startNegotiation() { |
| Set<RtpHeaderExtensionType> acceptedExtensions = |
| mRtpAdapter.getAcceptedRtpHeaderExtensions(); |
| mSupportedRtpHeaderExtensionTypes.addAll(acceptedExtensions); |
| |
| Log.i(this, "startNegotiation: supportedExtensions=%s", mSupportedRtpHeaderExtensionTypes |
| .stream() |
| .map(e -> e.toString()) |
| .collect(Collectors.joining(","))); |
| |
| if (mIsSdpNegotiationSupported) { |
| boolean areExtensionsAvailable = acceptedExtensions.stream().anyMatch( |
| e -> e.getUri().equals(DEVICE_STATE_RTP_HEADER_EXTENSION)) |
| && acceptedExtensions.stream().anyMatch( |
| e -> e.getUri().equals(CALL_STATE_RTP_HEADER_EXTENSION)); |
| |
| if (areExtensionsAvailable) { |
| // Headers were negotiated during SDP, so we can assume negotiation is complete and |
| // signal to the communicator that we can use this transport. |
| mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_COMPLETE; |
| Log.i(this, "startNegotiation: header extensions available, negotiation success"); |
| notifyProtocolReady(); |
| } else { |
| // Headers failed to be negotiated during SDP. Assume protocol is not available. |
| // TODO: Implement fallback logic where we still try an SDP probe/response. |
| mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_FAILED; |
| Log.i(this, |
| "startNegotiation: header extensions not available; negotiation failed"); |
| notifyProtocolUnavailable(); |
| } |
| } else { |
| Log.i(this, "startNegotiation: SDP negotiation not supported; negotiation complete"); |
| // TODO: This is temporary; we will need to implement a probe/response in this scenario |
| // if SDP is not supported. For now we will just assume the protocol is ready. |
| notifyProtocolReady(); |
| } |
| } |
| |
| /** |
| * Handles sending messages using the RTP transport. Generates valid {@link RtpHeaderExtension} |
| * instances for each message to send. |
| * @param messages The messages to send. |
| */ |
| @Override |
| public void sendMessages(Set<Communicator.Message> messages) { |
| Set<RtpHeaderExtension> toSend = messages.stream().map(m -> generateRtpHeaderExtension(m)) |
| .collect(Collectors.toSet()); |
| Log.i(this, "sendMessages: sending=%s", messages); |
| mRtpAdapter.sendRtpHeaderExtensions(toSend); |
| } |
| |
| /** |
| * Forces the protocol status to negotiated; for test purposes. |
| */ |
| @Override |
| public void forceNegotiated() { |
| // If there is no supported RTP header extensions we need to fake it. |
| if (mSupportedRtpHeaderExtensionTypes == null |
| || mSupportedRtpHeaderExtensionTypes.isEmpty()) { |
| mSupportedRtpHeaderExtensionTypes.add(DEVICE_STATE_RTP_HEADER_EXTENSION_TYPE); |
| mSupportedRtpHeaderExtensionTypes.add(CALL_STATE_RTP_HEADER_EXTENSION_TYPE); |
| } |
| mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_COMPLETE; |
| } |
| |
| /** |
| * Forces the protocol status to un-negotiated; for test purposes. |
| */ |
| @Override |
| public void forceNotNegotiated() { |
| mProtocolStatus = PROTOCOL_STATUS_NEGOTIATION_REQUIRED; |
| } |
| |
| /** |
| * Called by the platform when RTP header extensions are received and need to be translated to |
| * concrete messages. |
| * Results in a callback via |
| * {@link com.android.internal.telephony.d2d.TransportProtocol.Callback#onMessagesReceived(Set)} |
| * to notify when incoming messages are received. |
| * @param extensions The received RTP header extensions. |
| */ |
| @Override |
| public void onRtpHeaderExtensionsReceived(@NonNull Set<RtpHeaderExtension> extensions) { |
| Set<Communicator.Message> messages = extensions.stream().map(e -> extractMessage(e)) |
| .filter(Objects::nonNull) |
| .collect(Collectors.toSet()); |
| if (messages.size() == 0) { |
| return; |
| } |
| mCallback.onMessagesReceived(messages); |
| } |
| |
| /** |
| * Given a {@link RtpHeaderExtension} received from the network, parse out an found message. |
| * @param extension The RTP header extension to parse. |
| * @return The message, or {@code null} if no valid message found. |
| */ |
| private Communicator.Message extractMessage(@NonNull RtpHeaderExtension extension) { |
| // First determine the URI to figure out the general classification of the message. |
| Optional<Uri> foundUri = mSupportedRtpHeaderExtensionTypes.stream() |
| .filter(et -> et.getLocalIdentifier() == extension.getLocalIdentifier()) |
| .map(et -> et.getUri()) |
| .findFirst(); |
| if (!foundUri.isPresent()) { |
| Log.w(this, "extractMessage: localIdentifier=%d not supported.", |
| extension.getLocalIdentifier()); |
| return null; |
| } |
| |
| if (extension.getExtensionData() == null || extension.getExtensionData().length != 1) { |
| Log.w(this, "extractMessage: localIdentifier=%d message with invalid data length.", |
| extension.getLocalIdentifier()); |
| return null; |
| } |
| |
| Uri uri = foundUri.get(); |
| |
| // Extract the bits which are the message type. |
| byte messageTypeBits = (byte) (extension.getExtensionData()[0] & 0b1111); |
| byte messageValueBits = (byte) (extension.getExtensionData()[0] |
| & (0b1111 << RTP_PARAMETER_BIT_OFFSET)); |
| |
| int messageType; |
| int messageValue; |
| if (DEVICE_STATE_RTP_HEADER_EXTENSION.equals(uri)) { |
| Integer type = DEVICE_STATE_MSG_TYPE_TO_RTP_BITS.getKey(messageTypeBits); |
| if (type == null) { |
| Log.w(this, "extractMessage: localIdentifier=%d message with invalid type %s.", |
| extension.getLocalIdentifier(), Integer.toBinaryString(messageTypeBits)); |
| return null; |
| } |
| messageType = type; |
| switch (messageType) { |
| case Communicator.MESSAGE_DEVICE_BATTERY_STATE: |
| Integer val = BATTERY_STATE_VALUE_TO_RTP_BITS.getKey(messageValueBits); |
| if (val == null) { |
| Log.w(this, "extractMessage: localIdentifier=%d, battery state msg with " |
| + "invalid value=%s", |
| extension.getLocalIdentifier(), |
| Integer.toBinaryString(messageValueBits)); |
| return null; |
| } |
| messageValue = val; |
| break; |
| case Communicator.MESSAGE_DEVICE_NETWORK_COVERAGE: |
| Integer val2 = NETWORK_COVERAGE_VALUE_TO_RTP_BITS.getKey(messageValueBits); |
| if (val2 == null) { |
| Log.w(this, "extractMessage: localIdentifier=%d, network coverage msg with " |
| + "invalid value=%s", |
| extension.getLocalIdentifier(), |
| Integer.toBinaryString(messageValueBits)); |
| return null; |
| } |
| messageValue = val2; |
| break; |
| default: |
| Log.w(this, "messageType=%s, value=%s; invalid value", |
| Integer.toBinaryString(messageTypeBits), |
| Integer.toBinaryString(messageValueBits)); |
| return null; |
| } |
| } else if (CALL_STATE_RTP_HEADER_EXTENSION.equals(uri)) { |
| Integer typeValue = CALL_STATE_MSG_TYPE_TO_RTP_BITS.getKey(messageTypeBits); |
| if (typeValue == null) { |
| Log.w(this, "extractMessage: localIdentifier=%d, network coverage msg with " |
| + "invalid type=%s", |
| extension.getLocalIdentifier(), |
| Integer.toBinaryString(messageTypeBits)); |
| return null; |
| } |
| messageType = typeValue; |
| switch (messageType) { |
| case Communicator.MESSAGE_CALL_AUDIO_CODEC: |
| Integer val = CODEC_VALUE_TO_RTP_BITS.getKey(messageValueBits); |
| if (val == null) { |
| Log.w(this, "extractMessage: localIdentifier=%d, audio codec msg with " |
| + "invalid value=%s", |
| extension.getLocalIdentifier(), |
| Integer.toBinaryString(messageValueBits)); |
| return null; |
| } |
| messageValue = val; |
| break; |
| case Communicator.MESSAGE_CALL_RADIO_ACCESS_TYPE: |
| Integer val2 = RAT_VALUE_TO_RTP_BITS.getKey(messageValueBits); |
| if (val2 == null) { |
| Log.w(this, "extractMessage: localIdentifier=%d, rat type msg with " |
| + "invalid value=%s", |
| extension.getLocalIdentifier(), |
| Integer.toBinaryString(messageValueBits)); |
| return null; |
| } |
| messageValue = val2; |
| break; |
| default: |
| Log.w(this, "messageType=%s, value=%s; invalid value", |
| Integer.toBinaryString(messageTypeBits), |
| Integer.toBinaryString(messageValueBits)); |
| return null; |
| } |
| } else { |
| Log.w(this, "invalid uri=%s", uri); |
| return null; |
| } |
| Log.i(this, "extractMessage: messageType=%s, value=%s --> message=%d, value=%d", |
| Integer.toBinaryString(messageTypeBits), Integer.toBinaryString(messageValueBits), |
| messageType, messageValue); |
| return new Communicator.Message(messageType, messageValue); |
| } |
| |
| |
| /** |
| * Generates an {@link RtpHeaderExtension} based on the specified message. |
| * Per RFC8285, RTP header extensions have the format: |
| * (bit) |
| * ___byte__ __byte__ |
| * 1234 5678 12345678 |
| * +----+----+--------+ |
| * | ID | len| data | |
| * +----+----+--------+ |
| * Where ID is the {@link RtpHeaderExtensionType#getLocalIdentifier()}, len indicates the |
| * number of data bytes being sent ({@link RtpHeaderExtension#getExtensionData()}.size()), |
| * and data is the payload of the RTP header extension. |
| * The {@link RtpHeaderExtension} generated here contains the bytes necessary to populate the |
| * data field; the "ID" and "len" fields in the send RTP header extension are populated by the |
| * IMS service. |
| * |
| * This method is responsible for generating the data field in the RTP header extension to be |
| * sent. |
| * |
| * Device to device communication assumes the following format for the single byte payload: |
| * 12345678 |
| * +--------+ |
| * |PPPPVVVV| |
| * +--------+ |
| * Where: |
| * PPPP - 4 bits reserved for indicating the parameter encoded (e.g. it could be an audio |
| * codec ({@link Communicator#MESSAGE_CALL_AUDIO_CODEC})). |
| * VVVV - 4 bits reserved for indicating the value of the parameter encoded (e.g. it could be |
| * the EVS codec ({@link Communicator#AUDIO_CODEC_EVS})). |
| * |
| * @param message The message to be sent via RTP header extensions. |
| * @return An {@link RtpHeaderExtension} representing the message. |
| */ |
| public RtpHeaderExtension generateRtpHeaderExtension(Communicator.Message message) { |
| byte[] payload = new byte[1]; |
| switch (message.getType()) { |
| case Communicator.MESSAGE_CALL_AUDIO_CODEC: |
| payload[0] |= CALL_STATE_MSG_TYPE_TO_RTP_BITS.getValue(message.getType()); |
| payload[0] |= CODEC_VALUE_TO_RTP_BITS.getValue(message.getValue()); |
| return new RtpHeaderExtension( |
| getRtpHeaderExtensionIdentifier(CALL_STATE_RTP_HEADER_EXTENSION), |
| payload); |
| case Communicator.MESSAGE_CALL_RADIO_ACCESS_TYPE: |
| payload[0] |= CALL_STATE_MSG_TYPE_TO_RTP_BITS.getValue(message.getType()); |
| payload[0] |= RAT_VALUE_TO_RTP_BITS.getValue(message.getValue()); |
| return new RtpHeaderExtension( |
| getRtpHeaderExtensionIdentifier(CALL_STATE_RTP_HEADER_EXTENSION), |
| payload); |
| case Communicator.MESSAGE_DEVICE_BATTERY_STATE: |
| payload[0] |= DEVICE_STATE_MSG_TYPE_TO_RTP_BITS.getValue(message.getType()); |
| payload[0] |= BATTERY_STATE_VALUE_TO_RTP_BITS.getValue(message.getValue()); |
| return new RtpHeaderExtension( |
| getRtpHeaderExtensionIdentifier(DEVICE_STATE_RTP_HEADER_EXTENSION), |
| payload); |
| case Communicator.MESSAGE_DEVICE_NETWORK_COVERAGE: |
| payload[0] |= DEVICE_STATE_MSG_TYPE_TO_RTP_BITS.getValue(message.getType()); |
| payload[0] |= NETWORK_COVERAGE_VALUE_TO_RTP_BITS.getValue(message.getValue()); |
| return new RtpHeaderExtension( |
| getRtpHeaderExtensionIdentifier(DEVICE_STATE_RTP_HEADER_EXTENSION), |
| payload); |
| } |
| return null; |
| } |
| |
| /** |
| * Given a {@link Uri} identifying an RTP header extension, return the associated local |
| * identifier. |
| * @param requestedUri the requested {@link Uri}. |
| * @return the local identifier. |
| */ |
| private int getRtpHeaderExtensionIdentifier(Uri requestedUri) { |
| return mSupportedRtpHeaderExtensionTypes.stream() |
| .filter(t -> t.getUri().equals(requestedUri)) |
| .findFirst().get().getLocalIdentifier(); |
| } |
| |
| /** |
| * Notifies the {@link Communicator} that the RTP-based protocol is available.} |
| */ |
| private void notifyProtocolReady() { |
| if (mCallback != null) { |
| mCallback.onNegotiationSuccess(this); |
| } |
| } |
| |
| /** |
| * Notifies the {@link Communicator} that the RTP-based protocol is unavailable. |
| */ |
| private void notifyProtocolUnavailable() { |
| if (mCallback != null) { |
| mCallback.onNegotiationFailed(this); |
| } |
| } |
| } |