1 package com.example.imsmediatestingapp;
2 
3 import android.annotation.SuppressLint;
4 import android.content.Context;
5 import android.content.SharedPreferences;
6 import android.graphics.SurfaceTexture;
7 import android.hardware.radio.ims.media.AmrMode;
8 import android.hardware.radio.ims.media.CodecType;
9 import android.hardware.radio.ims.media.EvsBandwidth;
10 import android.hardware.radio.ims.media.EvsMode;
11 import android.media.AudioManager;
12 import android.net.ConnectivityManager;
13 import android.net.LinkProperties;
14 import android.net.Network;
15 import android.net.wifi.WifiInfo;
16 import android.net.wifi.WifiManager;
17 import android.os.Bundle;
18 import android.telephony.AccessNetworkConstants.AccessNetworkType;
19 import android.telephony.CallQuality;
20 import android.telephony.ims.RtpHeaderExtension;
21 import android.telephony.imsmedia.AmrParams;
22 import android.telephony.imsmedia.AudioConfig;
23 import android.telephony.imsmedia.AudioSessionCallback;
24 import android.telephony.imsmedia.EvsParams;
25 import android.telephony.imsmedia.IImsMediaCallback;
26 import android.telephony.imsmedia.ImsAudioSession;
27 import android.telephony.imsmedia.ImsMediaManager;
28 import android.telephony.imsmedia.ImsMediaSession;
29 import android.telephony.imsmedia.ImsTextSession;
30 import android.telephony.imsmedia.ImsVideoSession;
31 import android.telephony.imsmedia.MediaQualityStatus;
32 import android.telephony.imsmedia.MediaQualityThreshold;
33 import android.telephony.imsmedia.RtcpConfig;
34 import android.telephony.imsmedia.RtpConfig;
35 import android.telephony.imsmedia.RtpReceptionStats;
36 import android.telephony.imsmedia.TextConfig;
37 import android.telephony.imsmedia.TextSessionCallback;
38 import android.telephony.imsmedia.VideoConfig;
39 import android.telephony.imsmedia.VideoSessionCallback;
40 import android.text.format.Formatter;
41 import android.util.Log;
42 import android.view.Gravity;
43 import android.view.Menu;
44 import android.view.MenuItem;
45 import android.view.Surface;
46 import android.view.TextureView;
47 import android.view.View;
48 import android.view.ViewGroup;
49 import android.widget.AdapterView;
50 import android.widget.ArrayAdapter;
51 import android.widget.Button;
52 import android.widget.EditText;
53 import android.widget.FrameLayout;
54 import android.widget.LinearLayout;
55 import android.widget.ListView;
56 import android.widget.PopupMenu;
57 import android.widget.Spinner;
58 import android.widget.TextView;
59 import android.widget.Toast;
60 
61 import androidx.annotation.NonNull;
62 import androidx.appcompat.app.AppCompatActivity;
63 import androidx.appcompat.widget.SwitchCompat;
64 
65 import java.io.IOException;
66 import java.net.DatagramSocket;
67 import java.net.InetAddress;
68 import java.net.InetSocketAddress;
69 import java.net.SocketException;
70 import java.net.UnknownHostException;
71 import java.util.ArrayList;
72 import java.util.HashSet;
73 import java.util.List;
74 import java.util.Set;
75 import java.util.concurrent.Executor;
76 import java.util.concurrent.Executors;
77 
78 /**
79  * The MainActivity is the main and default layout for the application.
80  */
81 public class MainActivity extends AppCompatActivity {
82     private SharedPreferences prefs;
83     private SharedPreferences.Editor editor;
84     private SharedPrefsHandler prefsHandler;
85     public static final String PREF_NAME = "preferences";
86     private static final String HANDSHAKE_PORT_PREF = "HANDSHAKE_PORT_OPEN";
87     private static final String CONFIRMATION_MESSAGE = "CONNECTED";
88     private static final String TAG = MainActivity.class.getName();
89 
90     private static final int MAX_MTU_BYTES = 1500;
91     private static final int DSCP = 0;
92     private static final int AUDIO_RX_PAYLOAD_TYPE_NUMBER = 96;
93     private static final int AUDIO_TX_PAYLOAD_TYPE_NUMBER = 96;
94     private static final int VIDEO_RX_PAYLOAD_TYPE_NUMBER = 106;
95     private static final int VIDEO_TX_PAYLOAD_TYPE_NUMBER = 106;
96     private static final int TEXT_REDUNDANT_PAYLOAD_TYPE_NUMBER = 111;
97     private static final int TEXT_RX_PAYLOAD_TYPE_NUMBER = 112;
98     private static final int TEXT_TX_PAYLOAD_TYPE_NUMBER = 112;
99     private static final int SAMPLING_RATE_KHZ = 16;
100     private static final int P_TIME_MILLIS = 20;
101     private static final int MAX_P_TIME_MILLIS = 240;
102     private static final int DTMF_PAYLOAD_TYPE_NUMBER = 100;
103     private static final int DTMF_SAMPLING_RATE_KHZ = 16;
104     private static final int DTMF_DURATION = 140;
105     private static final int IDR_INTERVAL = 1;
106     private static final int RESOLUTION_WIDTH = 480;
107     private static final int RESOLUTION_HEIGHT = 640;
108     private static final String IMAGE = "data/user_de/0/com.android.telephony.imsmedia/test.jpg";
109     private static final float DISABLED_ALPHA = 0.3f;
110     private static final float ENABLED_ALPHA = 1.0f;
111     private static final int VIDEO_FRAMERATE = 15;
112     private static final int VIDEO_BITRATE = 384;
113     private static final int CAMERA_ID = 0;
114     private static final int CAMERA_ZOOM = 10;
115 
116     private static final int[] RTP_TIMEOUT = { 10000, 20000 };
117     private static final int RTCP_TIMEOUT = 15000;
118     private static final int RTP_HYSTERESIS_TIME = 3000;
119     private static final int RTP_PACKET_LOSS_DURATION = 3000;
120     private static final int[] PACKET_LOSS_RATE = { 1, 3 };
121     private static final int[] JITTER_THRESHOLD = { 100, 200 };
122     private static final boolean NOTIFY_STATUS = false;
123     private static final int VIDEO_BITRATE_THRESHOLD_BPS = 100000;
124 
125     private Set<Integer> mSelectedCodecTypes = new HashSet<>();
126     private Set<Integer> mSelectedAmrModes = new HashSet<>();
127     private Set<Integer> mSelectedEvsBandwidths = new HashSet<>();
128     private Set<Integer> mSelectedEvsModes = new HashSet<>();
129     private int mSelectedVideoCodec = VideoConfig.VIDEO_CODEC_AVC;
130     private int mSelectedVideoMode = VideoConfig.VIDEO_MODE_RECORDING;
131     private int mSelectedFramerate = VIDEO_FRAMERATE;
132     private int mSelectedBitrate = VIDEO_BITRATE;
133     private int mSelectedCodecProfile = VideoConfig.AVC_PROFILE_BASELINE;
134     private int mSelectedCodecLevel = VideoConfig.AVC_LEVEL_12;
135     private int mSelectedCameraId = CAMERA_ID;
136     private int mSelectedCameraZoom = CAMERA_ZOOM;
137     private int mSelectedDeviceOrientationDegree = 0;
138     private int mSelectedCvoValue = -1;
139     private String mSelectedVideoResolution = "VGA_PR";
140     private Set<Integer> mSelectedRtcpFbTypes = new HashSet<>();
141 
142     // The order of these values determines the priority in which they would be
143     // selected if there
144     // is a common match between the two devices' selections during the handshake
145     // process.
146     private static final int[] CODEC_ORDER = new int[] { CodecType.AMR, CodecType.AMR_WB,
147             CodecType.EVS, CodecType.PCMA, CodecType.PCMU };
148     private static final int[] EVS_BANDWIDTH_ORDER = new int[] { EvsBandwidth.NONE,
149             EvsBandwidth.NARROW_BAND, EvsBandwidth.WIDE_BAND, EvsBandwidth.SUPER_WIDE_BAND,
150             EvsBandwidth.FULL_BAND };
151     private static final int[] AMR_MODE_ORDER = new int[] { AmrMode.AMR_MODE_0, AmrMode.AMR_MODE_1,
152             AmrMode.AMR_MODE_2, AmrMode.AMR_MODE_3, AmrMode.AMR_MODE_4, AmrMode.AMR_MODE_5,
153             AmrMode.AMR_MODE_6, AmrMode.AMR_MODE_7, AmrMode.AMR_MODE_8 };
154     private static final int[] EVS_MODE_ORDER = new int[] { EvsMode.EVS_MODE_0, EvsMode.EVS_MODE_1,
155             EvsMode.EVS_MODE_2, EvsMode.EVS_MODE_3, EvsMode.EVS_MODE_4, EvsMode.EVS_MODE_5,
156             EvsMode.EVS_MODE_6, EvsMode.EVS_MODE_7, EvsMode.EVS_MODE_8, EvsMode.EVS_MODE_9,
157             EvsMode.EVS_MODE_10, EvsMode.EVS_MODE_11, EvsMode.EVS_MODE_12, EvsMode.EVS_MODE_13,
158             EvsMode.EVS_MODE_14, EvsMode.EVS_MODE_15, EvsMode.EVS_MODE_16, EvsMode.EVS_MODE_17,
159             EvsMode.EVS_MODE_18, EvsMode.EVS_MODE_19, EvsMode.EVS_MODE_20 };
160 
161     private boolean mLoopbackModeEnabled = false;
162     private boolean mIsMediaManagerReady = false;
163     private boolean mIsOpenSessionSent = false;
164     private boolean mIsVideoSessionOpened = false;
165     private boolean mIsTextSessionOpened = false;
166     private boolean mIsPreviewSurfaceSet = false;
167     private boolean mIsDisplaySurfaceSet = false;
168     private boolean mVideoEnabled = false;
169     private boolean mTextEnabled = false;
170     private final StringBuilder mDtmfInput = new StringBuilder();
171 
172     private ConnectionStatus mConnectionStatus;
173     private ImsAudioSession mAudioSession;
174     private ImsVideoSession mVideoSession;
175     private ImsTextSession mTextSession;
176     private AudioConfig mAudioConfig;
177     private VideoConfig mVideoConfig;
178     private TextConfig mTextConfig;
179     private ImsMediaManager mImsMediaManager;
180     private Executor mExecutor;
181     private Thread mWaitForHandshakeThread;
182     private HandshakeReceiver mHandshakeReceptionSocket;
183     private DatagramSocket mAudioRtp;
184     private DatagramSocket mAudioRtcp;
185     private DatagramSocket mVideoRtp;
186     private DatagramSocket mVideoRtcp;
187     private DatagramSocket mTextRtp;
188     private DatagramSocket mTextRtcp;
189     private DeviceInfo mRemoteDeviceInfo;
190     private DeviceInfo mLocalDeviceInfo;
191     private BottomSheetDialer mBottomSheetDialog;
192     private BottomSheetAudioCodecSettings mBottomSheetAudioCodecSettings;
193     private TextView mLocalHandshakePortLabel;
194     private TextView mLocalRtpPortLabel;
195     private TextView mLocalRtcpPortLabel;
196     private TextView mRemoteIpLabel;
197     private TextView mRemoteHandshakePortLabel;
198     private TextView mRemoteRtpPortLabel;
199     private TextView mRemoteRtcpPortLabel;
200     private Button mAllowCallsButton;
201     private Button mConnectButton;
202     private Button mOpenSessionButton;
203     private SwitchCompat mLoopbackSwitch;
204     private LinearLayout mActiveCallToolBar;
205     private TextureView mTexturePreview;
206     private TextureView mTextureDisplay;
207     private Surface mPreviewSurface;
208     private Surface mDisplaySurface;
209     private int mDelay;
210 
211     /**
212      * Enum of the CodecType from android.hardware.radio.ims.media.CodecType with
213      * the matching
214      * Integer value.
215      */
216     public enum CodecTypeEnum {
217         AMR(CodecType.AMR),
218         AMR_WB(CodecType.AMR_WB),
219         EVS(CodecType.EVS),
220         PCMA(CodecType.PCMA),
221         PCMU(CodecType.PCMU);
222 
223         private final int mValue;
224 
CodecTypeEnum(int value)225         CodecTypeEnum(int value) {
226             mValue = value;
227         }
228 
getValue()229         public int getValue() {
230             return mValue;
231         }
232     }
233 
234     /**
235      * Enum of the AmrMode from android.hardware.radio.ims.media.AmrMode with the
236      * matching
237      * Integer value.
238      */
239     public enum AmrModeEnum {
240         AMR_MODE_0(AmrMode.AMR_MODE_0),
241         AMR_MODE_1(AmrMode.AMR_MODE_1),
242         AMR_MODE_2(AmrMode.AMR_MODE_2),
243         AMR_MODE_3(AmrMode.AMR_MODE_3),
244         AMR_MODE_4(AmrMode.AMR_MODE_4),
245         AMR_MODE_5(AmrMode.AMR_MODE_5),
246         AMR_MODE_6(AmrMode.AMR_MODE_6),
247         AMR_MODE_7(AmrMode.AMR_MODE_7),
248         AMR_MODE_8(AmrMode.AMR_MODE_8);
249 
250         private final int mValue;
251 
AmrModeEnum(int value)252         AmrModeEnum(int value) {
253             mValue = value;
254         }
255 
getValue()256         public int getValue() {
257             return mValue;
258         }
259 
260     }
261 
262     /**
263      * Enum of the EvsBandwidth from android.hardware.radio.ims.media.EvsBandwidth
264      * with the
265      * matching Integer value.
266      */
267     public enum EvsBandwidthEnum {
268         NONE(EvsBandwidth.NONE),
269         NARROW_BAND(EvsBandwidth.NARROW_BAND),
270         WIDE_BAND(EvsBandwidth.WIDE_BAND),
271         SUPER_WIDE_BAND(EvsBandwidth.SUPER_WIDE_BAND),
272         FULL_BAND(EvsBandwidth.FULL_BAND);
273 
274         private final int mValue;
275 
EvsBandwidthEnum(int value)276         EvsBandwidthEnum(int value) {
277             mValue = value;
278         }
279 
getValue()280         public int getValue() {
281             return mValue;
282         }
283     }
284 
285     /**
286      * Enum of the EvsMode from android.hardware.radio.ims.media.EvsMode with the
287      * matching
288      * Integer value.
289      */
290     public enum EvsModeEnum {
291         EVS_MODE_0(EvsMode.EVS_MODE_0),
292         EVS_MODE_1(EvsMode.EVS_MODE_1),
293         EVS_MODE_2(EvsMode.EVS_MODE_2),
294         EVS_MODE_3(EvsMode.EVS_MODE_3),
295         EVS_MODE_4(EvsMode.EVS_MODE_4),
296         EVS_MODE_5(EvsMode.EVS_MODE_5),
297         EVS_MODE_6(EvsMode.EVS_MODE_6),
298         EVS_MODE_7(EvsMode.EVS_MODE_7),
299         EVS_MODE_8(EvsMode.EVS_MODE_8),
300         EVS_MODE_9(EvsMode.EVS_MODE_9),
301         EVS_MODE_10(EvsMode.EVS_MODE_10),
302         EVS_MODE_11(EvsMode.EVS_MODE_11),
303         EVS_MODE_12(EvsMode.EVS_MODE_12),
304         EVS_MODE_13(EvsMode.EVS_MODE_13),
305         EVS_MODE_14(EvsMode.EVS_MODE_14),
306         EVS_MODE_15(EvsMode.EVS_MODE_15),
307         EVS_MODE_16(EvsMode.EVS_MODE_16),
308         EVS_MODE_17(EvsMode.EVS_MODE_17),
309         EVS_MODE_18(EvsMode.EVS_MODE_18),
310         EVS_MODE_19(EvsMode.EVS_MODE_19),
311         EVS_MODE_20(EvsMode.EVS_MODE_20);
312 
313         private final int mValue;
314 
EvsModeEnum(int value)315         EvsModeEnum(int value) {
316             mValue = value;
317         }
318 
getValue()319         public int getValue() {
320             return mValue;
321         }
322     }
323 
324     /**
325      * Enum of the video codecs from VideoConfig with the matching
326      * Integer value.
327      */
328     public enum VideoCodecEnum {
329         AVC(VideoConfig.VIDEO_CODEC_AVC),
330         HEVC(VideoConfig.VIDEO_CODEC_HEVC);
331 
332         private final int mValue;
333 
VideoCodecEnum(int value)334         VideoCodecEnum(int value) {
335             mValue = value;
336         }
337 
getValue()338         public int getValue() {
339             return mValue;
340         }
341     }
342 
343     /**
344      * Enum of the video modes from VideoConfig with the matching
345      * Integer value.
346      */
347     public enum VideoModeEnum {
348         VIDEO_MODE_PREVIEW(VideoConfig.VIDEO_MODE_PREVIEW),
349         VIDEO_MODE_RECORDING(VideoConfig.VIDEO_MODE_RECORDING),
350         VIDEO_MODE_PAUSE_IMAGE(VideoConfig.VIDEO_MODE_PAUSE_IMAGE);
351 
352         private final int mValue;
353 
VideoModeEnum(int value)354         VideoModeEnum(int value) {
355             mValue = value;
356         }
357 
getValue()358         public int getValue() {
359             return mValue;
360         }
361     }
362 
363     /**
364      * Enum of the video codec profiles from VideoConfig with the matching
365      * Integer value.
366      */
367     public enum VideoCodecProfileEnum {
368         CODEC_PROFILE_NONE(VideoConfig.CODEC_PROFILE_NONE),
369         AVC_PROFILE_BASELINE(VideoConfig.AVC_PROFILE_BASELINE),
370         AVC_PROFILE_CONSTRAINED_BASELINE(VideoConfig.AVC_PROFILE_CONSTRAINED_BASELINE),
371         AVC_PROFILE_CONSTRAINED_HIGH(VideoConfig.AVC_PROFILE_CONSTRAINED_HIGH),
372         AVC_PROFILE_HIGH(VideoConfig.AVC_PROFILE_HIGH),
373         AVC_PROFILE_MAIN(VideoConfig.AVC_PROFILE_MAIN),
374         HEVC_PROFILE_MAIN(VideoConfig.HEVC_PROFILE_MAIN),
375         HEVC_PROFILE_MAIN10(VideoConfig.HEVC_PROFILE_MAIN10);
376 
377         private final int mValue;
378 
VideoCodecProfileEnum(int value)379         VideoCodecProfileEnum(int value) {
380             mValue = value;
381         }
382 
getValue()383         public int getValue() {
384             return mValue;
385         }
386     }
387 
388     /**
389      * Enum of the video codec levels from VideoConfig with the matching
390      * Integer value.
391      */
392     public enum VideoCodecLevelEnum {
393         CODEC_LEVEL_NONE(VideoConfig.CODEC_LEVEL_NONE),
394         AVC_LEVEL_1(VideoConfig.AVC_LEVEL_1),
395         AVC_LEVEL_1B(VideoConfig.AVC_LEVEL_1B),
396         AVC_LEVEL_11(VideoConfig.AVC_LEVEL_11),
397         AVC_LEVEL_12(VideoConfig.AVC_LEVEL_12),
398         AVC_LEVEL_13(VideoConfig.AVC_LEVEL_13),
399         AVC_LEVEL_2(VideoConfig.AVC_LEVEL_2),
400         AVC_LEVEL_21(VideoConfig.AVC_LEVEL_21),
401         AVC_LEVEL_22(VideoConfig.AVC_LEVEL_22),
402         AVC_LEVEL_3(VideoConfig.AVC_LEVEL_3),
403         AVC_LEVEL_31(VideoConfig.AVC_LEVEL_31),
404         HEVC_HIGHTIER_LEVEL_1(VideoConfig.HEVC_HIGHTIER_LEVEL_1),
405         HEVC_HIGHTIER_LEVEL_2(VideoConfig.HEVC_HIGHTIER_LEVEL_2),
406         HEVC_HIGHTIER_LEVEL_21(VideoConfig.HEVC_HIGHTIER_LEVEL_21),
407         HEVC_HIGHTIER_LEVEL_3(VideoConfig.HEVC_HIGHTIER_LEVEL_3),
408         HEVC_HIGHTIER_LEVEL_31(VideoConfig.HEVC_HIGHTIER_LEVEL_31),
409         HEVC_HIGHTIER_LEVEL_4(VideoConfig.HEVC_HIGHTIER_LEVEL_4),
410         HEVC_HIGHTIER_LEVEL_41(VideoConfig.HEVC_HIGHTIER_LEVEL_41),
411         HEVC_MAINTIER_LEVEL_1(VideoConfig.HEVC_MAINTIER_LEVEL_1),
412         HEVC_MAINTIER_LEVEL_2(VideoConfig.HEVC_MAINTIER_LEVEL_2),
413         HEVC_MAINTIER_LEVEL_21(VideoConfig.HEVC_MAINTIER_LEVEL_21),
414         HEVC_MAINTIER_LEVEL_3(VideoConfig.HEVC_MAINTIER_LEVEL_3),
415         HEVC_MAINTIER_LEVEL_31(VideoConfig.HEVC_MAINTIER_LEVEL_31),
416         HEVC_MAINTIER_LEVEL_4(VideoConfig.HEVC_MAINTIER_LEVEL_4),
417         HEVC_MAINTIER_LEVEL_41(VideoConfig.HEVC_MAINTIER_LEVEL_41);
418 
419         private final int mValue;
420 
VideoCodecLevelEnum(int value)421         VideoCodecLevelEnum(int value) {
422             mValue = value;
423         }
424 
getValue()425         public int getValue() {
426             return mValue;
427         }
428     }
429 
430     /**
431      * Enum of the video camera ids from VideoConfig
432      * Integer value.
433      */
434     public enum VideoCameraIdEnum {
435         ID_0(0),
436         ID_1(1),
437         ID_2(2),
438         ID_3(3),
439         ID_4(4);
440 
441         private final int mValue;
442 
VideoCameraIdEnum(int value)443         VideoCameraIdEnum(int value) {
444             mValue = value;
445         }
446 
getValue()447         public int getValue() {
448             return mValue;
449         }
450     }
451 
452     /**
453      * Enum of the video zoom levels from VideoConfig
454      * Integer value.
455      */
456     public enum VideoCameraZoomEnum {
457         LEVEL_0(0),
458         LEVEL_1(1),
459         LEVEL_2(2),
460         LEVEL_3(3),
461         LEVEL_4(4),
462         LEVEL_5(5),
463         LEVEL_6(6),
464         LEVEL_7(7),
465         LEVEL_8(8),
466         LEVEL_9(9);
467 
468         private final int mValue;
469 
VideoCameraZoomEnum(int value)470         VideoCameraZoomEnum(int value) {
471             mValue = value;
472         }
473 
getValue()474         public int getValue() {
475             return mValue;
476         }
477     }
478 
479     /**
480      * Enum of the video framerate from VideoConfig
481      * Integer value.
482      */
483     public enum VideoFramerateEnum {
484         FPS_10(10),
485         FPS_15(15),
486         FPS_20(20),
487         FPS_24(24),
488         FPS_30(30);
489 
490         private final int mValue;
491 
VideoFramerateEnum(int value)492         VideoFramerateEnum(int value) {
493             mValue = value;
494         }
495 
getValue()496         public int getValue() {
497             return mValue;
498         }
499     }
500 
501     /**
502      * Enum of the video bitrate from VideoConfig
503      * Integer value.
504      */
505     public enum VideoBitrateEnum {
506         BITRATE_192kbps(192),
507         BITRATE_256kbps(256),
508         BITRATE_384kbps(384),
509         BITRATE_512kbps(512),
510         BITRATE_640kbps(640);
511 
512         private final int mValue;
513 
VideoBitrateEnum(int value)514         VideoBitrateEnum(int value) {
515             mValue = value;
516         }
517 
getValue()518         public int getValue() {
519             return mValue;
520         }
521     }
522 
523     /**
524      * Enum of the video device orientation from VideoConfig
525      * Integer value.
526      */
527     public enum VideoDeviceOrientationEnum {
528         DEGREE_0(0),
529         DEGREE_90(90),
530         DEGREE_180(180),
531         DEGREE_270(270);
532 
533         private final int mValue;
534 
VideoDeviceOrientationEnum(int value)535         VideoDeviceOrientationEnum(int value) {
536             mValue = value;
537         }
538 
getValue()539         public int getValue() {
540             return mValue;
541         }
542     }
543 
544     /**
545      * Enum of the video cvo offset value from VideoConfig
546      * Integer value.
547      */
548     public enum VideoCvoValueEnum {
549         CVO_DISABLE(-1),
550         CVO_OFFSET_1(1),
551         CVO_OFFSET_2(2),
552         CVO_OFFSET_3(3),
553         CVO_OFFSET_4(4),
554         CVO_OFFSET_5(5),
555         CVO_OFFSET_6(6),
556         CVO_OFFSET_7(7),
557         CVO_OFFSET_8(8),
558         CVO_OFFSET_9(9),
559         CVO_OFFSET_10(10),
560         CVO_OFFSET_11(11),
561         CVO_OFFSET_12(12),
562         CVO_OFFSET_13(13),
563         CVO_OFFSET_14(14);
564 
565         private final int mValue;
566 
VideoCvoValueEnum(int value)567         VideoCvoValueEnum(int value) {
568             mValue = value;
569         }
570 
getValue()571         public int getValue() {
572             return mValue;
573         }
574     }
575 
576     public String[] mVideoResolutionStrings = new String[] {
577         "HD_PR", "HD_LS", "VGA_PR", "VGA_LS", "QVGA_PR", "QVGA_LS", "SIF_PR", "SIF_LS", "CIF_PR",
578         "CIF_LS", "QCIF_PR", "QCIF_LS",
579     };
580 
581     public int[][] mVideoResolution = {
582         {720, 1280}, {1280, 720}, {480, 640}, {640, 480}, {240, 320}, {320, 240}, {240, 352},
583         {352, 240}, {288, 352}, {352, 288}, {176, 144}, {144, 176},
584     };
585 
getResolutionWidth(String resolution)586     public int getResolutionWidth(String resolution) {
587         for (int i = 0; i < mVideoResolutionStrings.length; i++) {
588             if (mVideoResolutionStrings[i].equals(resolution)) {
589                 return mVideoResolution[i][0];
590             }
591         }
592         return RESOLUTION_WIDTH;
593     }
594 
getResolutionHeight(String resolution)595     public int getResolutionHeight(String resolution) {
596         for (int i = 0; i < mVideoResolutionStrings.length; i++) {
597             if (mVideoResolutionStrings[i].equals(resolution)) {
598                 return mVideoResolution[i][1];
599             }
600         }
601         return RESOLUTION_HEIGHT;
602     }
603 
604     /**
605      * Enum of the different states the application can be in. Mainly used to decide
606      * how
607      * different features of the app UI will be styled.
608      */
609     public enum ConnectionStatus {
610         OFFLINE(0),
611         DISCONNECTED(1),
612         CONNECTING(2),
613         CONNECTED(3),
614         ACTIVE_CALL(4);
615 
616         private final int mValue;
617 
ConnectionStatus(int value)618         ConnectionStatus(int value) {
619             mValue = value;
620         }
621 
getValue()622         public int getValue() {
623             return mValue;
624         }
625     }
626 
627     @Override
onCreate(Bundle savedInstanceState)628     protected void onCreate(Bundle savedInstanceState) {
629         Log.d(TAG, "onCreate");
630         super.onCreate(savedInstanceState);
631         setContentView(R.layout.activity_main);
632 
633         prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
634         prefsHandler = new SharedPrefsHandler(prefs);
635         editor = prefs.edit();
636         editor.putBoolean(HANDSHAKE_PORT_PREF, false);
637         editor.apply();
638 
639         Context context = getApplicationContext();
640         MediaManagerCallback callback = new MediaManagerCallback();
641         mExecutor = Executors.newSingleThreadExecutor();
642         mImsMediaManager = new ImsMediaManager(context, mExecutor, callback);
643 
644         mBottomSheetDialog = new BottomSheetDialer(this);
645         mBottomSheetDialog.setContentView(R.layout.dialer);
646 
647         mBottomSheetAudioCodecSettings = new BottomSheetAudioCodecSettings(this);
648         mBottomSheetAudioCodecSettings.setContentView(R.layout.audio_codec_change);
649         updateCodecSelectionFromPrefs();
650 
651         updateUI(ConnectionStatus.OFFLINE);
652         updateAdditionalMedia();
653 
654         mAudioSession = null;
655         mVideoSession = null;
656         mTextSession = null;
657         mDelay = 100;
658     }
659 
660     @Override
onStart()661     protected void onStart() {
662         super.onStart();
663         styleMainActivity();
664     }
665 
666     @Override
onResume()667     protected void onResume() {
668         Log.d(TAG, "onResume");
669         super.onResume();
670         styleMainActivity();
671         updateAdditionalMedia();
672     }
673 
674     @Override
onDestroy()675     protected void onDestroy() {
676         super.onDestroy();
677         if (mAudioRtp != null) {
678             mAudioRtp.close();
679         }
680         if (mAudioRtcp != null) {
681             mAudioRtcp.close();
682         }
683         if (mVideoRtp != null) {
684             mVideoRtp.close();
685         }
686         if (mVideoRtcp != null) {
687             mVideoRtcp.close();
688         }
689         if (mTextRtp != null) {
690             mTextRtp.close();
691         }
692         if (mTextRtcp != null) {
693             mTextRtcp.close();
694         }
695     }
696 
697     @Override
onCreateOptionsMenu(Menu menu)698     public boolean onCreateOptionsMenu(Menu menu) {
699         getMenuInflater().inflate(R.menu.main, menu);
700         return super.onCreateOptionsMenu(menu);
701     }
702 
703     @SuppressLint("NonConstantResourceId")
704     @Override
onOptionsItemSelected(@onNull MenuItem item)705     public boolean onOptionsItemSelected(@NonNull MenuItem item) {
706         switch (item.getItemId()) {
707             case R.id.homeMenuButton:
708                 setContentView(R.layout.activity_main);
709                 styleMainActivity();
710                 break;
711 
712             case R.id.settingsMenuButton:
713                 setContentView(R.layout.settings);
714                 setupSettingsPage();
715                 break;
716 
717             case R.id.settingsVideoMenuButton:
718                 setContentView(R.layout.settings_video);
719                 setupVideoSettingsPage();
720                 break;
721 
722             default:
723                 throw new IllegalStateException("Unexpected value: " + item.getItemId());
724         }
725         return super.onOptionsItemSelected(item);
726     }
727 
728     private class MediaManagerCallback implements ImsMediaManager.OnConnectedCallback {
729 
730         @Override
onConnected()731         public void onConnected() {
732             Log.d(TAG, "ImsMediaManager - connected");
733             mIsMediaManagerReady = true;
734         }
735 
736         @Override
onDisconnected()737         public void onDisconnected() {
738             Log.d(TAG, "ImsMediaManager - disconnected");
739             mIsMediaManagerReady = false;
740             updateUI(ConnectionStatus.CONNECTED);
741         }
742     }
743 
744     private class RtpAudioSessionCallback extends AudioSessionCallback {
745 
746         @Override
onModifySessionResponse(AudioConfig config, int result)747         public void onModifySessionResponse(AudioConfig config, int result) {
748             Log.d(TAG, "onModifySessionResponse");
749         }
750 
751         @Override
onOpenSessionFailure(int error)752         public void onOpenSessionFailure(int error) {
753             Log.e(TAG, "onOpenSessionFailure - error=" + error);
754         }
755 
756         @Override
onOpenSessionSuccess(ImsMediaSession session)757         public void onOpenSessionSuccess(ImsMediaSession session) {
758             mAudioSession = (ImsAudioSession) session;
759             Log.d(TAG, "onOpenSessionSuccess: id=" + mAudioSession.getSessionId());
760             mIsOpenSessionSent = true;
761 
762             MediaQualityThreshold threshold = createMediaQualityThreshold(RTP_TIMEOUT,
763                     RTCP_TIMEOUT, RTP_HYSTERESIS_TIME, RTP_PACKET_LOSS_DURATION, PACKET_LOSS_RATE,
764                     JITTER_THRESHOLD, NOTIFY_STATUS);
765             mAudioSession.setMediaQualityThreshold(threshold);
766             mAudioSession.modifySession(mAudioConfig);
767             mAudioSession.requestRtpReceptionStats(3000);
768 
769             AudioManager audioManager = getSystemService(AudioManager.class);
770             audioManager.setMode(AudioManager.MODE_IN_CALL);
771             updateUI(ConnectionStatus.ACTIVE_CALL);
772         }
773 
774         @Override
onAddConfigResponse(AudioConfig config, int result)775         public void onAddConfigResponse(AudioConfig config, int result) {
776             Log.d(TAG, "onAddConfigResponse");
777         }
778 
779         @Override
onConfirmConfigResponse(AudioConfig config, int result)780         public void onConfirmConfigResponse(AudioConfig config, int result) {
781             Log.d(TAG, "onConfirmConfigResponse");
782         }
783 
784         @Override
onFirstMediaPacketReceived(AudioConfig config)785         public void onFirstMediaPacketReceived(AudioConfig config) {
786             Log.d(TAG, "onFirstMediaPacketReceived");
787         }
788 
789         @Override
onHeaderExtensionReceived(final List<RtpHeaderExtension> extensions)790         public void onHeaderExtensionReceived(final List<RtpHeaderExtension> extensions) {
791             Log.d(TAG, "onHeaderExtensionReceived, list size=" + extensions.size()
792                     + "list=" + extensions);
793         }
794 
795         @Override
triggerAnbrQuery(AudioConfig config)796         public void triggerAnbrQuery(AudioConfig config) {
797             Log.d(TAG, "triggerAnbrQuery");
798         }
799 
800         @Override
onDtmfReceived(char dtmfDigit, int durationMs)801         public void onDtmfReceived(char dtmfDigit, int durationMs) {
802             Log.d(TAG, "onDtmfReceived digit: " + dtmfDigit + " duration: " + durationMs);
803         }
804 
805         @Override
notifyMediaQualityStatus(final MediaQualityStatus status)806         public void notifyMediaQualityStatus(final MediaQualityStatus status) {
807             Log.d(TAG, "notifyMediaQualityStatus, status=" + status);
808         }
809 
810         @Override
onCallQualityChanged(CallQuality callQuality)811         public void onCallQualityChanged(CallQuality callQuality) {
812             Log.d(TAG, "onCallQualityChanged, callQuality=" + callQuality);
813             Log.d(TAG, "onCallQualityChanged, discardRate="
814                     + (double) callQuality.getNumDroppedRtpPackets()
815                     / callQuality.getNumRtpPacketsReceived() * 100);
816             Log.d(TAG, "onCallQualityChanged, lossRate="
817                     + (double) callQuality.getNumRtpPacketsNotReceived()
818                     / callQuality.getNumRtpPacketsReceived() * 100);
819             Log.d(TAG, "onCallQualityChanged, maxPlayoutDelay="
820                     + (double) callQuality.getMaxPlayoutDelayMillis());
821         }
822 
823         @Override
notifyRtpReceptionStats(final RtpReceptionStats stats)824         public void notifyRtpReceptionStats(final RtpReceptionStats stats) {
825             Log.d(TAG, "notifyRtpReceptionStats, RtpReceptionStats=" + stats);
826         }
827     }
828 
829     private class RtpVideoSessionCallback extends VideoSessionCallback {
830         @Override
onOpenSessionFailure(int error)831         public void onOpenSessionFailure(int error) {
832             Log.e(TAG, "onOpenSessionFailure - error=" + error);
833         }
834 
835         @Override
onOpenSessionSuccess(ImsMediaSession session)836         public void onOpenSessionSuccess(ImsMediaSession session) {
837             mVideoSession = (ImsVideoSession) session;
838             Log.d(TAG, "onOpenSessionSuccess: id=" + mVideoSession.getSessionId());
839             mIsVideoSessionOpened = true;
840 
841             MediaQualityThreshold threshold = createMediaQualityThreshold(RTP_TIMEOUT,
842                     RTCP_TIMEOUT, RTP_HYSTERESIS_TIME, RTP_PACKET_LOSS_DURATION, PACKET_LOSS_RATE,
843                     JITTER_THRESHOLD, NOTIFY_STATUS);
844             mVideoSession.setMediaQualityThreshold(threshold);
845 
846             int rtcpfbTypes = 0;
847             for (int types : mSelectedRtcpFbTypes) {
848                 rtcpfbTypes |= types;
849             }
850 
851             mVideoConfig = createVideoConfig(mSelectedVideoCodec, mSelectedVideoMode,
852                     mSelectedFramerate, mSelectedBitrate, mSelectedCodecProfile,
853                     mSelectedCodecLevel, mSelectedCameraId, mSelectedCameraZoom,
854                     mSelectedDeviceOrientationDegree,
855                     mSelectedCvoValue, rtcpfbTypes,
856                     getResolutionWidth(mSelectedVideoResolution),
857                     getResolutionHeight(mSelectedVideoResolution));
858 
859             Log.d(TAG, "VideoConfig: " + mVideoConfig.toString());
860             mVideoSession.modifySession(mVideoConfig);
861 
862             runOnUiThread(() -> {
863                 if (mIsPreviewSurfaceSet) {
864                     mVideoSession.setPreviewSurface(mPreviewSurface);
865                 }
866                 if (mIsDisplaySurfaceSet) {
867                     mVideoSession.setDisplaySurface(mDisplaySurface);
868                 }
869             });
870         }
871 
872         @Override
onModifySessionResponse(VideoConfig config, final @ImsMediaSession.SessionOperationResult int result)873         public void onModifySessionResponse(VideoConfig config,
874                 final @ImsMediaSession.SessionOperationResult int result) {
875             Log.d(TAG, "onModifySessionResponse");
876         }
877 
878         @Override
onPeerDimensionChanged(final int width, final int height)879         public void onPeerDimensionChanged(final int width, final int height) {
880             Log.d(TAG, "onPeerDimensionChanged - width=" + width + ", height=" + height);
881         }
882 
883         @Override
notifyBitrate(final int bitrate)884         public void notifyBitrate(final int bitrate) {
885             Log.d(TAG, "notifyBitrate - bitrate=" + bitrate);
886         }
887     }
888 
889     private class RtpTextSessionCallback extends TextSessionCallback {
890         @Override
onOpenSessionFailure(int error)891         public void onOpenSessionFailure(int error) {
892             Log.e(TAG, "onOpenSessionFailure - error=" + error);
893         }
894 
895         @Override
onOpenSessionSuccess(ImsMediaSession session)896         public void onOpenSessionSuccess(ImsMediaSession session) {
897             mTextSession = (ImsTextSession) session;
898             Log.d(TAG, "onOpenSessionSuccess: id=" + mTextSession.getSessionId());
899             mIsTextSessionOpened = true;
900         }
901 
902         @Override
onRttReceived(final String text)903         public void onRttReceived(final String text) {
904             Log.d(TAG, "onRttReceived: text=" + text);
905             if (mIsTextSessionOpened) {
906                 runOnUiThread(() -> {
907                     TextView view = findViewById(R.id.receivedText);
908                     view.setText(text);
909                 });
910             }
911         }
912     }
913 
updateAdditionalMedia()914     private void updateAdditionalMedia() {
915         Log.d(TAG, "updateAdditionalMedia()");
916         ArrayList<String> listAdditionalMedia = new ArrayList<>();
917         ArrayAdapter<String> spinnerAdaptor = new ArrayAdapter<>(this,
918                 android.R.layout.simple_spinner_item, listAdditionalMedia);
919 
920         listAdditionalMedia.add(getString(R.string.media_none));
921         listAdditionalMedia.add(getString(R.string.media_video));
922         listAdditionalMedia.add(getString(R.string.media_text));
923         spinnerAdaptor.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
924 
925         Spinner spinnerAdditionalMedia = findViewById(R.id.spinnerAdditionalMedia);
926         spinnerAdditionalMedia.setAdapter(spinnerAdaptor);
927         spinnerAdditionalMedia.setOnItemSelectedListener(mAdditionalMediaListener);
928         if (mVideoEnabled) {
929             spinnerAdditionalMedia.setSelection(1);
930         } else if (mTextEnabled) {
931             spinnerAdditionalMedia.setSelection(2);
932         } else {
933             spinnerAdditionalMedia.setSelection(0);
934         }
935     }
936 
937     private AdapterView.OnItemSelectedListener mAdditionalMediaListener =
938             new AdapterView.OnItemSelectedListener() {
939                 @Override
940                 public void onItemSelected(AdapterView<?> adapterView, View view,
941                         int i, long l) {
942                     String str = adapterView.getItemAtPosition(i).toString();
943                     Log.d(TAG, "onItemSelected() str=" + str);
944                     if (str.equals(getString(R.string.media_video))) {
945                         mVideoEnabled = true;
946                         mTextEnabled = false;
947                     } else if (str.equals(getString(R.string.media_text))) {
948                         mTextEnabled = true;
949                         mVideoEnabled = false;
950                     } else {
951                         mVideoEnabled = false;
952                         mTextEnabled = false;
953                     }
954                     updateVideoUi(mVideoEnabled);
955                     updateTextUi(mTextEnabled);
956                 }
957 
958                 @Override
959                 public void onNothingSelected(AdapterView<?> adapterView) {
960 
961                 }
962             };
963 
retrieveNetworkConfig()964     private WifiInfo retrieveNetworkConfig() {
965         WifiManager wifiManager = (WifiManager) getApplication()
966                 .getSystemService(Context.WIFI_SERVICE);
967         return wifiManager.getConnectionInfo();
968     }
969 
getLocalIpAddress()970     private String getLocalIpAddress() {
971         return Formatter.formatIpAddress(retrieveNetworkConfig().getIpAddress());
972     }
973 
getOtherDeviceIp()974     private String getOtherDeviceIp() {
975         return prefs.getString("OTHER_IP_ADDRESS", "localhost");
976     }
977 
getOtherDevicePort()978     private int getOtherDevicePort() {
979         return prefs.getInt("OTHER_HANDSHAKE_PORT", -1);
980     }
981 
getRemoteDevicePortEditText()982     private int getRemoteDevicePortEditText() {
983         EditText portBox = findViewById(R.id.remotePortNumberEditText);
984         return Integer.parseInt(portBox.getText().toString());
985     }
986 
getRemoteDeviceIpEditText()987     private String getRemoteDeviceIpEditText() {
988         EditText ipBox = findViewById(R.id.remoteDeviceIpEditText);
989         return ipBox.getText().toString();
990     }
991 
getVideoRemoteDevicePortEditText()992     private int getVideoRemoteDevicePortEditText() {
993         EditText portBox = findViewById(R.id.remoteVideoPortNumberEditText);
994         return Integer.parseInt(portBox.getText().toString());
995     }
996 
getVideoRemoteDeviceIpEditText()997     private String getVideoRemoteDeviceIpEditText() {
998         EditText ipBox = findViewById(R.id.videoRemoteDeviceIpEditText);
999         return ipBox.getText().toString();
1000     }
1001 
createTextureView()1002     private void createTextureView() {
1003         Log.d(TAG, "createTextureView");
1004         mTexturePreview = (TextureView) findViewById(R.id.texturePreview);
1005         assert mTexturePreview != null;
1006         mTexturePreview.setSurfaceTextureListener(mPreviewListener);
1007         if (mTexturePreview.isAvailable()) {
1008             Log.d(TAG, "preview available");
1009             mTexturePreview.setLayoutParams(
1010                     new FrameLayout.LayoutParams(300, 400, Gravity.CENTER));
1011             mTexturePreview.setKeepScreenOn(true);
1012             mTexturePreview.getSurfaceTexture().setDefaultBufferSize(
1013                     RESOLUTION_WIDTH, RESOLUTION_HEIGHT);
1014         }
1015         mTextureDisplay = (TextureView) findViewById(R.id.textureDisplay);
1016         assert mTextureDisplay != null;
1017         mTextureDisplay.setSurfaceTextureListener(mDisplayListener);
1018         if (mTextureDisplay.isAvailable()) {
1019             Log.d(TAG, "display available");
1020             mTextureDisplay.setLayoutParams(
1021                     new FrameLayout.LayoutParams(300, 400, Gravity.CENTER));
1022             mTextureDisplay.setKeepScreenOn(true);
1023             mTextureDisplay.getSurfaceTexture().setDefaultBufferSize(
1024                     RESOLUTION_WIDTH, RESOLUTION_HEIGHT);
1025         }
1026     }
1027 
1028     /**
1029      * Opens two datagram sockets for audio rtp and rtcp, and a third for the handshake between
1030      * devices if true is passed in the parameter.
1031      *
1032      * @param openHandshakePort boolean value to open a port for the handshake.
1033      */
openPorts(boolean openHandshakePort)1034     private void openPorts(boolean openHandshakePort) {
1035         Log.d(TAG, "openPorts");
1036         mAudioRtp = createDatagramSocket(getLocalIpAddress(), 10000);
1037         mAudioRtcp = createDatagramSocket(getLocalIpAddress(),
1038                 mAudioRtp.getLocalPort() + 1);
1039         mVideoRtp = createDatagramSocket(getLocalIpAddress(), 20000);
1040         mVideoRtcp = createDatagramSocket(getLocalIpAddress(),
1041                 mVideoRtp.getLocalPort() + 1);
1042         mTextRtp = createDatagramSocket(getLocalIpAddress(), 30000);
1043         mTextRtcp = createDatagramSocket(getLocalIpAddress(),
1044                 mTextRtp.getLocalPort() + 1);
1045         if (openHandshakePort) {
1046             mHandshakeReceptionSocket = new HandshakeReceiver(prefs);
1047             mHandshakeReceptionSocket.run();
1048         }
1049     }
1050 
1051     /**
1052      * Closes the handshake, rtp, and rtcp ports if they have been opened or instantiated.
1053      */
closePorts()1054     private void closePorts() {
1055         Log.d(TAG, "closePorts");
1056         if (mHandshakeReceptionSocket != null) {
1057             mHandshakeReceptionSocket.close();
1058         }
1059         closeDatagramSocket(mAudioRtp);
1060         closeDatagramSocket(mAudioRtcp);
1061         closeDatagramSocket(mVideoRtp);
1062         closeDatagramSocket(mVideoRtcp);
1063         closeDatagramSocket(mTextRtp);
1064         closeDatagramSocket(mTextRtcp);
1065     }
1066 
createDatagramSocket(@onNull String address, int port)1067     private DatagramSocket createDatagramSocket(@NonNull String address, int port) {
1068         DatagramSocket socket = null;
1069 
1070         try {
1071             socket = new DatagramSocket(null);
1072 
1073             if (socket == null) {
1074                 Log.e(TAG, "socket not found");
1075                 return null;
1076             }
1077 
1078             socket.setReuseAddress(true);
1079             InetAddress inetAddress = createInetAddress(address);
1080             if (inetAddress == null) {
1081                 Log.e(TAG, "inetAddress not found");
1082                 closeDatagramSocket(socket);
1083                 return null;
1084             }
1085 
1086             InetSocketAddress sockAddr = new InetSocketAddress(inetAddress, port);
1087             socket.bind(sockAddr);
1088 
1089             Network network = getNetworkForIpAddress(inetAddress);
1090             if (network == null) {
1091                 Log.e(TAG, "Network not found");
1092                 closeDatagramSocket(socket);
1093                 return null;
1094             }
1095 
1096             network.bindSocket(socket);
1097         } catch (SocketException e) {
1098             Log.e(TAG, "SocketException: " + e.toString());
1099         } catch (UnknownHostException e) {
1100             Log.e(TAG, "UnknownHostException: " + e.toString());
1101         } catch (IOException e) {
1102             Log.e(TAG, "IOException: " + e.toString());
1103             closeDatagramSocket(socket);
1104         } catch (IllegalArgumentException e) {
1105             Log.e(TAG, "IllegalArgumentException: " + e.toString());
1106             closeDatagramSocket(socket);
1107         }
1108 
1109         Log.v(TAG, "DatagramSocket created");
1110         return socket;
1111     }
1112 
closeDatagramSocket(DatagramSocket socket)1113     public void closeDatagramSocket(DatagramSocket socket) {
1114         if (socket != null) {
1115             socket.close();
1116             Log.v(TAG, "DatagramSocket closed");
1117         }
1118     }
1119 
getNetworkForIpAddress(InetAddress addr)1120     private Network getNetworkForIpAddress(InetAddress addr) {
1121         ConnectivityManager cm =
1122                 getApplication().getSystemService(ConnectivityManager.class);
1123 
1124         if (cm == null) {
1125             return null;
1126         }
1127 
1128         Network[] networks = cm.getAllNetworks();
1129 
1130         if (networks == null) {
1131             return null;
1132         }
1133 
1134         for (Network network : networks) {
1135             LinkProperties lp = cm.getLinkProperties(network);
1136 
1137             if (lp == null) {
1138                 continue;
1139             }
1140 
1141             List<InetAddress> linkAddrs = lp.getAddresses();
1142             for (InetAddress linkAddr : linkAddrs) {
1143                 if (addr.equals(linkAddr)) {
1144                     return network;
1145                 }
1146             }
1147         }
1148         return null;
1149     }
1150 
createInetAddress(String address)1151     private InetAddress createInetAddress(String address) {
1152         try {
1153             return InetAddress.getByName(address);
1154         } catch (IOException e) {
1155             Log.e(TAG, "IOException: " + e.toString());
1156         }
1157 
1158         return null;
1159     }
1160 
1161     /**
1162      * texture view listener for preview
1163      */
1164     TextureView.SurfaceTextureListener mPreviewListener =
1165             new TextureView.SurfaceTextureListener() {
1166         @Override
1167         public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1168             Log.d(TAG, "onSurfaceTextureAvailable - preview, width=" + width + ",height=" + height);
1169             surface.setDefaultBufferSize(RESOLUTION_WIDTH, RESOLUTION_HEIGHT);
1170             mPreviewSurface = new Surface(surface);
1171             mIsPreviewSurfaceSet = true;
1172         }
1173 
1174         @Override
1175         public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1176             Log.d(TAG, "onSurfaceTextureSizeChanged, width=" + width + ", height=" + height);
1177         }
1178 
1179         @Override
1180         public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1181             Log.d(TAG, "onSurfaceTextureDestroyed");
1182             return false;
1183         }
1184 
1185         @Override
1186         public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1187             surface.setDefaultBufferSize(RESOLUTION_WIDTH, RESOLUTION_HEIGHT);
1188             mPreviewSurface = new Surface(surface);
1189             mIsPreviewSurfaceSet = true;
1190         }
1191     };
1192 
1193     /**
1194      * texture view listener for display
1195      */
1196     TextureView.SurfaceTextureListener mDisplayListener =
1197             new TextureView.SurfaceTextureListener() {
1198         @Override
1199         public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1200             Log.d(TAG, "onSurfaceTextureAvailable - display, width=" + width + ",height=" + height);
1201             surface.setDefaultBufferSize(RESOLUTION_WIDTH, RESOLUTION_HEIGHT);
1202             mDisplaySurface = new Surface(surface);
1203             mIsDisplaySurfaceSet = true;
1204         }
1205 
1206         @Override
1207         public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1208             Log.d(TAG, "onSurfaceTextureSizeChanged, width=" + width + ", height=" + height);
1209         }
1210 
1211         @Override
1212         public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1213             Log.d(TAG, "onSurfaceTextureDestroyed");
1214             return false;
1215         }
1216 
1217         @Override
1218         public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1219             surface.setDefaultBufferSize(RESOLUTION_WIDTH, RESOLUTION_HEIGHT);
1220             mDisplaySurface = new Surface(surface);
1221             mIsDisplaySurfaceSet = true;
1222         }
1223     };
1224 
1225     /**
1226      * After the ports are open this runnable is called to wait for in incoming handshake to pair
1227      * with the remote device.
1228      */
1229     Runnable handleIncomingHandshake = new Runnable() {
1230         @Override
1231         public void run() {
1232             try {
1233                 while (!mHandshakeReceptionSocket.isHandshakeReceived()) {
1234                     if (Thread.currentThread().isInterrupted()) {
1235                         throw new InterruptedException();
1236                     }
1237                 }
1238 
1239                 mRemoteDeviceInfo = mHandshakeReceptionSocket.getReceivedDeviceInfo();
1240 
1241                 HandshakeSender handshakeSender = new HandshakeSender(
1242                         mRemoteDeviceInfo.getInetAddress(),
1243                         mRemoteDeviceInfo.getHandshakePort());
1244                 mLocalDeviceInfo = createMyDeviceInfo();
1245                 handshakeSender.setData(mLocalDeviceInfo);
1246                 handshakeSender.run();
1247 
1248                 while (!mHandshakeReceptionSocket.isConfirmationReceived()) {
1249                     if (Thread.currentThread().isInterrupted()) {
1250                         throw new InterruptedException();
1251                     }
1252                 }
1253 
1254                 handshakeSender = new HandshakeSender(mRemoteDeviceInfo.getInetAddress(),
1255                         mRemoteDeviceInfo.getHandshakePort());
1256                 handshakeSender.setData(CONFIRMATION_MESSAGE);
1257                 handshakeSender.run();
1258                 Log.d(TAG, "Handshake has been completed. Devices are connected.");
1259                 editor.putString("OTHER_IP_ADDRESS",
1260                         mRemoteDeviceInfo.getInetAddress().getHostName());
1261                 editor.putInt("OTHER_HANDSHAKE_PORT",
1262                         mRemoteDeviceInfo.getAudioRtpPort());
1263                 editor.apply();
1264                 updateUI(ConnectionStatus.CONNECTED);
1265             } catch (InterruptedException e) {
1266                 Log.e(TAG, e.toString());
1267             }
1268 
1269         }
1270     };
1271 
1272     /**
1273      * This runnable controls the handshake process from the user that is attempting to connect to
1274      * the remote device. First it will create and send a DeviceInfo object that contains the local
1275      * devices info, and wait until it receives the remote DeviceInfo. After it receives the remote
1276      * DeviceInfo it will save it into memory and send a conformation String back, then wait until
1277      * it receives a conformation String.
1278      */
1279     Runnable initiateHandshake = new Runnable() {
1280         @Override
1281         public void run() {
1282             try {
1283                 HandshakeSender sender = new HandshakeSender(InetAddress.getByName(
1284                         getOtherDeviceIp()), getOtherDevicePort());
1285                 mLocalDeviceInfo = createMyDeviceInfo();
1286                 sender.setData(mLocalDeviceInfo);
1287                 sender.run();
1288                 mRemoteDeviceInfo = mHandshakeReceptionSocket.getReceivedDeviceInfo();
1289                 sender.setData(CONFIRMATION_MESSAGE);
1290                 sender.run();
1291                 Log.d(TAG, "Handshake successful, devices connected.");
1292                 updateUI(ConnectionStatus.CONNECTED);
1293             } catch (Exception e) {
1294                 Log.e(TAG, "initiateHandshake(): e=" + e.toString());
1295             }
1296         }
1297     };
1298 
1299     /**
1300      * Creates and returns a DeviceInfo object with the local port, ip, and audio codec settings
1301      *
1302      * @return DeviceInfo object containing the local device's information
1303      */
createMyDeviceInfo()1304     public DeviceInfo createMyDeviceInfo() {
1305         try {
1306             return new DeviceInfo.Builder()
1307                     .setInetAddress(InetAddress.getByName(getLocalIpAddress()))
1308                     .setHandshakePort(mHandshakeReceptionSocket != null
1309                             ? mHandshakeReceptionSocket.getBoundSocket() : 0)
1310                     .setAudioRtpPort(mAudioRtp.getLocalPort())
1311                     .setVideoRtpPort(mVideoRtp.getLocalPort())
1312                     .setTextRtpPort(mTextRtp.getLocalPort())
1313                     .setAudioCodecs(mSelectedCodecTypes)
1314                     .setAmrModes(mSelectedAmrModes)
1315                     .setEvsBandwidths(mSelectedEvsBandwidths)
1316                     .setEvsModes(mSelectedEvsModes)
1317                     .setVideoCodec(mSelectedVideoCodec)
1318                     .setVideoResolutionWidth(RESOLUTION_WIDTH)
1319                     .setVideoResolutionHeight(RESOLUTION_HEIGHT)
1320                     .setVideoCvoValue(mSelectedCvoValue)
1321                     .setRtcpFbTypes(mSelectedRtcpFbTypes)
1322                     .build();
1323 
1324         } catch (UnknownHostException e) {
1325             Log.e(TAG, "UnknownHostException: " + e.toString());
1326         }
1327         return null;
1328     }
1329 
1330     /**
1331      * Updates the mConnectionStatus and restyles the UI
1332      *
1333      * @param newStatus The new ConnectionStatus used to style the UI
1334      */
updateUI(ConnectionStatus newStatus)1335     public void updateUI(ConnectionStatus newStatus) {
1336         mConnectionStatus = newStatus;
1337         styleMainActivity();
1338     }
1339 
1340     /**
1341      * Creates and returns an InetSocketAddress from the remote device that is
1342      * connected to the
1343      * local device.
1344      *
1345      * @return the InetSocketAddress of the remote device
1346      */
getRemoteAudioSocketAddress()1347     private InetSocketAddress getRemoteAudioSocketAddress() {
1348         int remotePort = mRemoteDeviceInfo.getAudioRtpPort();
1349         InetAddress remoteInetAddress = mRemoteDeviceInfo.getInetAddress();
1350         return new InetSocketAddress(remoteInetAddress, remotePort);
1351     }
1352 
1353     /**
1354      * Creates and returns an InetSocketAddress from the remote device that is
1355      * connected to the
1356      * local device.
1357      *
1358      * @return the InetSocketAddress of the remote device
1359      */
getRemoteVideoSocketAddress()1360     private InetSocketAddress getRemoteVideoSocketAddress() {
1361         int remotePort = mRemoteDeviceInfo.getVideoRtpPort();
1362         InetAddress remoteInetAddress = mRemoteDeviceInfo.getInetAddress();
1363         return new InetSocketAddress(remoteInetAddress, remotePort);
1364     }
1365 
1366     /**
1367      * Creates and returns an InetSocketAddress from the remote device that is connected to the
1368      * local device.
1369      *
1370      * @return the InetSocketAddress of the remote device
1371      */
getRemoteTextSocketAddress()1372     private InetSocketAddress getRemoteTextSocketAddress() {
1373         int remotePort = mRemoteDeviceInfo.getTextRtpPort();
1374         InetAddress remoteInetAddress = mRemoteDeviceInfo.getInetAddress();
1375         return new InetSocketAddress(remoteInetAddress, remotePort);
1376     }
1377 
1378     /**
1379      * Builds and returns an RtcpConfig for the remote device that is connected to the local device.
1380      *
1381      * @return the RtcpConfig for the remote device
1382      */
getRemoteAudioRtcpConfig()1383     private RtcpConfig getRemoteAudioRtcpConfig() {
1384         return new RtcpConfig.Builder()
1385                 .setCanonicalName("audio rtcp config")
1386                 .setTransmitPort(mRemoteDeviceInfo.getAudioRtpPort() + 1)
1387                 .setIntervalSec(5)
1388                 .setRtcpXrBlockTypes(RtcpConfig.FLAG_RTCPXR_STATISTICS_SUMMARY_REPORT_BLOCK
1389                         | RtcpConfig.FLAG_RTCPXR_VOIP_METRICS_REPORT_BLOCK)
1390                 .build();
1391     }
1392 
getRemoteVideoRtcpConfig()1393     private RtcpConfig getRemoteVideoRtcpConfig() {
1394         return new RtcpConfig.Builder()
1395                 .setCanonicalName("video rtcp config")
1396                 .setTransmitPort(mRemoteDeviceInfo.getVideoRtpPort() + 1)
1397                 .setIntervalSec(5)
1398                 .setRtcpXrBlockTypes(0)
1399                 .build();
1400     }
1401 
getRemoteTextRtcpConfig()1402     private RtcpConfig getRemoteTextRtcpConfig() {
1403         return new RtcpConfig.Builder()
1404                 .setCanonicalName("text rtcp config")
1405                 .setTransmitPort(mRemoteDeviceInfo.getTextRtpPort() + 1)
1406                 .setIntervalSec(5)
1407                 .setRtcpXrBlockTypes(0)
1408                 .build();
1409     }
1410 
1411     /**
1412      * Creates and returns a new AudioConfig
1413      *
1414      * @param remoteRtpAddress - InetSocketAddress of the remote device
1415      * @param rtcpConfig       - RtcpConfig of the remove device
1416      * @param audioCodec       - the type of AudioCodec
1417      * @param amrParams        - the settings if the AudioCodec is an AMR variant
1418      * @param evsParams        - the settings if the AudioCodec is EVS
1419      * @return an AudioConfig with the given params
1420      */
createAudioConfig(InetSocketAddress remoteRtpAddress, RtcpConfig rtcpConfig, int audioCodec, AmrParams amrParams, EvsParams evsParams)1421     private AudioConfig createAudioConfig(InetSocketAddress remoteRtpAddress,
1422             RtcpConfig rtcpConfig, int audioCodec, AmrParams amrParams, EvsParams evsParams) {
1423         AudioConfig config;
1424 
1425         if (audioCodec == AudioConfig.CODEC_AMR) {
1426             config = new AudioConfig.Builder()
1427                     .setMediaDirection(RtpConfig.MEDIA_DIRECTION_SEND_RECEIVE)
1428                     .setAccessNetwork(AccessNetworkType.EUTRAN)
1429                     .setRemoteRtpAddress(remoteRtpAddress)
1430                     .setRtcpConfig(rtcpConfig)
1431                     .setDscp((byte) DSCP)
1432                     .setRxPayloadTypeNumber((byte) AUDIO_RX_PAYLOAD_TYPE_NUMBER)
1433                     .setTxPayloadTypeNumber((byte) AUDIO_TX_PAYLOAD_TYPE_NUMBER)
1434                     .setSamplingRateKHz((byte) 8)
1435                     .setPtimeMillis((byte) P_TIME_MILLIS)
1436                     .setMaxPtimeMillis((byte) MAX_P_TIME_MILLIS)
1437                     .setDtxEnabled(true)
1438                     .setTxDtmfPayloadTypeNumber((byte) DTMF_PAYLOAD_TYPE_NUMBER)
1439                     .setRxDtmfPayloadTypeNumber((byte) DTMF_PAYLOAD_TYPE_NUMBER)
1440                     .setDtmfSamplingRateKHz((byte) 8)
1441                     .setCodecType(audioCodec)
1442                     .setAmrParams(amrParams)
1443                     // TODO audio is currently only working when amr params are set as well
1444                     .setEvsParams(evsParams)
1445                     .build();
1446         } else if (audioCodec == AudioConfig.CODEC_AMR_WB) {
1447             config = new AudioConfig.Builder()
1448                     .setMediaDirection(RtpConfig.MEDIA_DIRECTION_SEND_RECEIVE)
1449                     .setAccessNetwork(AccessNetworkType.EUTRAN)
1450                     .setRemoteRtpAddress(remoteRtpAddress)
1451                     .setRtcpConfig(rtcpConfig)
1452                     .setDscp((byte) DSCP)
1453                     .setRxPayloadTypeNumber((byte) AUDIO_RX_PAYLOAD_TYPE_NUMBER)
1454                     .setTxPayloadTypeNumber((byte) AUDIO_TX_PAYLOAD_TYPE_NUMBER)
1455                     .setSamplingRateKHz((byte) SAMPLING_RATE_KHZ)
1456                     .setPtimeMillis((byte) P_TIME_MILLIS)
1457                     .setMaxPtimeMillis((byte) MAX_P_TIME_MILLIS)
1458                     .setDtxEnabled(true)
1459                     .setTxDtmfPayloadTypeNumber((byte) DTMF_PAYLOAD_TYPE_NUMBER)
1460                     .setRxDtmfPayloadTypeNumber((byte) DTMF_PAYLOAD_TYPE_NUMBER)
1461                     .setDtmfSamplingRateKHz((byte) SAMPLING_RATE_KHZ)
1462                     .setCodecType(audioCodec)
1463                     .setAmrParams(amrParams)
1464                     // TODO audio is currently only working when amr params are set as well
1465                     .setEvsParams(evsParams)
1466                     .build();
1467         } else if (audioCodec == AudioConfig.CODEC_EVS) {
1468             config = new AudioConfig.Builder()
1469                     .setMediaDirection(RtpConfig.MEDIA_DIRECTION_SEND_RECEIVE)
1470                     .setAccessNetwork(AccessNetworkType.EUTRAN)
1471                     .setRemoteRtpAddress(remoteRtpAddress)
1472                     .setRtcpConfig(rtcpConfig)
1473                     .setDscp((byte) DSCP)
1474                     .setRxPayloadTypeNumber((byte) AUDIO_RX_PAYLOAD_TYPE_NUMBER)
1475                     .setTxPayloadTypeNumber((byte) AUDIO_TX_PAYLOAD_TYPE_NUMBER)
1476                     .setSamplingRateKHz((byte) SAMPLING_RATE_KHZ)
1477                     .setPtimeMillis((byte) P_TIME_MILLIS)
1478                     .setMaxPtimeMillis((byte) MAX_P_TIME_MILLIS)
1479                     .setDtxEnabled(true)
1480                     .setTxDtmfPayloadTypeNumber((byte) DTMF_PAYLOAD_TYPE_NUMBER)
1481                     .setRxDtmfPayloadTypeNumber((byte) DTMF_PAYLOAD_TYPE_NUMBER)
1482                     .setDtmfSamplingRateKHz((byte) DTMF_SAMPLING_RATE_KHZ)
1483                     .setCodecType(audioCodec)
1484                     // TODO audio is currently only working when amr params are set as well
1485                     .setAmrParams(amrParams)
1486                     .setEvsParams(evsParams)
1487                     .build();
1488         } else {
1489             config = new AudioConfig.Builder()
1490                     .setMediaDirection(RtpConfig.MEDIA_DIRECTION_SEND_RECEIVE)
1491                     .setAccessNetwork(AccessNetworkType.EUTRAN)
1492                     .setRemoteRtpAddress(remoteRtpAddress)
1493                     .setRtcpConfig(rtcpConfig)
1494                     .setDscp((byte) DSCP)
1495                     .setRxPayloadTypeNumber((byte) AUDIO_RX_PAYLOAD_TYPE_NUMBER)
1496                     .setTxPayloadTypeNumber((byte) AUDIO_TX_PAYLOAD_TYPE_NUMBER)
1497                     .setSamplingRateKHz((byte) SAMPLING_RATE_KHZ)
1498                     .setPtimeMillis((byte) P_TIME_MILLIS)
1499                     .setMaxPtimeMillis((byte) MAX_P_TIME_MILLIS)
1500                     .setDtxEnabled(true)
1501                     .setTxDtmfPayloadTypeNumber((byte) DTMF_PAYLOAD_TYPE_NUMBER)
1502                     .setRxDtmfPayloadTypeNumber((byte) DTMF_PAYLOAD_TYPE_NUMBER)
1503                     .setDtmfSamplingRateKHz((byte) DTMF_SAMPLING_RATE_KHZ)
1504                     .setCodecType(audioCodec)
1505                     .build();
1506         }
1507         return config;
1508     }
1509 
createVideoConfig(InetSocketAddress remoteRtpAddress, RtcpConfig rtcpConfig, int codecType, int videoMode, int framerate, int bitrate, int profile, int level, int cameraId, int cameraZoom, int deviceOrientation, int cvo, int rtcpFbTypes, int width, int height)1510     private VideoConfig createVideoConfig(InetSocketAddress remoteRtpAddress,
1511             RtcpConfig rtcpConfig, int codecType, int videoMode, int framerate, int bitrate,
1512             int profile, int level, int cameraId, int cameraZoom, int deviceOrientation, int cvo,
1513             int rtcpFbTypes, int width, int height) {
1514         VideoConfig config = new VideoConfig.Builder()
1515                 .setMediaDirection(RtpConfig.MEDIA_DIRECTION_SEND_RECEIVE)
1516                 .setAccessNetwork(AccessNetworkType.EUTRAN)
1517                 .setRemoteRtpAddress(remoteRtpAddress)
1518                 .setRtcpConfig(rtcpConfig)
1519                 .setDscp((byte) DSCP)
1520                 .setRxPayloadTypeNumber((byte) VIDEO_RX_PAYLOAD_TYPE_NUMBER)
1521                 .setTxPayloadTypeNumber((byte) VIDEO_TX_PAYLOAD_TYPE_NUMBER)
1522                 .setMaxMtuBytes(MAX_MTU_BYTES)
1523                 .setSamplingRateKHz((byte) 90)
1524                 .setCodecType(codecType)
1525                 .setVideoMode(videoMode)
1526                 .setFramerate(framerate)
1527                 .setBitrate(bitrate)
1528                 .setCodecProfile(profile)
1529                 .setCodecLevel(level)
1530                 .setIntraFrameIntervalSec(IDR_INTERVAL)
1531                 .setPacketizationMode(VideoConfig.MODE_NON_INTERLEAVED)
1532                 .setCameraId(cameraId)
1533                 .setCameraZoom(cameraZoom)
1534                 .setResolutionWidth(width)
1535                 .setResolutionHeight(height)
1536                 .setPauseImagePath(IMAGE)
1537                 .setDeviceOrientationDegree(deviceOrientation)
1538                 .setCvoValue(cvo)
1539                 .setRtcpFbTypes(rtcpFbTypes)
1540                 .build();
1541         return config;
1542     }
1543 
createTextConfig(InetSocketAddress remoteRtpAddress, RtcpConfig rtcpConfig, int codecType)1544     private TextConfig createTextConfig(InetSocketAddress remoteRtpAddress,
1545             RtcpConfig rtcpConfig, int codecType) {
1546         TextConfig config = new TextConfig.Builder()
1547                 .setMediaDirection(RtpConfig.MEDIA_DIRECTION_SEND_RECEIVE)
1548                 .setAccessNetwork(AccessNetworkType.EUTRAN)
1549                 .setRemoteRtpAddress(remoteRtpAddress)
1550                 .setRtcpConfig(rtcpConfig)
1551                 .setDscp((byte) DSCP)
1552                 .setRxPayloadTypeNumber((byte) TEXT_RX_PAYLOAD_TYPE_NUMBER)
1553                 .setTxPayloadTypeNumber((byte) TEXT_TX_PAYLOAD_TYPE_NUMBER)
1554                 .setSamplingRateKHz((byte) 10)
1555                 .setCodecType(codecType)
1556                 .setBitrate(1000)
1557                 .setRedundantPayload((byte) TEXT_REDUNDANT_PAYLOAD_TYPE_NUMBER)
1558                 .setRedundantLevel((byte) 2)
1559                 .setKeepRedundantLevel(true)
1560                 .build();
1561         return config;
1562     }
1563 
createMediaQualityThreshold(int[] rtpInactivityTimerMillis, int rtcpInactivityTimerMillis, int rtpHysteresisTimeInMillis, int rtpPacketLossDurationMillis, int[] rtpPacketLossRate, int[] rtpJitterMillis, boolean notifyCurrentStatus)1564     private MediaQualityThreshold createMediaQualityThreshold(int[] rtpInactivityTimerMillis,
1565             int rtcpInactivityTimerMillis, int rtpHysteresisTimeInMillis,
1566             int rtpPacketLossDurationMillis, int[] rtpPacketLossRate, int[] rtpJitterMillis,
1567             boolean notifyCurrentStatus) {
1568         return new MediaQualityThreshold.Builder()
1569                 .setRtpInactivityTimerMillis(rtpInactivityTimerMillis)
1570                 .setRtcpInactivityTimerMillis(rtcpInactivityTimerMillis)
1571                 .setRtpHysteresisTimeInMillis(rtpHysteresisTimeInMillis)
1572                 .setRtpPacketLossDurationMillis(rtpPacketLossDurationMillis)
1573                 .setRtpPacketLossRate(rtpPacketLossRate)
1574                 .setRtpJitterMillis(rtpJitterMillis)
1575                 .setNotifyCurrentStatus(notifyCurrentStatus)
1576                 .setVideoBitrateBps(VIDEO_BITRATE_THRESHOLD_BPS)
1577                 .build();
1578     }
1579 
1580     /**
1581      * @param amrMode Integer value of the AmrMode
1582      * @return AmrParams object with the passed AmrMode value
1583      */
createAmrParams(int amrMode, boolean octateAligned, int maxRed)1584     private AmrParams createAmrParams(int amrMode, boolean octateAligned, int maxRed) {
1585         return new AmrParams.Builder()
1586             .setAmrMode(amrMode)
1587             .setOctetAligned(octateAligned)
1588             .setMaxRedundancyMillis(maxRed)
1589             .build();
1590     }
1591 
1592     /**
1593      * @param evsBand Integer value of the EvsBandwidth
1594      * @param evsMode Integer value of the EvsMode
1595      * @return EvsParams object with the passed EvsBandwidth and EvsMode
1596      */
createEvsParams(int evsBand, int evsMode)1597     private EvsParams createEvsParams(int evsBand, int evsMode) {
1598         return new EvsParams.Builder()
1599                 .setEvsbandwidth(evsBand)
1600                 .setEvsMode(evsMode)
1601                 .setChannelAwareMode((byte) 3)
1602                 .setHeaderFullOnly(true)
1603                 .setCodecModeRequest((byte) 15)
1604                 .build();
1605     }
1606 
1607     /**
1608      * Determines the audio codec to use to configure the AudioConfig object. The
1609      * function uses
1610      * the order arrays of Integers to determine the priority of a given codec,
1611      * mode, and
1612      * bandwidth. Then creates and returns a AudioConfig object containing it.
1613      *
1614      * @param localDevice  DeviceInfo object containing the local device's
1615      *                     information
1616      * @param remoteDevice DeviceInfo object containing the remote device's
1617      *                     information
1618      * @return AudioConfig containing the selected audio codec, determined by the
1619      *         algorithm
1620      */
determineAudioConfig(DeviceInfo localDevice, DeviceInfo remoteDevice)1621     private AudioConfig determineAudioConfig(DeviceInfo localDevice, DeviceInfo remoteDevice) {
1622         AmrParams amrParams = null;
1623         EvsParams evsParams = null;
1624 
1625         int selectedCodec = determineCommonCodecSettings(localDevice.getAudioCodecs(),
1626                 remoteDevice.getAudioCodecs(), CODEC_ORDER);
1627 
1628         switch (selectedCodec) {
1629             case CodecType.AMR:
1630             case CodecType.AMR_WB:
1631                 int amrMode = determineCommonCodecSettings(localDevice.getAmrModes(),
1632                     remoteDevice.getAmrModes(), AMR_MODE_ORDER);
1633                 amrParams = createAmrParams(amrMode, false, 0);
1634                 break;
1635 
1636             case CodecType.EVS:
1637                 int evsMode = determineCommonCodecSettings(localDevice.getEvsModes(),
1638                         remoteDevice.getEvsModes(), EVS_MODE_ORDER);
1639                 int evsBand = determineCommonCodecSettings(localDevice.getEvsBandwidths(),
1640                         remoteDevice.getEvsBandwidths(), EVS_BANDWIDTH_ORDER);
1641                 evsParams = createEvsParams(evsBand, evsMode);
1642                 amrParams = createAmrParams(0, false, 0);
1643                 break;
1644 
1645             case -1:
1646                 return createAudioConfig(CodecType.AMR_WB,
1647                     createAmrParams(AmrMode.AMR_MODE_4, false, 0), null);
1648         }
1649 
1650         return createAudioConfig(selectedCodec, amrParams, evsParams);
1651     }
1652 
1653     /**
1654      * Helper function used to determine the highest ranking codec, mode, or
1655      * bandwidth between
1656      * two devices.
1657      *
1658      * @param localSet     the set containing the local device's selection of
1659      *                     codecs, modes, or
1660      *                     bandwidths
1661      * @param remoteSet    the set containing the remote device's selection of
1662      *                     codecs, modes, or
1663      *                     bandwidths
1664      * @param codecSetting the Integer array containing the ranking order of the
1665      *                     different values
1666      * @return highest ranking mode, codec, bandwidth, or -1 if no match is found
1667      */
determineCommonCodecSettings(Set<Integer> localSet, Set<Integer> remoteSet, int[] codecSetting)1668     private int determineCommonCodecSettings(Set<Integer> localSet, Set<Integer> remoteSet,
1669             int[] codecSetting) {
1670         Log.d(TAG, "determineCommonCodecSettings() - localSet : " + localSet);
1671         int negotiatedMode = 0;
1672         for (int setting : codecSetting) {
1673             if (localSet.contains(setting) && remoteSet.contains(setting)) {
1674                 negotiatedMode |= setting;
1675             }
1676         }
1677         return negotiatedMode;
1678     }
1679 
1680     /**
1681      * Creates an AudioConfig object depending on the passed parameters and returns it.
1682      *
1683      * @param audioCodec Integer value of the CodecType
1684      * @param amrParams  AmrParams object to be set in the AudioConfig
1685      * @param evsParams  EvsParams object to be set in the AudioConfig
1686      * @return an AudioConfig with the passed parameters and default values.
1687      */
createAudioConfig(int audioCodec, AmrParams amrParams, EvsParams evsParams)1688     private AudioConfig createAudioConfig(int audioCodec, AmrParams amrParams,
1689             EvsParams evsParams) {
1690         AudioConfig mAudioConfig = null;
1691         // TODO - evs params must be present to hear audio currently, regardless of codec
1692         EvsParams mEvs = new EvsParams.Builder()
1693                 .setEvsbandwidth(EvsParams.EVS_BAND_NONE)
1694                 .setEvsMode(EvsParams.EVS_MODE_0)
1695                 .setChannelAwareMode((byte) 3)
1696                 .setHeaderFullOnly(true)
1697                 .setCodecModeRequest((byte) 15)
1698                 .build();
1699 
1700         switch (audioCodec) {
1701             case CodecType.AMR:
1702             case CodecType.AMR_WB:
1703                 mAudioConfig = createAudioConfig(getRemoteAudioSocketAddress(),
1704                         getRemoteAudioRtcpConfig(), audioCodec, amrParams, mEvs);
1705                 break;
1706 
1707             case CodecType.EVS:
1708                 mAudioConfig = createAudioConfig(getRemoteAudioSocketAddress(),
1709                         getRemoteAudioRtcpConfig(), audioCodec, amrParams, evsParams);
1710                 break;
1711 
1712             case CodecType.PCMA:
1713             case CodecType.PCMU:
1714                 mAudioConfig = createAudioConfig(getRemoteAudioSocketAddress(),
1715                         getRemoteAudioRtcpConfig(), audioCodec, null, null);
1716                 break;
1717 
1718         }
1719 
1720         return mAudioConfig;
1721     }
1722 
1723     /**
1724      * Creates a VideoConfig object depending on the passed parameters and returns it.
1725      *
1726      * @return a VideoConfig with the passed parameters and default values.
1727      */
createVideoConfig(int codecType, int videoMode, int framerate, int bitrate, int profile, int level, int cameraId, int cameraZoom, int deviceOrientation, int cvo, int rtcpFbTypes, int width, int height)1728     private VideoConfig createVideoConfig(int codecType, int videoMode, int framerate, int bitrate,
1729             int profile, int level, int cameraId, int cameraZoom, int deviceOrientation, int cvo,
1730             int rtcpFbTypes, int width, int height) {
1731         VideoConfig videoConfig = null;
1732 
1733         switch (codecType) {
1734             case VideoConfig.VIDEO_CODEC_AVC:
1735             case VideoConfig.VIDEO_CODEC_HEVC:
1736                 videoConfig = createVideoConfig(getRemoteVideoSocketAddress(),
1737                         getRemoteVideoRtcpConfig(), codecType, videoMode, framerate, bitrate,
1738                         profile, level, cameraId, cameraZoom, deviceOrientation, cvo, rtcpFbTypes,
1739                         width, height);
1740                 break;
1741         }
1742 
1743         return videoConfig;
1744     }
1745 
1746     /**
1747      * Creates a TextConfig object depending on the passed parameters and returns it.
1748      *
1749      * @param codecType Integer value of the text codec type
1750      * @return a TextConfig with the passed parameters and default values.
1751      */
createTextConfig(int codecType)1752     private TextConfig createTextConfig(int codecType) {
1753         TextConfig textConfig = null;
1754 
1755         switch (codecType) {
1756             case TextConfig.TEXT_T140:
1757             case TextConfig.TEXT_T140_RED:
1758                 textConfig = createTextConfig(getRemoteTextSocketAddress(),
1759                         getRemoteTextRtcpConfig(), codecType);
1760                 break;
1761         }
1762 
1763         return textConfig;
1764     }
1765 
1766     /**
1767      * Displays the dialer BottomSheetDialog when the button is clicked
1768      *
1769      * @param view the view form the button click
1770      */
openDialer(View view)1771     public void openDialer(View view) {
1772         if (!mBottomSheetDialog.isOpen()) {
1773             mBottomSheetDialog.show();
1774         }
1775     }
1776 
1777     /**
1778      * Sends a DTMF input to the current AudioSession and updates the TextView to
1779      * display the input.
1780      *
1781      * @param view the view from the button click
1782      */
sendDtmfOnClick(View view)1783     public void sendDtmfOnClick(View view) {
1784         char digit = ((Button) view).getText().toString().charAt(0);
1785         mDtmfInput.append(digit);
1786 
1787         TextView mDtmfInputBox = mBottomSheetDialog.getDtmfInput();
1788         mDtmfInputBox.setText(mDtmfInput.toString());
1789 
1790         mAudioSession.sendDtmf(digit, DTMF_DURATION);
1791     }
1792 
1793     /**
1794      * Resets the TextView containing the DTMF input
1795      *
1796      * @param view the view from the button click
1797      */
clearDtmfInputOnClick(View view)1798     public void clearDtmfInputOnClick(View view) {
1799         mDtmfInput.setLength(0);
1800         TextView mDtmfInputBox = mBottomSheetDialog.getDtmfInput();
1801         mDtmfInputBox.setText(getString(R.string.dtmfInputPlaceholder));
1802     }
1803 
1804     /**
1805      * Set a speaker mode on/off
1806      *
1807      * @param view the view from the button click
1808      */
setSpeakModeOnClick(View view)1809     public void setSpeakModeOnClick(View view) {
1810         SwitchCompat speakerMode = findViewById(R.id.speakerModeSwitch);
1811         if (speakerMode != null) {
1812             AudioManager audioManager = getSystemService(AudioManager.class);
1813             audioManager.setSpeakerphoneOn(speakerMode.isChecked());
1814         }
1815     }
1816 
1817     /**
1818      * Calls closeSession() on ImsMediaManager and resets the flag on
1819      * mIsOpenSessionSent
1820      *
1821      * @param view the view from the button click
1822      */
closeSessionOnClick(View view)1823     public void closeSessionOnClick(View view) {
1824         Log.d(TAG, "closeSessionOnClick");
1825         if (mIsOpenSessionSent) {
1826             mImsMediaManager.closeSession(mAudioSession);
1827             mIsOpenSessionSent = false;
1828         }
1829         if (mIsVideoSessionOpened) {
1830             mImsMediaManager.closeSession(mVideoSession);
1831             mIsVideoSessionOpened = false;
1832             TextureView texturePreview = findViewById(R.id.texturePreview);
1833             TextureView textureDisplay = findViewById(R.id.textureDisplay);
1834             texturePreview.setVisibility(View.GONE);
1835             textureDisplay.setVisibility(View.GONE);
1836         }
1837         if (mIsTextSessionOpened) {
1838             mImsMediaManager.closeSession(mTextSession);
1839             mIsTextSessionOpened = false;
1840         }
1841     }
1842 
1843     /**
1844      * When the button is clicked a menu is opened containing the different media
1845      * directions and
1846      * the onMenuItemClickListener is set to handle the user's selection.
1847      *
1848      * @param view The view passed in from the button that is clicked
1849      */
1850     @SuppressLint("NonConstantResourceId")
mediaDirectionOnClick(View view)1851     public void mediaDirectionOnClick(View view) {
1852         PopupMenu mediaDirectionMenu = new PopupMenu(this, findViewById(R.id.mediaDirectionButton));
1853         mediaDirectionMenu.getMenuInflater()
1854                 .inflate(R.menu.media_direction_menu, mediaDirectionMenu.getMenu());
1855         int[] direction = { 0 };
1856         mediaDirectionMenu.setOnMenuItemClickListener(item -> {
1857             switch (item.getItemId()) {
1858                 case R.id.noFlowDirectionMenuItem:
1859                     direction[0] = RtpConfig.MEDIA_DIRECTION_NO_FLOW;
1860                     break;
1861                 case R.id.sendReceiveDirectionMenuItem:
1862                     direction[0] = RtpConfig.MEDIA_DIRECTION_SEND_RECEIVE;
1863                     break;
1864                 case R.id.receiveOnlyDirectionMenuItem:
1865                     direction[0] = RtpConfig.MEDIA_DIRECTION_RECEIVE_ONLY;
1866                     break;
1867                 case R.id.sendOnlyDirectionMenuItem:
1868                     direction[0] = RtpConfig.MEDIA_DIRECTION_SEND_ONLY;
1869                     break;
1870                 case R.id.inactiveDirectionMenuItem:
1871                     direction[0] = RtpConfig.MEDIA_DIRECTION_INACTIVE;
1872                     break;
1873                 default:
1874                     return false;
1875             }
1876             mAudioConfig.setMediaDirection(direction[0]);
1877             mAudioSession.modifySession(mAudioConfig);
1878             if (mIsVideoSessionOpened) {
1879                 mVideoConfig.setMediaDirection(direction[0]);
1880                 mVideoSession.modifySession(mVideoConfig);
1881             }
1882             if (mIsTextSessionOpened) {
1883                 mTextConfig.setMediaDirection(direction[0]);
1884                 mTextSession.modifySession(mTextConfig);
1885             }
1886             return true;
1887         });
1888         mediaDirectionMenu.show();
1889     }
1890 
1891     /**
1892      * Displays the audio codec change BottomSheetDialog when the button is clicked
1893      *
1894      * @param view the view form the button click
1895      */
openChangeAudioCodecSheet(View view)1896     public void openChangeAudioCodecSheet(View view) {
1897         if (!mBottomSheetAudioCodecSettings.isOpen()) {
1898             mBottomSheetAudioCodecSettings.show();
1899         }
1900     }
1901 
1902     /**
1903      * Calls openSession() on the ImsMediaManager
1904      *
1905      * @param view the view from the button click
1906      */
openSessionOnClick(View view)1907     public void openSessionOnClick(View view) {
1908         Log.d(TAG, "openSessionOnClick()");
1909         if (mIsMediaManagerReady && !mIsOpenSessionSent) {
1910 
1911             Toast.makeText(getApplicationContext(), getString(R.string.connecting_call_toast_text),
1912                     Toast.LENGTH_SHORT).show();
1913 
1914             mAudioConfig = determineAudioConfig(mLocalDeviceInfo, mRemoteDeviceInfo);
1915             Log.d(TAG, "AudioConfig: " + mAudioConfig.toString());
1916 
1917             RtpAudioSessionCallback sessionAudioCallback = new RtpAudioSessionCallback();
1918             mImsMediaManager.openSession(mAudioRtp, mAudioRtcp,
1919                     ImsMediaSession.SESSION_TYPE_AUDIO,
1920                     null, mExecutor, sessionAudioCallback);
1921             Log.d(TAG, "openSession(): audio=" + mRemoteDeviceInfo.getInetAddress() + ":"
1922                     + mRemoteDeviceInfo.getAudioRtpPort());
1923 
1924             if (mVideoEnabled) {
1925                 RtpVideoSessionCallback sessionVideoCallback = new RtpVideoSessionCallback();
1926                 mImsMediaManager.openSession(mVideoRtp, mVideoRtcp,
1927                         ImsMediaSession.SESSION_TYPE_VIDEO,
1928                         null, mExecutor, sessionVideoCallback);
1929                 Log.d(TAG, "openSession(): video=" + mRemoteDeviceInfo.getInetAddress() + ":"
1930                         + mRemoteDeviceInfo.getVideoRtpPort());
1931             }
1932 
1933             if (mTextEnabled) {
1934                 mTextConfig = createTextConfig(TextConfig.TEXT_T140_RED);
1935                 Log.d(TAG, "TextConfig: " + mTextConfig.toString());
1936 
1937                 RtpTextSessionCallback sessionTextCallback = new RtpTextSessionCallback();
1938                 mImsMediaManager.openSession(mTextRtp, mTextRtcp,
1939                         ImsMediaSession.SESSION_TYPE_RTT,
1940                         mTextConfig, mExecutor, sessionTextCallback);
1941                 Log.d(TAG, "openSession(): text=" + mRemoteDeviceInfo.getInetAddress() + ":"
1942                         + mRemoteDeviceInfo.getTextRtpPort());
1943             }
1944         }
1945     }
1946 
1947     /**
1948      * Saves the inputted ip address and port number to SharedPreferences.
1949      *
1950      * @param view the view from the button click
1951      */
saveSettingsOnClick(View view)1952     public void saveSettingsOnClick(View view) {
1953         int port = getRemoteDevicePortEditText();
1954         String ip = getRemoteDeviceIpEditText();
1955         editor.putInt("OTHER_HANDSHAKE_PORT", port);
1956         editor.putString("OTHER_IP_ADDRESS", ip);
1957         editor.apply();
1958         Toast.makeText(getApplicationContext(), R.string.save_button_action_toast,
1959                 Toast.LENGTH_SHORT).show();
1960     }
1961 
1962     /**
1963      * Saves the inputted ip address and video port number to SharedPreferences.
1964      *
1965      * @param view the view from the button click
1966      */
saveVideoSettingsOnClick(View view)1967     public void saveVideoSettingsOnClick(View view) {
1968         int port = getVideoRemoteDevicePortEditText();
1969         String ip = getVideoRemoteDeviceIpEditText();
1970         editor.putInt("OTHER_HANDSHAKE_VIDEO_PORT", port);
1971         editor.putString("OTHER_VIDEO_IP_ADDRESS", ip);
1972         editor.apply();
1973 
1974         Spinner videoCodecSpinner = findViewById(R.id.spinnerVideoCodecs);
1975         Spinner videoCodecProfileSpinner = findViewById(R.id.spinnerVideoCodecProfiles);
1976         Spinner videoCodecLevelSpinner = findViewById(R.id.spinnerVideoCodecLevels);
1977         Spinner videoModeSpinner = findViewById(R.id.spinnerVideoModes);
1978         Spinner videoCameraIdSpinner = findViewById(R.id.spinnerVideoCameraIds);
1979         Spinner videoCameraZoomSpinner = findViewById(R.id.spinnerVideoCameraZoom);
1980         Spinner videoFramerateSpinner = findViewById(R.id.spinnerVideoFramerates);
1981         Spinner videoBitrateSpinner = findViewById(R.id.spinnerVideoBitrates);
1982         Spinner videoDeviceOrientationSpinner = findViewById(R.id.spinnerVideoDeviceOrientations);
1983         Spinner videoCvoValueSpinner = findViewById(R.id.spinnerVideoCvoValues);
1984         Spinner videoResolutionSpinner = (Spinner) findViewById(R.id.spinnerVideoResolution);
1985 
1986         mSelectedVideoCodec =
1987                 ((VideoCodecEnum) videoCodecSpinner.getSelectedItem()).getValue();
1988         mSelectedCodecProfile =
1989                 ((VideoCodecProfileEnum) videoCodecProfileSpinner.getSelectedItem()).getValue();
1990         mSelectedCodecLevel =
1991                 ((VideoCodecLevelEnum) videoCodecLevelSpinner.getSelectedItem()).getValue();
1992         mSelectedVideoMode =
1993                 ((VideoModeEnum) videoModeSpinner.getSelectedItem()).getValue();
1994         mSelectedCameraId =
1995                 ((VideoCameraIdEnum) videoCameraIdSpinner.getSelectedItem()).getValue();
1996         mSelectedCameraZoom =
1997                 ((VideoCameraZoomEnum) videoCameraZoomSpinner.getSelectedItem()).getValue();
1998         mSelectedFramerate =
1999                 ((VideoFramerateEnum) videoFramerateSpinner.getSelectedItem()).getValue();
2000         mSelectedBitrate =
2001                 ((VideoBitrateEnum) videoBitrateSpinner.getSelectedItem()).getValue();
2002         mSelectedDeviceOrientationDegree =
2003                 ((VideoDeviceOrientationEnum) videoDeviceOrientationSpinner
2004                 .getSelectedItem())
2005                 .getValue();
2006         mSelectedCvoValue = ((VideoCvoValueEnum) videoCvoValueSpinner.getSelectedItem())
2007                 .getValue();
2008         mSelectedVideoResolution = (String) videoResolutionSpinner.getSelectedItem();
2009         Toast.makeText(getApplicationContext(), R.string.save_button_action_toast,
2010                 Toast.LENGTH_SHORT).show();
2011     }
2012 
2013     /**
2014      * Calls modifySession to change the audio codec on the current AudioSession.
2015      * Also contains
2016      * the logic to create the new AudioConfig.
2017      *
2018      * @param view the view form the button click
2019      */
changeAudioCodecOnClick(View view)2020     public void changeAudioCodecOnClick(View view) {
2021         AudioConfig config = null;
2022         AmrParams amrParams;
2023         EvsParams evsParams;
2024         int audioCodec = mBottomSheetAudioCodecSettings.getAudioCodec();
2025 
2026         switch (audioCodec) {
2027             case CodecType.AMR:
2028             case CodecType.AMR_WB:
2029 
2030                 evsParams = new EvsParams.Builder()
2031                 .setEvsbandwidth(EvsParams.EVS_BAND_NONE)
2032                 .setEvsMode(EvsParams.EVS_MODE_0)
2033                 .setChannelAwareMode((byte) 3)
2034                 .setHeaderFullOnly(true)
2035                 .setCodecModeRequest((byte) 15)
2036                 .build();
2037 
2038                 amrParams = createAmrParams(mBottomSheetAudioCodecSettings.getAmrMode(), false, 0);
2039                 config = createAudioConfig(getRemoteAudioSocketAddress(),
2040                         getRemoteAudioRtcpConfig(), audioCodec, amrParams, evsParams);
2041                 Log.d(TAG, String.format("AudioConfig switched to Codec: %s\t Params: %s",
2042                         mBottomSheetAudioCodecSettings.getAudioCodec(),
2043                         config.getAmrParams().toString()));
2044                 break;
2045 
2046             case CodecType.EVS:
2047                 evsParams = createEvsParams(mBottomSheetAudioCodecSettings.getEvsBand(),
2048                     mBottomSheetAudioCodecSettings.getEvsMode());
2049                 amrParams = createAmrParams(0, false, 0);
2050                 config = createAudioConfig(getRemoteAudioSocketAddress(),
2051                         getRemoteAudioRtcpConfig(), audioCodec, amrParams, evsParams);
2052                 Log.d(TAG, String.format("AudioConfig switched to Codec: %s\t Params: %s",
2053                         mBottomSheetAudioCodecSettings.getAudioCodec(),
2054                         config.getEvsParams().toString()));
2055                 break;
2056 
2057             case CodecType.PCMA:
2058             case CodecType.PCMU:
2059                 config = createAudioConfig(getRemoteAudioSocketAddress(),
2060                         getRemoteAudioRtcpConfig(), audioCodec, null, null);
2061                 Log.d(TAG, String.format("AudioConfig switched to Codec: %s",
2062                         mBottomSheetAudioCodecSettings.getAudioCodec()));
2063                 break;
2064         }
2065 
2066         mAudioSession.modifySession(config);
2067         mBottomSheetAudioCodecSettings.dismiss();
2068     }
2069 
2070     /**
2071      * Changes the flag of loopback mode, changes the ConnectionStatus sate, and
2072      * restyles the UI
2073      *
2074      * @param view the view from, the button click
2075      */
loopbackOnClick(View view)2076     public void loopbackOnClick(View view) {
2077         SwitchCompat mLoopbackSwitch = findViewById(R.id.loopbackModeSwitch);
2078         if (mLoopbackSwitch.isChecked()) {
2079             mLoopbackModeEnabled = true;
2080             openPorts(true);
2081             editor.putString("OTHER_IP_ADDRESS", getLocalIpAddress()).apply();
2082             mRemoteDeviceInfo = createMyDeviceInfo();
2083             mLocalDeviceInfo = createMyDeviceInfo();
2084             updateUI(ConnectionStatus.CONNECTED);
2085             updateAdditionalMedia();
2086         } else {
2087             closePorts();
2088             mLoopbackModeEnabled = false;
2089             updateUI(ConnectionStatus.OFFLINE);
2090             updateVideoUi(false);
2091             updateTextUi(false);
2092         }
2093     }
2094 
2095     /**
2096      * Send text message to the text session
2097      *
2098      * @param view the view from, the button click
2099      */
sendRttOnClick(View view)2100     public void sendRttOnClick(View view) {
2101         if (mIsTextSessionOpened) {
2102             EditText edit = findViewById(R.id.textEditTextSending);
2103             mTextSession.sendRtt(edit.getText().toString());
2104         }
2105     }
2106 
2107     /**
2108      * Opens or closes ports and starts the waiting handshake runnable depending on
2109      * the current
2110      * state of the button.
2111      *
2112      * @param view view from the button click
2113      */
allowCallsOnClick(View view)2114     public void allowCallsOnClick(View view) {
2115         if (prefs.getBoolean(HANDSHAKE_PORT_PREF, false)) {
2116             closePorts();
2117             Log.d(TAG, "Closed handshake, rtp, and rtcp ports.");
2118 
2119             Toast.makeText(getApplicationContext(),
2120                     "Closing ports",
2121                     Toast.LENGTH_SHORT).show();
2122             updateUI(ConnectionStatus.OFFLINE);
2123         } else {
2124             openPorts(true);
2125             while (!prefs.getBoolean(HANDSHAKE_PORT_PREF, false)) {
2126             }
2127             Log.d(TAG, "Handshake, rtp, and rtcp ports have been bound.");
2128 
2129             Toast.makeText(getApplicationContext(), getString(R.string.allowing_calls_toast_text),
2130                     Toast.LENGTH_SHORT).show();
2131 
2132             mWaitForHandshakeThread = new Thread(handleIncomingHandshake);
2133             mWaitForHandshakeThread.start();
2134             updateUI(ConnectionStatus.DISCONNECTED);
2135         }
2136     }
2137 
2138     /**
2139      * Starts the handshake process runnable that attempts to connect to two device
2140      * together.
2141      *
2142      * @param view view from the button click
2143      */
initiateHandshakeOnClick(View view)2144     public void initiateHandshakeOnClick(View view) {
2145         mWaitForHandshakeThread.interrupt();
2146         Thread initiateHandshakeThread = new Thread(initiateHandshake);
2147         initiateHandshakeThread.start();
2148         updateUI(ConnectionStatus.CONNECTING);
2149     }
2150 
2151     /**
2152      * Handles the styling of the settings layout.
2153      */
setupSettingsPage()2154     public void setupSettingsPage() {
2155         EditText ipAddress = findViewById(R.id.remoteDeviceIpEditText);
2156         EditText portNumber = findViewById(R.id.remotePortNumberEditText);
2157         ipAddress.setText(prefs.getString("OTHER_IP_ADDRESS", ""));
2158         portNumber.setText(String.valueOf(prefs.getInt("OTHER_HANDSHAKE_PORT", 0)));
2159 
2160         setupAudioCodecSelectionLists();
2161         setupCodecSelectionOnClickListeners();
2162     }
2163 
getSpinnerIndex(Spinner spinner, Object value)2164     private int getSpinnerIndex(Spinner spinner, Object value) {
2165         int index = 0;
2166         for (int i = 0; i < spinner.getCount(); i++) {
2167             if (spinner.getItemAtPosition(i).equals(value)) {
2168                 index = i;
2169             }
2170         }
2171         return index;
2172     }
2173 
2174     /**
2175      * Handles the styling of the video settings layout.
2176      */
setupVideoSettingsPage()2177     public void setupVideoSettingsPage() {
2178         EditText ipAddress = findViewById(R.id.videoRemoteDeviceIpEditText);
2179         EditText portNumber = findViewById(R.id.remoteVideoPortNumberEditText);
2180         ipAddress.setText(prefs.getString("OTHER_IP_ADDRESS", ""));
2181         portNumber.setText(String.valueOf(prefs.getInt("OTHER_HANDSHAKE_PORT", 0)));
2182 
2183         setupVideoCodecSelectionLists();
2184     }
2185 
2186     /**
2187      * Gets the saved user selections for the audio codec settings and updates the UI's lists to
2188      * match.
2189      */
setupAudioCodecSelectionLists()2190     private void setupAudioCodecSelectionLists() {
2191         updateCodecSelectionFromPrefs();
2192 
2193         ArrayAdapter<CodecTypeEnum> codecTypeAdapter = new ArrayAdapter<>(
2194                 this, android.R.layout.simple_list_item_multiple_choice, CodecTypeEnum.values());
2195         ListView codecTypeList = findViewById(R.id.audioCodecList);
2196         codecTypeList.setAdapter(codecTypeAdapter);
2197         for (int i = 0; i < codecTypeAdapter.getCount(); i++) {
2198             CodecTypeEnum mode = (CodecTypeEnum) codecTypeList.getItemAtPosition(i);
2199             codecTypeList.setItemChecked(i, mSelectedCodecTypes.contains(mode.getValue()));
2200         }
2201 
2202         ArrayAdapter<EvsBandwidthEnum> evsBandAdaptor = new ArrayAdapter<>(
2203                 this, android.R.layout.simple_list_item_multiple_choice, EvsBandwidthEnum.values());
2204         ListView evsBandwidthList = findViewById(R.id.evsBandwidthsList);
2205         evsBandwidthList.setAdapter(evsBandAdaptor);
2206         for (int i = 0; i < evsBandAdaptor.getCount(); i++) {
2207             EvsBandwidthEnum mode = (EvsBandwidthEnum) evsBandwidthList.getItemAtPosition(i);
2208             evsBandwidthList.setItemChecked(i, mSelectedEvsBandwidths.contains(mode.getValue()));
2209         }
2210 
2211         ArrayAdapter<AmrModeEnum> amrModeAdapter = new ArrayAdapter<>(
2212                 this, android.R.layout.simple_list_item_multiple_choice, AmrModeEnum.values());
2213         ListView amrModesList = findViewById(R.id.amrModesList);
2214         amrModesList.setAdapter(amrModeAdapter);
2215         for (int i = 0; i < amrModeAdapter.getCount(); i++) {
2216             AmrModeEnum mode = (AmrModeEnum) amrModesList.getItemAtPosition(i);
2217             amrModesList.setItemChecked(i, mSelectedAmrModes.contains(mode.getValue()));
2218         }
2219 
2220         ArrayAdapter<EvsModeEnum> evsModeAdaptor = new ArrayAdapter<>(
2221                 this, android.R.layout.simple_list_item_multiple_choice, EvsModeEnum.values());
2222         ListView evsModeList = findViewById(R.id.evsModesList);
2223         evsModeList.setAdapter(evsModeAdaptor);
2224         for (int i = 0; i < evsModeAdaptor.getCount(); i++) {
2225             EvsModeEnum mode = (EvsModeEnum) evsModeList.getItemAtPosition(i);
2226             evsModeList.setItemChecked(i, mSelectedEvsModes.contains(mode.getValue()));
2227         }
2228     }
2229 
setupVideoCodecSelectionLists()2230     private void setupVideoCodecSelectionLists() {
2231         Spinner videoCodecSpinner = findViewById(R.id.spinnerVideoCodecs);
2232         ArrayAdapter<VideoCodecEnum> videoCodecAdaptor = new ArrayAdapter<>(
2233                 this, android.R.layout.simple_spinner_item, VideoCodecEnum.values());
2234         videoCodecAdaptor.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
2235         videoCodecSpinner.setAdapter(videoCodecAdaptor);
2236         videoCodecSpinner.setSelection(getSpinnerIndex(videoCodecSpinner, mSelectedVideoCodec));
2237 
2238         Spinner videoCodecProfileSpinner = findViewById(R.id.spinnerVideoCodecProfiles);
2239         ArrayAdapter<VideoCodecProfileEnum> videoCodecProfileAdaptor = new ArrayAdapter<>(
2240                 this, android.R.layout.simple_spinner_item, VideoCodecProfileEnum.values());
2241         videoCodecProfileAdaptor.setDropDownViewResource(
2242                 android.R.layout.simple_spinner_dropdown_item);
2243         videoCodecProfileSpinner.setAdapter(videoCodecProfileAdaptor);
2244         videoCodecProfileSpinner.setSelection(getSpinnerIndex(videoCodecProfileSpinner,
2245                 mSelectedCodecProfile));
2246 
2247         Spinner videoCodecLevelSpinner = findViewById(R.id.spinnerVideoCodecLevels);
2248         ArrayAdapter<VideoCodecLevelEnum> videoCodecLevelAdaptor = new ArrayAdapter<>(
2249                 this, android.R.layout.simple_spinner_item, VideoCodecLevelEnum.values());
2250         videoCodecLevelAdaptor.setDropDownViewResource(
2251                 android.R.layout.simple_spinner_dropdown_item);
2252         videoCodecLevelSpinner.setAdapter(videoCodecLevelAdaptor);
2253         videoCodecLevelSpinner.setSelection(getSpinnerIndex(videoCodecLevelSpinner,
2254                 mSelectedCodecLevel));
2255 
2256         Spinner videoModeSpinner = findViewById(R.id.spinnerVideoModes);
2257         ArrayAdapter<VideoModeEnum> videoModeAdapter = new ArrayAdapter<>(
2258                 this, android.R.layout.simple_spinner_item, VideoModeEnum.values());
2259         videoModeAdapter.setDropDownViewResource(
2260                 android.R.layout.simple_spinner_dropdown_item);
2261         videoModeSpinner.setAdapter(videoModeAdapter);
2262         videoModeSpinner.setSelection(getSpinnerIndex(videoModeSpinner, mSelectedVideoMode));
2263 
2264         Spinner videoCameraIdSpinner = findViewById(R.id.spinnerVideoCameraIds);
2265         ArrayAdapter<VideoCameraIdEnum> videoCameraIdAdaptor = new ArrayAdapter<>(
2266                 this, android.R.layout.simple_spinner_item, VideoCameraIdEnum.values());
2267         videoCameraIdAdaptor.setDropDownViewResource(
2268                 android.R.layout.simple_spinner_dropdown_item);
2269         videoCameraIdSpinner.setAdapter(videoCameraIdAdaptor);
2270         videoCameraIdSpinner.setSelection(getSpinnerIndex(videoCameraIdSpinner, mSelectedCameraId));
2271 
2272         Spinner videoCameraZoomSpinner = findViewById(R.id.spinnerVideoCameraZoom);
2273         ArrayAdapter<VideoCameraZoomEnum> videoCameraZoomAdaptor = new ArrayAdapter<>(
2274                 this, android.R.layout.simple_spinner_item, VideoCameraZoomEnum.values());
2275         videoCameraZoomAdaptor.setDropDownViewResource(
2276                 android.R.layout.simple_spinner_dropdown_item);
2277         videoCameraZoomSpinner.setAdapter(videoCameraZoomAdaptor);
2278         videoCameraZoomSpinner.setSelection(getSpinnerIndex(videoCameraZoomSpinner,
2279                 mSelectedCameraZoom));
2280 
2281         Spinner videoFramerateSpinner = findViewById(R.id.spinnerVideoFramerates);
2282         ArrayAdapter<VideoFramerateEnum> videoFramerateAdaptor = new ArrayAdapter<>(
2283                 this, android.R.layout.simple_spinner_item, VideoFramerateEnum.values());
2284         videoFramerateAdaptor.setDropDownViewResource(
2285                 android.R.layout.simple_spinner_dropdown_item);
2286         videoFramerateSpinner.setAdapter(videoFramerateAdaptor);
2287         videoFramerateSpinner.setSelection(getSpinnerIndex(videoFramerateSpinner,
2288                 mSelectedFramerate));
2289 
2290         Spinner videoBitrateSpinner = findViewById(R.id.spinnerVideoBitrates);
2291         ArrayAdapter<VideoBitrateEnum> videoBitrateAdaptor = new ArrayAdapter<>(
2292                 this, android.R.layout.simple_spinner_item, VideoBitrateEnum.values());
2293         videoBitrateAdaptor.setDropDownViewResource(
2294                 android.R.layout.simple_spinner_dropdown_item);
2295         videoBitrateSpinner.setAdapter(videoBitrateAdaptor);
2296         videoBitrateSpinner.setSelection(getSpinnerIndex(videoBitrateSpinner, mSelectedBitrate));
2297 
2298         Spinner videoDeviceOrientationSpinner = findViewById(R.id.spinnerVideoDeviceOrientations);
2299         ArrayAdapter<VideoDeviceOrientationEnum> videoDeviceOrientationAdaptor = new ArrayAdapter<>(
2300                 this, android.R.layout.simple_spinner_item, VideoDeviceOrientationEnum.values());
2301         videoDeviceOrientationAdaptor.setDropDownViewResource(
2302                 android.R.layout.simple_spinner_dropdown_item);
2303         videoDeviceOrientationSpinner.setAdapter(videoDeviceOrientationAdaptor);
2304         videoDeviceOrientationSpinner.setSelection(getSpinnerIndex(videoDeviceOrientationSpinner,
2305                 mSelectedDeviceOrientationDegree));
2306 
2307         Spinner videoCvoValueSpinner = findViewById(R.id.spinnerVideoCvoValues);
2308         ArrayAdapter<VideoCvoValueEnum> videoCvoValueAdaptor = new ArrayAdapter<>(
2309                 this, android.R.layout.simple_spinner_item, VideoCvoValueEnum.values());
2310         videoCvoValueAdaptor.setDropDownViewResource(
2311                 android.R.layout.simple_spinner_dropdown_item);
2312         videoCvoValueSpinner.setAdapter(videoCvoValueAdaptor);
2313         videoCvoValueSpinner.setSelection(getSpinnerIndex(videoCvoValueSpinner,
2314                 mSelectedCvoValue));
2315 
2316         Spinner videoResolutionSpinner = (Spinner) findViewById(R.id.spinnerVideoResolution);
2317         ArrayAdapter<String> videoResolutionAdapter = new ArrayAdapter<String>(this,
2318                 android.R.layout.simple_spinner_item, mVideoResolutionStrings);
2319         videoResolutionAdapter.setDropDownViewResource(
2320                 android.R.layout.simple_spinner_dropdown_item);
2321         videoResolutionSpinner.setAdapter(videoResolutionAdapter);
2322         videoResolutionSpinner.setSelection(getSpinnerIndex(videoResolutionSpinner,
2323                 mSelectedVideoResolution));
2324     }
2325 
2326     /**
2327      * Updates all of the lists containing the user's codecs selections.
2328      */
updateCodecSelectionFromPrefs()2329     private void updateCodecSelectionFromPrefs() {
2330         mSelectedCodecTypes =
2331                 prefsHandler.getIntegerSetFromPrefs(SharedPrefsHandler.CODECS_PREF);
2332         mSelectedEvsBandwidths =
2333                 prefsHandler.getIntegerSetFromPrefs(SharedPrefsHandler.EVS_BANDS_PREF);
2334         mSelectedAmrModes =
2335                 prefsHandler.getIntegerSetFromPrefs(SharedPrefsHandler.AMR_MODES_PREF);
2336         mSelectedEvsModes =
2337                 prefsHandler.getIntegerSetFromPrefs(SharedPrefsHandler.EVS_MODES_PREF);
2338     }
2339 
2340     /**
2341      * Adds onClickListeners to the 4 check box lists on the settings page, to
2342      * handle the user input
2343      * of the codec, bandwidth, and mode selections.
2344      */
setupCodecSelectionOnClickListeners()2345     public void setupCodecSelectionOnClickListeners() {
2346         ListView audioCodecList, evsBandList, amrModeList, evsModeList;
2347 
2348         audioCodecList = findViewById(R.id.audioCodecList);
2349         evsBandList = findViewById(R.id.evsBandwidthsList);
2350         amrModeList = findViewById(R.id.amrModesList);
2351         evsModeList = findViewById(R.id.evsModesList);
2352 
2353         audioCodecList.setOnItemClickListener((adapterView, view, position, id) -> {
2354             CodecTypeEnum item = (CodecTypeEnum) audioCodecList.getItemAtPosition(position);
2355 
2356             if (audioCodecList.isItemChecked(position)) {
2357                 mSelectedCodecTypes.add(item.getValue());
2358                 if (item == CodecTypeEnum.AMR || item == CodecTypeEnum.AMR_WB) {
2359                     amrModeList.setAlpha(ENABLED_ALPHA);
2360                     amrModeList.setEnabled(true);
2361                 } else if (item == CodecTypeEnum.EVS) {
2362                     evsBandList.setAlpha(ENABLED_ALPHA);
2363                     amrModeList.setEnabled(true);
2364                     evsModeList.setAlpha(ENABLED_ALPHA);
2365                     evsModeList.setEnabled(true);
2366                 }
2367             } else {
2368                 mSelectedCodecTypes.remove(item.getValue());
2369                 if (item == CodecTypeEnum.AMR || item == CodecTypeEnum.AMR_WB) {
2370                     amrModeList.setAlpha(0.3f);
2371                     amrModeList.setEnabled(false);
2372                 } else if (item == CodecTypeEnum.EVS) {
2373                     evsBandList.setAlpha(0.3f);
2374                     evsBandList.setEnabled(false);
2375                     evsModeList.setAlpha(0.3f);
2376                     evsModeList.setEnabled(false);
2377                 }
2378             }
2379 
2380             prefsHandler.saveIntegerSetToPrefs(SharedPrefsHandler.CODECS_PREF,
2381                     mSelectedCodecTypes);
2382 
2383         });
2384         evsBandList.setOnItemClickListener((adapterView, view, position, id) -> {
2385             EvsBandwidthEnum item = (EvsBandwidthEnum) evsBandList.getItemAtPosition(position);
2386 
2387             if (evsBandList.isItemChecked(position)) {
2388                 mSelectedEvsBandwidths.add(item.getValue());
2389             } else {
2390                 mSelectedEvsBandwidths.remove(item.getValue());
2391             }
2392 
2393             prefsHandler.saveIntegerSetToPrefs(SharedPrefsHandler.EVS_BANDS_PREF,
2394                     mSelectedEvsBandwidths);
2395         });
2396         evsModeList.setOnItemClickListener((adapterView, view, position, id) -> {
2397             EvsModeEnum item = (EvsModeEnum) evsModeList.getItemAtPosition(position);
2398 
2399             if (evsModeList.isItemChecked(position)) {
2400                 mSelectedEvsModes.add(item.getValue());
2401             } else {
2402                 mSelectedEvsModes.remove(item.getValue());
2403             }
2404 
2405             prefsHandler.saveIntegerSetToPrefs(SharedPrefsHandler.EVS_MODES_PREF,
2406                     mSelectedEvsModes);
2407         });
2408         amrModeList.setOnItemClickListener((adapterView, view, position, id) -> {
2409             AmrModeEnum item = (AmrModeEnum) amrModeList.getItemAtPosition(position);
2410 
2411             if (amrModeList.isItemChecked(position)) {
2412                 mSelectedAmrModes.add(item.getValue());
2413             } else {
2414                 mSelectedAmrModes.remove(item.getValue());
2415             }
2416 
2417             prefsHandler.saveIntegerSetToPrefs(SharedPrefsHandler.AMR_MODES_PREF,
2418                     mSelectedAmrModes);
2419         });
2420     }
2421 
2422     /**
2423      * Styles the main activity UI based on the current ConnectionStatus enum state.
2424      */
styleMainActivity()2425     private void styleMainActivity() {
2426         runOnUiThread(() -> {
2427             updateUiViews();
2428             styleDevicesInfo();
2429             switch (mConnectionStatus) {
2430                 case OFFLINE:
2431                     styleOffline();
2432                     break;
2433 
2434                 case DISCONNECTED:
2435                     styleDisconnected();
2436                     break;
2437 
2438                 case CONNECTING:
2439                     break;
2440 
2441                 case CONNECTED:
2442                     styleConnected();
2443                     break;
2444 
2445                 case ACTIVE_CALL:
2446                     styleActiveCall();
2447                     break;
2448             }
2449         });
2450     }
2451 
styleDevicesInfo()2452     private void styleDevicesInfo() {
2453         TextView localIpLabel = findViewById(R.id.localIpLabel);
2454         localIpLabel.setText(getString(R.string.local_ip_label, getLocalIpAddress()));
2455 
2456         mRemoteIpLabel = findViewById(R.id.remoteIpLabel);
2457         mRemoteIpLabel.setText(getString(R.string.other_device_ip_label,
2458                 prefs.getString("OTHER_IP_ADDRESS", "null")));
2459 
2460         mRemoteHandshakePortLabel = findViewById(R.id.remoteHandshakePortLabel);
2461         mRemoteHandshakePortLabel.setText(getString(R.string.handshake_port_label,
2462                 String.valueOf(getOtherDevicePort())));
2463     }
2464 
styleOffline()2465     private void styleOffline() {
2466         mLocalHandshakePortLabel.setText(getString(R.string.port_closed_label));
2467         mLocalRtpPortLabel.setText(getString(R.string.port_closed_label));
2468         mLocalRtcpPortLabel.setText(getString(R.string.port_closed_label));
2469         mRemoteRtpPortLabel.setText(getString(R.string.port_closed_label));
2470         mRemoteRtcpPortLabel.setText(getString(R.string.port_closed_label));
2471 
2472         mAllowCallsButton.setText(R.string.allow_calls_button_text);
2473         mAllowCallsButton.setBackgroundColor(getColor(R.color.mint_green));
2474         styleButtonEnabled(mAllowCallsButton);
2475 
2476         mConnectButton.setText(R.string.connect_button_text);
2477         mConnectButton.setBackgroundColor(getColor(R.color.mint_green));
2478         styleButtonDisabled(mConnectButton);
2479 
2480         styleLoopbackSwitchEnabled();
2481         styleButtonDisabled(mOpenSessionButton);
2482         styleLayoutChildrenDisabled(mActiveCallToolBar);
2483     }
2484 
styleDisconnected()2485     private void styleDisconnected() {
2486         mLocalHandshakePortLabel.setText(getString(R.string.handshake_port_label,
2487                 String.valueOf(mHandshakeReceptionSocket.getBoundSocket())));
2488         mLocalRtpPortLabel.setText(getString(R.string.rtp_reception_port_label,
2489                 String.valueOf(mAudioRtp.getLocalPort())));
2490         mLocalRtcpPortLabel.setText(getString(R.string.rtcp_reception_port_label,
2491                 String.valueOf(mAudioRtcp.getLocalPort())));
2492         mRemoteRtpPortLabel.setText(getString(R.string.port_closed_label));
2493         mRemoteRtcpPortLabel.setText(getString(R.string.port_closed_label));
2494 
2495         mAllowCallsButton.setText(R.string.disable_calls_button_text);
2496         mAllowCallsButton.setBackgroundColor(getColor(R.color.coral_red));
2497         styleButtonEnabled(mAllowCallsButton);
2498 
2499         mConnectButton.setText(R.string.connect_button_text);
2500         mConnectButton.setBackgroundColor(getColor(R.color.mint_green));
2501         styleButtonEnabled(mConnectButton);
2502 
2503         styleLoopbackSwitchDisabled();
2504         styleButtonDisabled(mOpenSessionButton);
2505         styleLayoutChildrenDisabled(mActiveCallToolBar);
2506     }
2507 
styleConnected()2508     private void styleConnected() {
2509         if (mLoopbackModeEnabled) {
2510             mAllowCallsButton.setText(R.string.allow_calls_button_text);
2511             mAllowCallsButton.setBackgroundColor(getColor(R.color.mint_green));
2512             styleButtonDisabled(mAllowCallsButton);
2513 
2514             mConnectButton.setText(R.string.connect_button_text);
2515             mConnectButton.setBackgroundColor(getColor(R.color.mint_green));
2516             styleButtonDisabled(mConnectButton);
2517             styleLoopbackSwitchEnabled();
2518 
2519         } else {
2520             mAllowCallsButton.setText(R.string.disable_calls_button_text);
2521             mAllowCallsButton.setBackgroundColor(getColor(R.color.coral_red));
2522             styleButtonEnabled(mAllowCallsButton);
2523 
2524             mConnectButton.setText(R.string.disconnect_button_text);
2525             mConnectButton.setBackgroundColor(getColor(R.color.coral_red));
2526             styleButtonEnabled(mConnectButton);
2527             styleLoopbackSwitchDisabled();
2528         }
2529 
2530         mLocalHandshakePortLabel.setText(getString(R.string.reception_port_label,
2531                 String.valueOf(mHandshakeReceptionSocket.getBoundSocket())));
2532         mLocalRtpPortLabel.setText(getString(R.string.rtp_reception_port_label,
2533                 String.valueOf(mAudioRtp.getLocalPort())));
2534         mLocalRtcpPortLabel.setText(getString(R.string.rtcp_reception_port_label,
2535                 String.valueOf(mAudioRtcp.getLocalPort())));
2536         mRemoteRtpPortLabel.setText(getString(R.string.rtp_reception_port_label,
2537                 String.valueOf(mRemoteDeviceInfo.getAudioRtpPort())));
2538         mRemoteRtcpPortLabel.setText(getString(R.string.rtcp_reception_port_label,
2539                 String.valueOf(mRemoteDeviceInfo.getAudioRtpPort() + 1)));
2540         styleButtonEnabled(mOpenSessionButton);
2541         styleLayoutChildrenDisabled(mActiveCallToolBar);
2542     }
2543 
styleActiveCall()2544     private void styleActiveCall() {
2545         if (mLoopbackModeEnabled) {
2546             styleLoopbackSwitchEnabled();
2547 
2548             mAllowCallsButton.setText(R.string.allow_calls_button_text);
2549             mAllowCallsButton.setBackgroundColor(getColor(R.color.mint_green));
2550             styleButtonDisabled(mAllowCallsButton);
2551 
2552             mConnectButton.setText(R.string.connect_button_text);
2553             mConnectButton.setBackgroundColor(getColor(R.color.mint_green));
2554             styleButtonDisabled(mConnectButton);
2555             styleLoopbackSwitchEnabled();
2556 
2557         } else {
2558             styleLoopbackSwitchDisabled();
2559 
2560             mAllowCallsButton.setText(R.string.disable_calls_button_text);
2561             mAllowCallsButton.setBackgroundColor(getColor(R.color.coral_red));
2562             styleButtonDisabled(mAllowCallsButton);
2563 
2564             mConnectButton.setText(R.string.disconnect_button_text);
2565             mConnectButton.setBackgroundColor(getColor(R.color.coral_red));
2566             styleButtonDisabled(mConnectButton);
2567             styleLoopbackSwitchDisabled();
2568         }
2569 
2570         mLocalHandshakePortLabel
2571                 .setText(getString(R.string.reception_port_label, getString(R.string.connected)));
2572         mLocalRtpPortLabel.setText(getString(R.string.rtp_reception_port_label,
2573                 String.valueOf(mAudioRtp.getLocalPort())));
2574         mLocalRtcpPortLabel.setText(getString(R.string.rtcp_reception_port_label,
2575                 String.valueOf(mAudioRtcp.getLocalPort())));
2576         mRemoteRtpPortLabel.setText(getString(R.string.rtp_reception_port_label,
2577                 String.valueOf(mRemoteDeviceInfo.getAudioRtpPort())));
2578         mRemoteRtcpPortLabel.setText(getString(R.string.rtcp_reception_port_label,
2579                 String.valueOf(mRemoteDeviceInfo.getAudioRtpPort() + 1)));
2580 
2581         styleButtonDisabled(mOpenSessionButton);
2582         styleLayoutChildrenEnabled(mActiveCallToolBar);
2583     }
2584 
styleButtonDisabled(Button button)2585     private void styleButtonDisabled(Button button) {
2586         button.setEnabled(false);
2587         button.setClickable(false);
2588         button.setAlpha(DISABLED_ALPHA);
2589     }
2590 
styleButtonEnabled(Button button)2591     private void styleButtonEnabled(Button button) {
2592         button.setEnabled(true);
2593         button.setClickable(true);
2594         button.setAlpha(ENABLED_ALPHA);
2595     }
2596 
styleLayoutChildrenDisabled(LinearLayout linearLayout)2597     private void styleLayoutChildrenDisabled(LinearLayout linearLayout) {
2598         linearLayout.setAlpha(DISABLED_ALPHA);
2599         for (int x = 0; x < linearLayout.getChildCount(); x++) {
2600             linearLayout.getChildAt(x).setAlpha(DISABLED_ALPHA);
2601             linearLayout.getChildAt(x).setEnabled(false);
2602             linearLayout.getChildAt(x).setClickable(false);
2603         }
2604     }
2605 
styleLayoutChildrenEnabled(LinearLayout linearLayout)2606     private void styleLayoutChildrenEnabled(LinearLayout linearLayout) {
2607         linearLayout.setAlpha(ENABLED_ALPHA);
2608         for (int x = 0; x < linearLayout.getChildCount(); x++) {
2609             linearLayout.getChildAt(x).setAlpha(ENABLED_ALPHA);
2610             linearLayout.getChildAt(x).setEnabled(true);
2611             linearLayout.getChildAt(x).setClickable(true);
2612         }
2613     }
2614 
updateUiViews()2615     private void updateUiViews() {
2616         mLocalHandshakePortLabel = findViewById(R.id.localHandshakePortLabel);
2617         mLocalRtpPortLabel = findViewById(R.id.localRtpPortLabel);
2618         mLocalRtcpPortLabel = findViewById(R.id.localRtcpPortLabel);
2619         mRemoteHandshakePortLabel = findViewById(R.id.remoteHandshakePortLabel);
2620         mRemoteRtpPortLabel = findViewById(R.id.remoteRtpPortLabel);
2621         mRemoteRtcpPortLabel = findViewById(R.id.remoteRtcpPortLabel);
2622         mAllowCallsButton = findViewById(R.id.allowCallsButton);
2623         mConnectButton = findViewById(R.id.connectButton);
2624         mOpenSessionButton = findViewById(R.id.openSessionButton);
2625         mActiveCallToolBar = findViewById(R.id.activeCallActionsLayout);
2626         mLoopbackSwitch = findViewById(R.id.loopbackModeSwitch);
2627     }
2628 
styleLoopbackSwitchEnabled()2629     private void styleLoopbackSwitchEnabled() {
2630         mLoopbackSwitch.setChecked(mLoopbackModeEnabled);
2631         mLoopbackSwitch.setEnabled(true);
2632         mLoopbackSwitch.setClickable(true);
2633         mLoopbackSwitch.setAlpha(ENABLED_ALPHA);
2634     }
2635 
styleLoopbackSwitchDisabled()2636     private void styleLoopbackSwitchDisabled() {
2637         mLoopbackSwitch.setChecked(mLoopbackModeEnabled);
2638         mLoopbackSwitch.setEnabled(false);
2639         mLoopbackSwitch.setClickable(false);
2640         mLoopbackSwitch.setAlpha(DISABLED_ALPHA);
2641     }
2642 
updateVideoUi(boolean enable)2643     private void updateVideoUi(boolean enable) {
2644         createTextureView();
2645         TextView previewLabel = findViewById(R.id.previewLabel);
2646         TextView displayLabel = findViewById(R.id.displayLabel);
2647         FrameLayout previewFrame = findViewById(R.id.previewFrame);
2648         FrameLayout displayFrames = findViewById(R.id.displayFrames);
2649 
2650         if (enable) {
2651             previewLabel.setVisibility(View.VISIBLE);
2652             displayLabel.setVisibility(View.VISIBLE);
2653             previewFrame.setVisibility(View.VISIBLE);
2654             mTexturePreview.setVisibility(View.VISIBLE);
2655             displayFrames.setVisibility(View.VISIBLE);
2656             mTextureDisplay.setVisibility(View.VISIBLE);
2657         } else {
2658             previewLabel.setVisibility(View.GONE);
2659             displayLabel.setVisibility(View.GONE);
2660             previewFrame.setVisibility(View.GONE);
2661             mTexturePreview.setVisibility(View.GONE);
2662             displayFrames.setVisibility(View.GONE);
2663             mTextureDisplay.setVisibility(View.GONE);
2664         }
2665 
2666         ViewGroup viewGroup = findViewById(R.id.rootLayout);
2667         viewGroup.invalidate();
2668     }
2669 
updateTextUi(boolean enable)2670     private void updateTextUi(boolean enable) {
2671         TextView sendingTextTitle = findViewById(R.id.sendingTextTitle);
2672         EditText textEditTextSending = findViewById(R.id.textEditTextSending);
2673         TextView receivedTextTitle = findViewById(R.id.receivedTextTitle);
2674         TextView receivedText = findViewById(R.id.receivedText);
2675         TextView buttonTextSend = findViewById(R.id.buttonTextSend);
2676 
2677         if (enable) {
2678             sendingTextTitle.setVisibility(View.VISIBLE);
2679             textEditTextSending.setVisibility(View.VISIBLE);
2680             receivedTextTitle.setVisibility(View.VISIBLE);
2681             receivedText.setVisibility(View.VISIBLE);
2682             buttonTextSend.setVisibility(View.VISIBLE);
2683         } else {
2684             sendingTextTitle.setVisibility(View.GONE);
2685             textEditTextSending.setVisibility(View.GONE);
2686             receivedTextTitle.setVisibility(View.GONE);
2687             receivedText.setVisibility(View.GONE);
2688             buttonTextSend.setVisibility(View.GONE);
2689         }
2690         ViewGroup viewGroup = findViewById(R.id.rootLayout);
2691         viewGroup.invalidate();
2692     }
2693 
2694     private IImsMediaCallback.Stub mMediaUtilCallback = new IImsMediaCallback.Stub() {
2695         @Override
2696         public void onVideoSpropResponse(String[] spropList) {
2697             Log.d(TAG, "[GetSprop]onVideoSprop");
2698             for (String sprop : spropList) {
2699                 Log.d(TAG, "[GetSprop]SPROP : " + sprop);
2700             }
2701         }
2702     };
2703 
generateSprop(View btn)2704     public void generateSprop(View btn) {
2705         if (!mIsMediaManagerReady) {
2706             Log.d(TAG, "[GetSprop]Media Manager not ready");
2707             return;
2708         }
2709 
2710         VideoConfig[] videoConfigList = new VideoConfig[1];
2711         VideoConfig.Builder vcbuilder = new VideoConfig.Builder();
2712         vcbuilder.setBitrate(384)
2713                 .setCodecType(VideoConfig.VIDEO_CODEC_AVC)
2714                 .setCodecProfile(VideoConfig.AVC_PROFILE_CONSTRAINED_BASELINE)
2715                 .setCodecLevel(VideoConfig.AVC_LEVEL_12)
2716                 .setFramerate(10)
2717                 .setIntraFrameIntervalSec(1)
2718                 .setPacketizationMode(VideoConfig.MODE_NON_INTERLEAVED)
2719                 .setResolutionWidth(RESOLUTION_WIDTH)
2720                 .setResolutionHeight(RESOLUTION_HEIGHT)
2721                 .setVideoMode(VideoConfig.VIDEO_MODE_RECORDING)
2722                 .setMaxMtuBytes(1500);
2723 
2724         videoConfigList[0] = vcbuilder.build();
2725 
2726         try {
2727             mImsMediaManager.generateVideoSprop(videoConfigList, mMediaUtilCallback.asBinder());
2728         } catch (Exception e) {
2729             Log.d(TAG, e.toString());
2730         }
2731     }
2732 
sendHeaderExtension(View btn)2733     public void sendHeaderExtension(View btn) {
2734         if (mAudioSession != null) {
2735             List<RtpHeaderExtension> extensions = new ArrayList<>();
2736             byte[] testBytes1 = new byte[1];
2737             byte[] testBytes2 = new byte[1];
2738             testBytes1[0] = 5;
2739             testBytes2[0] = 10;
2740             RtpHeaderExtension extension1 = new RtpHeaderExtension(1, testBytes1);
2741             RtpHeaderExtension extension2 = new RtpHeaderExtension(2, testBytes2);
2742             extensions.add(extension1);
2743             extensions.add(extension2);
2744             Log.d(TAG, "[sendHeaderExtension] extension size=" + extensions.size());
2745             mAudioSession.sendHeaderExtension(extensions);
2746         }
2747     }
2748 
2749     /**
2750      * Send the parameter to adjust the audio delay
2751      */
adjustDelay(View btn)2752     public void adjustDelay(View btn) {
2753         if (mAudioSession != null) {
2754             mDelay = (mDelay + 20) % 240;
2755             if (mDelay < 60) {
2756                 mDelay = 60;
2757             }
2758             mAudioSession.adjustDelay(mDelay);
2759         }
2760     }
2761 }
2762