1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony.d2d;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.telecom.Connection;
22 import android.telecom.Log;
23 
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.Objects;
27 import java.util.Optional;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 
31 /**
32  * Responsible for facilitating device-to-device communication between both ends of a call.
33  */
34 public class Communicator implements TransportProtocol.Callback {
35 
36     /**
37      * Callback for events out of communicator.
38      */
39     public interface Callback {
onMessagesReceived(@onNull Set<Message> messages)40         void onMessagesReceived(@NonNull Set<Message> messages);
onD2DAvailabilitychanged(boolean isAvailable)41         void onD2DAvailabilitychanged(boolean isAvailable);
42     }
43 
44     public static final int MESSAGE_CALL_RADIO_ACCESS_TYPE = 1;
45     public static final int MESSAGE_CALL_AUDIO_CODEC = 2;
46     public static final int MESSAGE_DEVICE_BATTERY_STATE = 3;
47     public static final int MESSAGE_DEVICE_NETWORK_COVERAGE = 4;
48 
49     public static final int RADIO_ACCESS_TYPE_LTE = 1;
50     public static final int RADIO_ACCESS_TYPE_IWLAN = 2;
51     public static final int RADIO_ACCESS_TYPE_NR = 3;
52 
53     public static final int AUDIO_CODEC_EVS = 1;
54     public static final int AUDIO_CODEC_AMR_WB = 2;
55     public static final int AUDIO_CODEC_AMR_NB = 3;
56 
57     public static final int BATTERY_STATE_LOW = 1;
58     public static final int BATTERY_STATE_GOOD = 2;
59     public static final int BATTERY_STATE_CHARGING = 3;
60 
61     public static final int COVERAGE_POOR = 1;
62     public static final int COVERAGE_GOOD = 2;
63 
64     /**
65      * Encapsulates a D2D communication message.
66      */
67     public static class Message {
68         private int mType;
69         private int mValue;
70 
Message(int type, int value)71         public Message(int type, int value) {
72             mType = type;
73             mValue = value;
74         }
75 
getType()76         public int getType() {
77             return mType;
78         }
79 
getValue()80         public int getValue() {
81             return mValue;
82         }
83 
84         @Override
equals(Object o)85         public boolean equals(Object o) {
86             if (this == o) return true;
87             if (o == null || getClass() != o.getClass()) return false;
88             Message message = (Message) o;
89             return mType == message.mType && mValue == message.mValue;
90         }
91 
92         @Override
hashCode()93         public int hashCode() {
94             return Objects.hash(mType, mValue);
95         }
96 
97         @Override
toString()98         public String toString() {
99             return "Message{" + "mType=" + messageToString(mType) +", mValue="
100                     + valueToString(mType, mValue) + '}';
101         }
102     }
103 
104     private boolean mIsNegotiated;
105     private boolean mIsNegotiationAttempted;
106     private TransportProtocol mActiveTransport;
107     private List<TransportProtocol> mTransportProtocols = new ArrayList<>();
108     private Callback mCallback;
109 
Communicator(@onNull List<TransportProtocol> transportProtocols, @NonNull Callback callback)110     public Communicator(@NonNull List<TransportProtocol> transportProtocols,
111             @NonNull Callback callback) {
112         Log.i(this, "Initializing communicator with transports: %s",
113                 transportProtocols.stream().map(p -> p.getClass().getSimpleName()).collect(
114                         Collectors.joining(",")));
115         mTransportProtocols.addAll(transportProtocols);
116         mTransportProtocols.forEach(p -> p.setCallback(this));
117         mCallback = callback;
118     }
119 
120     /**
121      * @return the active {@link TransportProtocol} which is being used for sending/receiving
122      * messages.
123      */
getActiveTransport()124     public @Nullable TransportProtocol getActiveTransport() {
125         return mActiveTransport;
126     }
127 
128     /**
129      * Handles state changes for a call.
130      * @param id The call in question.
131      * @param state The new state.
132      */
onStateChanged(String id, @Connection.ConnectionState int state)133     public void onStateChanged(String id, @Connection.ConnectionState int state) {
134         Log.i(this, "onStateChanged: id=%s, newState=%d", id, state);
135         if (state == Connection.STATE_ACTIVE) {
136             // Protocol negotiation can start as we are active
137             if (mActiveTransport == null && !mIsNegotiationAttempted) {
138                 mIsNegotiated = false;
139                 mIsNegotiationAttempted = true;
140                 Log.i(this, "onStateChanged: call active; negotiate D2D.");
141                 negotiateNextProtocol();
142             }
143         }
144     }
145 
146     /**
147      * Called by a {@link TransportProtocol} when negotiation of that protocol has succeeded.
148      * @param protocol The protocol.
149      */
150     @Override
onNegotiationSuccess(@onNull TransportProtocol protocol)151     public void onNegotiationSuccess(@NonNull TransportProtocol protocol) {
152         if (protocol != mActiveTransport) {
153             // Uh oh, shouldn't happen.
154             String activeTransportName = mActiveTransport == null ? "none"
155                     : mActiveTransport.getClass().getSimpleName();
156             Log.w(this, "onNegotiationSuccess: ignored - %s negotiated but active transport is %s.",
157                     protocol.getClass().getSimpleName(), activeTransportName);
158         }
159         Log.i(this, "onNegotiationSuccess: %s negotiated; setting active.",
160                 protocol.getClass().getSimpleName());
161         mIsNegotiated = true;
162         notifyD2DStatus(true /* isAvailable */);
163     }
164 
165     /**
166      * Called by a {@link TransportProtocol} when negotiation of that protocol has failed.
167      * @param protocol The protocol.
168      */
169     @Override
onNegotiationFailed(@onNull TransportProtocol protocol)170     public void onNegotiationFailed(@NonNull TransportProtocol protocol) {
171         if (protocol != mActiveTransport) {
172             // Uh oh, shouldn't happen.
173         }
174         Log.i(this, "onNegotiationFailed: %s failed to negotiate.",
175                 protocol.getClass().getSimpleName());
176         mIsNegotiated = false;
177         negotiateNextProtocol();
178     }
179 
180     /**
181      * Called by a {@link TransportProtocol} to report incoming messages received via that
182      * transport.
183      * @param messages The received messages.
184      */
185     @Override
onMessagesReceived(@onNull Set<Message> messages)186     public void onMessagesReceived(@NonNull Set<Message> messages) {
187         if (mCallback != null) {
188             mCallback.onMessagesReceived(messages);
189         }
190     }
191 
192     /**
193      * Use the {@link Communicator} to send a set of device-to-device messages.
194      * @param messages The {@link Message}s to send.
195      */
sendMessages(@onNull Set<Message> messages)196     public void sendMessages(@NonNull Set<Message> messages) {
197         if (mActiveTransport == null || !mIsNegotiated) {
198             Log.w(this, "sendMessages: no active transport");
199             return;
200         }
201 
202         Log.i(this, "sendMessages: msgs=%d, activeTransport=%s",
203                 messages.size(), mActiveTransport.getClass().getSimpleName());
204         mActiveTransport.sendMessages(messages);
205     }
206 
207     /**
208      * Find a new protocol to use and start negotiation.
209      */
negotiateNextProtocol()210     private void negotiateNextProtocol() {
211         mActiveTransport = getNextCandidateProtocol();
212         if (mActiveTransport == null) {
213             // No more protocols, exit.
214             Log.i(this, "negotiateNextProtocol: no remaining transports.");
215             notifyD2DStatus(false /* isAvailable */);
216             return;
217         }
218         Log.i(this, "negotiateNextProtocol: trying %s",
219                 mActiveTransport.getClass().getSimpleName());
220         mActiveTransport.startNegotiation();
221     }
222 
223     /**
224      * @return the next protocol to attempt to use.  If there is no active protocol, use the first
225      * one; otherwise use the one after the currently active one.
226      */
getNextCandidateProtocol()227     private TransportProtocol getNextCandidateProtocol() {
228         TransportProtocol candidateProtocol = null;
229         if (mActiveTransport == null) {
230             if (mTransportProtocols.size() > 0) {
231                 candidateProtocol = mTransportProtocols.get(0);
232             } else {
233                 mIsNegotiated = false;
234             }
235         } else {
236             for (int ix = 0; ix < mTransportProtocols.size(); ix++) {
237                 TransportProtocol protocol = mTransportProtocols.get(ix);
238                 if (protocol == mActiveTransport) {
239                     if (ix + 1 < mTransportProtocols.size()) {
240                         // Next one is candidate
241                         candidateProtocol = mTransportProtocols.get(ix + 1);
242                     }
243                     break;
244                 }
245             }
246         }
247         return candidateProtocol;
248     }
249 
250     /**
251      * Notifies listeners (okay, {@link com.android.services.telephony.TelephonyConnection} when
252      * the availability of D2D communication changes.
253      * @param isAvailable {@code true} if D2D is available, {@code false} otherwise.
254      */
notifyD2DStatus(boolean isAvailable)255     private void notifyD2DStatus(boolean isAvailable) {
256         if (mCallback != null) {
257             mCallback.onD2DAvailabilitychanged(isAvailable);
258         }
259     }
260 
messageToString(int messageType)261     public static String messageToString(int messageType) {
262         switch (messageType) {
263             case MESSAGE_CALL_RADIO_ACCESS_TYPE:
264                 return "MESSAGE_CALL_RADIO_ACCESS_TYPE";
265             case MESSAGE_CALL_AUDIO_CODEC:
266                 return "MESSAGE_CALL_AUDIO_CODEC";
267             case MESSAGE_DEVICE_BATTERY_STATE:
268                 return "MESSAGE_DEVICE_BATTERY_STATE";
269             case MESSAGE_DEVICE_NETWORK_COVERAGE:
270                 return "MESSAGE_DEVICE_NETWORK_COVERAGE";
271         }
272         return "";
273     }
274 
valueToString(int messageType, int value)275     public static String valueToString(int messageType, int value) {
276         switch (messageType) {
277             case MESSAGE_CALL_RADIO_ACCESS_TYPE:
278                 switch (value) {
279                     case RADIO_ACCESS_TYPE_LTE:
280                         return "RADIO_ACCESS_TYPE_LTE";
281                     case RADIO_ACCESS_TYPE_IWLAN:
282                         return "RADIO_ACCESS_TYPE_IWLAN";
283                     case RADIO_ACCESS_TYPE_NR:
284                         return "RADIO_ACCESS_TYPE_NR";
285                 }
286                 return "";
287             case MESSAGE_CALL_AUDIO_CODEC:
288                 switch (value) {
289                     case AUDIO_CODEC_EVS:
290                         return "AUDIO_CODEC_EVS";
291                     case AUDIO_CODEC_AMR_WB:
292                         return "AUDIO_CODEC_AMR_WB";
293                     case AUDIO_CODEC_AMR_NB:
294                         return "AUDIO_CODEC_AMR_NB";
295                 }
296                 return "";
297             case MESSAGE_DEVICE_BATTERY_STATE:
298                 switch (value) {
299                     case BATTERY_STATE_LOW:
300                         return "BATTERY_STATE_LOW";
301                     case BATTERY_STATE_GOOD:
302                         return "BATTERY_STATE_GOOD";
303                     case BATTERY_STATE_CHARGING:
304                         return "BATTERY_STATE_CHARGING";
305                 }
306                 return "";
307             case MESSAGE_DEVICE_NETWORK_COVERAGE:
308                 switch (value) {
309                     case COVERAGE_POOR:
310                         return "COVERAGE_POOR";
311                     case COVERAGE_GOOD:
312                         return "COVERAGE_GOOD";
313                 }
314                 return "";
315         }
316         return "";
317     }
318 
319     /**
320      * Test method used to force a transport type to be the active transport.
321      * @param transport The transport to activate.
322      */
setTransportActive(@onNull String transport)323     public void setTransportActive(@NonNull String transport) {
324         Optional<TransportProtocol> tp = mTransportProtocols.stream()
325                 .filter(t -> t.getClass().getSimpleName().equals(transport))
326                 .findFirst();
327         if (!tp.isPresent()) {
328             Log.w(this, "setTransportActive: %s is not a valid transport.");
329             return;
330         }
331 
332         mTransportProtocols.stream()
333                 .filter(p -> p != tp.get())
334                 .forEach(t -> t.forceNotNegotiated());
335         tp.get().forceNegotiated();
336         mActiveTransport = tp.get();
337         mIsNegotiated = true;
338         Log.i(this, "setTransportActive: %s has been forced active.", transport);
339     }
340 
341     /**
342      * @return the list of {@link TransportProtocol} which are configured at the current time.
343      */
getTransportProtocols()344     public @NonNull List<TransportProtocol> getTransportProtocols() {
345         return mTransportProtocols;
346     }
347 }
348