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

Commit 8e14fdc4 authored by Tyler Gunn's avatar Tyler Gunn Committed by Gerrit Code Review
Browse files

Merge "Basic support for device to device communication."

parents 1ba7f4be d124e2bc
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