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

Commit d124e2bc authored by Tyler Gunn's avatar Tyler Gunn
Browse files

Basic support for device to device communication.

Implements the framework for a variety of in-call device-to-device
communication protocols.  Provides an RTP header extension based D2D
protocol and the skeleton of a DTMF based protocol (to be completed).

Bug: 163085177
Test: Add new unit tests for all classes.
Change-Id: I83ac8fd34d9f44aab4a8275e33cbe3bef45d6683
parent 4a27ec7d
Loading
Loading
Loading
Loading
+78 −0
Original line number Diff line number Diff line
/*
 * 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;

import android.util.ArrayMap;

import java.util.Collection;
import java.util.Map;

/**
 * A very basic bidirectional map.
 */
public class BiMap<K, V> {
    private Map<K, V> mPrimaryMap = new ArrayMap<>();
    private Map<V, K> mSecondaryMap = new ArrayMap<>();

    public boolean put(K key, V value) {
        if (key == null || value == null || mPrimaryMap.containsKey(key) ||
                mSecondaryMap.containsKey(value)) {
            return false;
        }

        mPrimaryMap.put(key, value);
        mSecondaryMap.put(value, key);
        return true;
    }

    public boolean remove(K key) {
        if (key == null) {
            return false;
        }
        if (mPrimaryMap.containsKey(key)) {
            V value = getValue(key);
            mPrimaryMap.remove(key);
            mSecondaryMap.remove(value);
            return true;
        }
        return false;
    }

    public boolean removeValue(V value) {
        if (value == null) {
            return false;
        }
        return remove(getKey(value));
    }

    public V getValue(K key) {
        return mPrimaryMap.get(key);
    }

    public K getKey(V value) {
        return mSecondaryMap.get(value);
    }

    public Collection<V> getValues() {
        return mPrimaryMap.values();
    }

    public void clear() {
        mPrimaryMap.clear();
        mSecondaryMap.clear();
    }
}
+277 −0
Original line number Diff line number Diff line
/*
 * 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.annotation.Nullable;
import android.telecom.Connection;

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

/**
 * Responsible for facilitating device-to-device communication between both ends of a call.
 */
public class Communicator implements TransportProtocol.Callback {

    /**
     * Callback for events out of communicator.
     */
    public interface Callback {
        void onMessagesReceived(@NonNull Set<Message> messages);
    }

    public static final int MESSAGE_CALL_RADIO_ACCESS_TYPE = 1;
    public static final int MESSAGE_CALL_AUDIO_CODEC = 2;
    public static final int MESSAGE_DEVICE_BATTERY_STATE = 3;
    public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4;

    public static final int RADIO_ACCESS_TYPE_LTE = 1;
    public static final int RADIO_ACCESS_TYPE_IWLAN = 2;
    public static final int RADIO_ACCESS_TYPE_NR = 3;

    public static final int AUDIO_CODEC_EVS = 1;
    public static final int AUDIO_CODEC_AMR_WB = 2;
    public static final int AUDIO_CODEC_AMR_NB = 3;

    public static final int BATTERY_STATE_LOW = 1;
    public static final int BATTERY_STATE_GOOD = 2;
    public static final int BATTERY_STATE_CHARGING = 3;

    public static final int COVERAGE_POOR = 1;
    public static final int COVERAGE_GOOD = 2;

    /**
     * Encapsulates a D2D communication message.
     */
    public static class Message {
        private int mType;
        private int mValue;

        public Message(int type, int value) {
            mType = type;
            mValue = value;
        }

        public int getType() {
            return mType;
        }

        public int getValue() {
            return mValue;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Message message = (Message) o;
            return mType == message.mType && mValue == message.mValue;
        }

        @Override
        public int hashCode() {
            return Objects.hash(mType, mValue);
        }

        @Override
        public String toString() {
            return "Message{" + "mType=" + messageToString(mType) +", mValue="
                    + valueToString(mType, mValue) + '}';
        }
    }

    private boolean mIsNegotiated;
    private TransportProtocol mActiveTransport;
    private List<TransportProtocol> mTransportProtocols = new ArrayList<>();
    private Callback mCallback;

    public Communicator(@NonNull List<TransportProtocol> transportProtocols,
            @NonNull Callback callback) {
        mTransportProtocols.addAll(transportProtocols);
        mTransportProtocols.forEach(p -> p.setCallback(this));
        mCallback = callback;
    }

    /**
     * @return the active {@link TransportProtocol} which is being used for sending/receiving
     * messages.
     */
    public @Nullable TransportProtocol getActiveTransport() {
        return mActiveTransport;
    }

    /**
     * Handles state changes for a call.
     * @param c The call in question.
     * @param state The new state.
     */
    public void onStateChanged(Connection c, @Connection.ConnectionState int state) {
        if (state == Connection.STATE_ACTIVE) {
            // Protocol negotiation can start as we are active
            if (mActiveTransport == null) {
                mIsNegotiated = false;
                negotiateNextProtocol();
            }
        }
    }

    /**
     * Called by a {@link TransportProtocol} when negotiation of that protocol has succeeded.
     * @param protocol The protocol.
     */
    @Override
    public void onNegotiationSuccess(@NonNull TransportProtocol protocol) {
        if (protocol != mActiveTransport) {
            // Uh oh, shouldn't happen.
        }
        mIsNegotiated = true;
    }

    /**
     * Called by a {@link TransportProtocol} when negotiation of that protocol has failed.
     * @param protocol The protocol.
     */
    @Override
    public void onNegotiationFailed(@NonNull TransportProtocol protocol) {
        if (protocol != mActiveTransport) {
            // Uh oh, shouldn't happen.
        }
        mIsNegotiated = false;
        negotiateNextProtocol();
    }

    /**
     * Called by a {@link TransportProtocol} to report incoming messages received via that
     * transport.
     * @param messages The received messages.
     */
    @Override
    public void onMessagesReceived(@NonNull Set<Message> messages) {
        if (mCallback != null) {
            mCallback.onMessagesReceived(messages);
        }
    }

    /**
     * Use the {@link Communicator} to send a set of device-to-device messages.
     * @param messages The {@link Message}s to send.
     */
    public void sendMessages(@NonNull Set<Message> messages) {
        if (mActiveTransport == null || !mIsNegotiated) {
            return;
        }

        mActiveTransport.sendMessages(messages);
    }

    /**
     * Find a new protocol to use and start negotiation.
     */
    private void negotiateNextProtocol() {
        mActiveTransport = getNextCandidateProtocol();
        if (mActiveTransport == null) {
            // No more protocols, exit.
            return;
        }

        mActiveTransport.startNegotiation();
    }

    /**
     * @return the next protocol to attempt to use.  If there is no active protocol, use the first
     * one; otherwise use the one after the currently active one.
     */
    private TransportProtocol getNextCandidateProtocol() {
        TransportProtocol candidateProtocol = null;
        if (mActiveTransport == null) {
            candidateProtocol = mTransportProtocols.get(0);
        } else {
            for (int ix = 0; ix < mTransportProtocols.size(); ix++) {
                TransportProtocol protocol = mTransportProtocols.get(ix);
                if (protocol == mActiveTransport) {
                    if (ix + 1 < mTransportProtocols.size()) {
                        // Next one is candidate
                        candidateProtocol = mTransportProtocols.get(ix + 1);
                    }
                    break;
                }
            }
        }
        return candidateProtocol;
    }

    public static String messageToString(int messageType) {
        switch (messageType) {
            case MESSAGE_CALL_RADIO_ACCESS_TYPE:
                return "MESSAGE_CALL_RADIO_ACCESS_TYPE";
            case MESSAGE_CALL_AUDIO_CODEC:
                return "MESSAGE_CALL_AUDIO_CODEC";
            case MESSAGE_DEVICE_BATTERY_STATE:
                return "MESSAGE_DEVICE_BATTERY_STATE";
            case MESSAGE_DEVICE_NETWORK_COVERAGE:
                return "MESSAGE_DEVICE_NETWORK_COVERAGE";
        }
        return "";
    }

    public static String valueToString(int messageType, int value) {
        switch (messageType) {
            case MESSAGE_CALL_RADIO_ACCESS_TYPE:
                switch (value) {
                    case RADIO_ACCESS_TYPE_LTE:
                        return "RADIO_ACCESS_TYPE_LTE";
                    case RADIO_ACCESS_TYPE_IWLAN:
                        return "RADIO_ACCESS_TYPE_IWLAN";
                    case RADIO_ACCESS_TYPE_NR:
                        return "RADIO_ACCESS_TYPE_NR";
                }
                return "";
            case MESSAGE_CALL_AUDIO_CODEC:
                switch (value) {
                    case AUDIO_CODEC_EVS:
                        return "AUDIO_CODEC_EVS";
                    case AUDIO_CODEC_AMR_WB:
                        return "AUDIO_CODEC_AMR_WB";
                    case AUDIO_CODEC_AMR_NB:
                        return "AUDIO_CODEC_AMR_NB";
                }
                return "";
            case MESSAGE_DEVICE_BATTERY_STATE:
                switch (value) {
                    case BATTERY_STATE_LOW:
                        return "BATTERY_STATE_LOW";
                    case BATTERY_STATE_GOOD:
                        return "BATTERY_STATE_GOOD";
                    case BATTERY_STATE_CHARGING:
                        return "BATTERY_STATE_CHARGING";
                }
                return "";
            case MESSAGE_DEVICE_NETWORK_COVERAGE:
                switch (value) {
                    case COVERAGE_POOR:
                        return "COVERAGE_POOR";
                    case COVERAGE_GOOD:
                        return "COVERAGE_GOOD";
                }
                return "";
        }
        return "";
    }
}
+43 −0
Original line number Diff line number Diff line
/*
 * 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.os.Message;

/**
 * Abstracts interaction with DTMF communication APIs.
 */
public interface DtmfAdapter {
    /**
     * Called when a DTMF digit is received from the network.
     * <p>
     * In concrete implementations, should be linked to
     * {@link android.telephony.ims.ImsCallSessionListener#callSessionDtmfReceived(char)}.
     *
     * @param digit The received DTMF digit.
     */
    void onDtmfReceived(char digit);

    /**
     * Called when a DTMF digit should be sent to the network.
     * <p>
     * In concrete implementations, should be linked to
     * {@link android.telephony.ims.stub.ImsCallSessionImplBase#sendDtmf(char, Message)}.
     * @param digit The DTMF digit to send.
     */
    void sendDtmf(char digit);
}
+44 −0
Original line number Diff line number Diff line
/*
 * 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 java.util.Set;

/**
 * Implements a DTMF-based transport for use with device-to-device communication.
 *
 * TODO: This is a stub placeholder protocol for now.
 */
public class DtmfTransport implements TransportProtocol {

    private TransportProtocol.Callback mCallback;

    @Override
    public void setCallback(Callback callback) {
        mCallback = callback;
    }

    @Override
    public void startNegotiation() {
        // TODO: implement
    }

    @Override
    public void sendMessages(Set<Communicator.Message> messages) {
        // TODO: implement
    }
}
+49 −0
Original line number Diff line number Diff line
/*
 * 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.telephony.ims.RtpHeaderExtension;
import android.telephony.ims.RtpHeaderExtensionType;

import java.util.Set;

/**
 * Abstracts out details of dealing with the RTP header extensions from the {@link RtpTransport} to
 * facilitate easier testing.
 */
public interface RtpAdapter {
    public interface Callback {
        /**
         * Used to indicate when RTP header extensions are received.
         * @param extensions The extensions.
         */
        void onRtpHeaderExtensionsReceived(Set<RtpHeaderExtension> extensions);
    }

    /**
     * Used to retrieve the accepted RTP header extensions by the {@link RtpTransport}.
     * @return the accepted RTP header extensions.
     */
    Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensions();

    /**
     * Used by the {@link RtpTransport} to send RTP header extension messages.
     * @param rtpHeaderExtensions the rtp header extension messages to send.
     */
    void sendRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> rtpHeaderExtensions);
}
Loading