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