CallActivity.java 122 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835
  1. /*
  2. * Nextcloud Talk application
  3. *
  4. * @author Mario Danic
  5. * @author Tim Krüger
  6. * Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
  7. * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation, either version 3 of the License, or
  12. * at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. */
  22. package com.nextcloud.talk.activities;
  23. import android.Manifest;
  24. import android.animation.Animator;
  25. import android.animation.AnimatorListenerAdapter;
  26. import android.annotation.SuppressLint;
  27. import android.app.PendingIntent;
  28. import android.app.RemoteAction;
  29. import android.content.BroadcastReceiver;
  30. import android.content.Context;
  31. import android.content.Intent;
  32. import android.content.IntentFilter;
  33. import android.content.pm.PackageManager;
  34. import android.content.res.Configuration;
  35. import android.graphics.Color;
  36. import android.graphics.drawable.Icon;
  37. import android.media.AudioAttributes;
  38. import android.media.MediaPlayer;
  39. import android.net.Uri;
  40. import android.os.Build;
  41. import android.os.Bundle;
  42. import android.os.Handler;
  43. import android.os.Looper;
  44. import android.text.TextUtils;
  45. import android.util.DisplayMetrics;
  46. import android.util.Log;
  47. import android.view.MotionEvent;
  48. import android.view.View;
  49. import android.view.ViewGroup;
  50. import android.view.ViewTreeObserver;
  51. import android.widget.FrameLayout;
  52. import android.widget.RelativeLayout;
  53. import android.widget.Toast;
  54. import com.bluelinelabs.logansquare.LoganSquare;
  55. import com.nextcloud.talk.R;
  56. import com.nextcloud.talk.adapters.ParticipantDisplayItem;
  57. import com.nextcloud.talk.adapters.ParticipantsAdapter;
  58. import com.nextcloud.talk.api.NcApi;
  59. import com.nextcloud.talk.application.NextcloudTalkApplication;
  60. import com.nextcloud.talk.data.user.model.User;
  61. import com.nextcloud.talk.databinding.CallActivityBinding;
  62. import com.nextcloud.talk.events.ConfigurationChangeEvent;
  63. import com.nextcloud.talk.events.MediaStreamEvent;
  64. import com.nextcloud.talk.events.NetworkEvent;
  65. import com.nextcloud.talk.events.PeerConnectionEvent;
  66. import com.nextcloud.talk.events.SessionDescriptionSendEvent;
  67. import com.nextcloud.talk.events.WebSocketCommunicationEvent;
  68. import com.nextcloud.talk.models.ExternalSignalingServer;
  69. import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
  70. import com.nextcloud.talk.models.json.conversations.Conversation;
  71. import com.nextcloud.talk.models.json.conversations.RoomOverall;
  72. import com.nextcloud.talk.models.json.conversations.RoomsOverall;
  73. import com.nextcloud.talk.models.json.generic.GenericOverall;
  74. import com.nextcloud.talk.models.json.participants.Participant;
  75. import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
  76. import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
  77. import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
  78. import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
  79. import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
  80. import com.nextcloud.talk.models.json.signaling.Signaling;
  81. import com.nextcloud.talk.models.json.signaling.SignalingOverall;
  82. import com.nextcloud.talk.models.json.signaling.settings.IceServer;
  83. import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
  84. import com.nextcloud.talk.signaling.SignalingMessageReceiver;
  85. import com.nextcloud.talk.signaling.SignalingMessageSender;
  86. import com.nextcloud.talk.ui.dialog.AudioOutputDialog;
  87. import com.nextcloud.talk.users.UserManager;
  88. import com.nextcloud.talk.utils.ApiUtils;
  89. import com.nextcloud.talk.utils.DisplayUtils;
  90. import com.nextcloud.talk.utils.NotificationUtils;
  91. import com.nextcloud.talk.utils.animations.PulseAnimation;
  92. import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil;
  93. import com.nextcloud.talk.utils.power.PowerManagerUtils;
  94. import com.nextcloud.talk.utils.preferences.AppPreferences;
  95. import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder;
  96. import com.nextcloud.talk.webrtc.MagicWebRTCUtils;
  97. import com.nextcloud.talk.webrtc.MagicWebSocketInstance;
  98. import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
  99. import com.nextcloud.talk.webrtc.WebRtcAudioManager;
  100. import com.nextcloud.talk.webrtc.WebSocketConnectionHelper;
  101. import com.wooplr.spotlight.SpotlightView;
  102. import org.apache.commons.lang3.StringEscapeUtils;
  103. import org.greenrobot.eventbus.EventBus;
  104. import org.greenrobot.eventbus.Subscribe;
  105. import org.greenrobot.eventbus.ThreadMode;
  106. import org.webrtc.AudioSource;
  107. import org.webrtc.AudioTrack;
  108. import org.webrtc.Camera1Enumerator;
  109. import org.webrtc.Camera2Enumerator;
  110. import org.webrtc.CameraEnumerator;
  111. import org.webrtc.CameraVideoCapturer;
  112. import org.webrtc.DefaultVideoDecoderFactory;
  113. import org.webrtc.DefaultVideoEncoderFactory;
  114. import org.webrtc.EglBase;
  115. import org.webrtc.Logging;
  116. import org.webrtc.MediaConstraints;
  117. import org.webrtc.MediaStream;
  118. import org.webrtc.PeerConnection;
  119. import org.webrtc.PeerConnectionFactory;
  120. import org.webrtc.RendererCommon;
  121. import org.webrtc.SurfaceTextureHelper;
  122. import org.webrtc.VideoCapturer;
  123. import org.webrtc.VideoSource;
  124. import org.webrtc.VideoTrack;
  125. import java.io.IOException;
  126. import java.util.ArrayList;
  127. import java.util.HashMap;
  128. import java.util.HashSet;
  129. import java.util.List;
  130. import java.util.Map;
  131. import java.util.Objects;
  132. import java.util.Set;
  133. import java.util.concurrent.TimeUnit;
  134. import java.util.concurrent.atomic.AtomicInteger;
  135. import javax.inject.Inject;
  136. import androidx.activity.result.ActivityResultLauncher;
  137. import androidx.activity.result.contract.ActivityResultContracts;
  138. import androidx.annotation.DrawableRes;
  139. import androidx.annotation.NonNull;
  140. import androidx.annotation.Nullable;
  141. import androidx.annotation.RequiresApi;
  142. import androidx.appcompat.app.AppCompatActivity;
  143. import androidx.core.content.ContextCompat;
  144. import androidx.core.graphics.drawable.DrawableCompat;
  145. import autodagger.AutoInjector;
  146. import io.reactivex.Observable;
  147. import io.reactivex.Observer;
  148. import io.reactivex.android.schedulers.AndroidSchedulers;
  149. import io.reactivex.disposables.Disposable;
  150. import io.reactivex.schedulers.Schedulers;
  151. import me.zhanghai.android.effortlesspermissions.AfterPermissionDenied;
  152. import me.zhanghai.android.effortlesspermissions.EffortlessPermissions;
  153. import me.zhanghai.android.effortlesspermissions.OpenAppDetailsDialogFragment;
  154. import okhttp3.Cache;
  155. import pub.devrel.easypermissions.AfterPermissionGranted;
  156. import static android.app.PendingIntent.FLAG_IMMUTABLE;
  157. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_VOICE_ONLY;
  158. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION;
  159. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME;
  160. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_PASSWORD;
  161. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FROM_NOTIFICATION_START_CALL;
  162. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_MODIFIED_BASE_URL;
  163. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO;
  164. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO;
  165. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_ID;
  166. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN;
  167. import static com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY;
  168. @AutoInjector(NextcloudTalkApplication.class)
  169. public class CallActivity extends CallBaseActivity {
  170. public static final String VIDEO_STREAM_TYPE_SCREEN = "screen";
  171. public static final String VIDEO_STREAM_TYPE_VIDEO = "video";
  172. @Inject
  173. NcApi ncApi;
  174. @Inject
  175. EventBus eventBus;
  176. @Inject
  177. UserManager userManager;
  178. @Inject
  179. AppPreferences appPreferences;
  180. @Inject
  181. Cache cache;
  182. @Inject
  183. PlatformPermissionUtil permissionUtil;
  184. public static final String TAG = "CallActivity";
  185. public WebRtcAudioManager audioManager;
  186. private static final String[] PERMISSIONS_CALL = {
  187. Manifest.permission.CAMERA,
  188. Manifest.permission.RECORD_AUDIO
  189. };
  190. private static final String[] PERMISSIONS_CAMERA = {
  191. Manifest.permission.CAMERA
  192. };
  193. private static final String[] PERMISSIONS_MICROPHONE = {
  194. Manifest.permission.RECORD_AUDIO
  195. };
  196. private static final String MICROPHONE_PIP_INTENT_NAME = "microphone_pip_intent";
  197. private static final String MICROPHONE_PIP_INTENT_EXTRA_ACTION = "microphone_pip_action";
  198. private static final int MICROPHONE_PIP_REQUEST_MUTE = 1;
  199. private static final int MICROPHONE_PIP_REQUEST_UNMUTE = 2;
  200. private BroadcastReceiver mReceiver;
  201. private PeerConnectionFactory peerConnectionFactory;
  202. private MediaConstraints audioConstraints;
  203. private MediaConstraints videoConstraints;
  204. private MediaConstraints sdpConstraints;
  205. private MediaConstraints sdpConstraintsForMCU;
  206. private VideoSource videoSource;
  207. private VideoTrack localVideoTrack;
  208. private AudioSource audioSource;
  209. private AudioTrack localAudioTrack;
  210. private VideoCapturer videoCapturer;
  211. private EglBase rootEglBase;
  212. private Disposable signalingDisposable;
  213. private List<PeerConnection.IceServer> iceServers;
  214. private CameraEnumerator cameraEnumerator;
  215. private String roomToken;
  216. private User conversationUser;
  217. private String conversationName;
  218. private String callSession;
  219. private MediaStream localStream;
  220. private String credentials;
  221. private List<PeerConnectionWrapper> peerConnectionWrapperList = new ArrayList<>();
  222. private Map<String, Participant> participantMap = new HashMap<>();
  223. private boolean videoOn = false;
  224. private boolean microphoneOn = false;
  225. private boolean isVoiceOnlyCall;
  226. private boolean isCallWithoutNotification;
  227. private boolean isIncomingCallFromNotification;
  228. private Handler callControlHandler = new Handler();
  229. private Handler callInfosHandler = new Handler();
  230. private Handler cameraSwitchHandler = new Handler();
  231. // push to talk
  232. private boolean isPushToTalkActive = false;
  233. private PulseAnimation pulseAnimation;
  234. private String baseUrl;
  235. private String roomId;
  236. private SpotlightView spotlightView;
  237. private InternalSignalingMessageReceiver internalSignalingMessageReceiver = new InternalSignalingMessageReceiver();
  238. private SignalingMessageReceiver signalingMessageReceiver;
  239. private InternalSignalingMessageSender internalSignalingMessageSender = new InternalSignalingMessageSender();
  240. private SignalingMessageSender signalingMessageSender;
  241. private Map<String, SignalingMessageReceiver.CallParticipantMessageListener> callParticipantMessageListeners =
  242. new HashMap<>();
  243. private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener = new SignalingMessageReceiver.ParticipantListMessageListener() {
  244. @Override
  245. public void onUsersInRoom(List<Participant> participants) {
  246. processUsersInRoom(participants);
  247. }
  248. @Override
  249. public void onParticipantsUpdate(List<Participant> participants) {
  250. processUsersInRoom(participants);
  251. }
  252. @Override
  253. public void onAllParticipantsUpdate(long inCall) {
  254. if (inCall == Participant.InCallFlags.DISCONNECTED) {
  255. Log.d(TAG, "A moderator ended the call for all.");
  256. hangup(true);
  257. }
  258. }
  259. };
  260. private SignalingMessageReceiver.OfferMessageListener offerMessageListener = new SignalingMessageReceiver.OfferMessageListener() {
  261. @Override
  262. public void onOffer(String sessionId, String roomType, String sdp, String nick) {
  263. getOrCreatePeerConnectionWrapperForSessionIdAndType(sessionId, roomType, false);
  264. }
  265. };
  266. private ExternalSignalingServer externalSignalingServer;
  267. private MagicWebSocketInstance webSocketClient;
  268. private WebSocketConnectionHelper webSocketConnectionHelper;
  269. private boolean hasMCU;
  270. private boolean hasExternalSignalingServer;
  271. private String conversationPassword;
  272. private PowerManagerUtils powerManagerUtils;
  273. private Handler handler;
  274. private CallStatus currentCallStatus;
  275. private MediaPlayer mediaPlayer;
  276. private Map<String, ParticipantDisplayItem> participantDisplayItems;
  277. private ParticipantsAdapter participantsAdapter;
  278. private CallActivityBinding binding;
  279. private AudioOutputDialog audioOutputDialog;
  280. private final ActivityResultLauncher<String> requestBluetoothPermissionLauncher =
  281. registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
  282. if (isGranted) {
  283. enableBluetoothManager();
  284. }
  285. });
  286. private boolean canPublishAudioStream;
  287. private boolean canPublishVideoStream;
  288. @SuppressLint("ClickableViewAccessibility")
  289. @Override
  290. public void onCreate(Bundle savedInstanceState) {
  291. Log.d(TAG, "onCreate");
  292. super.onCreate(savedInstanceState);
  293. NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
  294. binding = CallActivityBinding.inflate(getLayoutInflater());
  295. setContentView(binding.getRoot());
  296. hideNavigationIfNoPipAvailable();
  297. Bundle extras = getIntent().getExtras();
  298. roomId = extras.getString(KEY_ROOM_ID, "");
  299. roomToken = extras.getString(KEY_ROOM_TOKEN, "");
  300. conversationUser = extras.getParcelable(KEY_USER_ENTITY);
  301. conversationPassword = extras.getString(KEY_CONVERSATION_PASSWORD, "");
  302. conversationName = extras.getString(KEY_CONVERSATION_NAME, "");
  303. isVoiceOnlyCall = extras.getBoolean(KEY_CALL_VOICE_ONLY, false);
  304. isCallWithoutNotification = extras.getBoolean(KEY_CALL_WITHOUT_NOTIFICATION, false);
  305. canPublishAudioStream = extras.getBoolean(KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO);
  306. canPublishVideoStream = extras.getBoolean(KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO);
  307. if (extras.containsKey(KEY_FROM_NOTIFICATION_START_CALL)) {
  308. isIncomingCallFromNotification = extras.getBoolean(KEY_FROM_NOTIFICATION_START_CALL);
  309. }
  310. credentials = ApiUtils.getCredentials(conversationUser.getUsername(), conversationUser.getToken());
  311. baseUrl = extras.getString(KEY_MODIFIED_BASE_URL, "");
  312. if (TextUtils.isEmpty(baseUrl)) {
  313. baseUrl = conversationUser.getBaseUrl();
  314. }
  315. powerManagerUtils = new PowerManagerUtils();
  316. if (extras.getString("state", "").equalsIgnoreCase("resume")) {
  317. setCallState(CallStatus.IN_CONVERSATION);
  318. } else {
  319. setCallState(CallStatus.CONNECTING);
  320. }
  321. initClickListeners();
  322. binding.microphoneButton.setOnTouchListener(new MicrophoneButtonTouchListener());
  323. pulseAnimation = PulseAnimation.create().with(binding.microphoneButton)
  324. .setDuration(310)
  325. .setRepeatCount(PulseAnimation.INFINITE)
  326. .setRepeatMode(PulseAnimation.REVERSE);
  327. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
  328. requestBluetoothPermission();
  329. }
  330. basicInitialization();
  331. participantDisplayItems = new HashMap<>();
  332. initViews();
  333. if (!isConnectionEstablished()) {
  334. initiateCall();
  335. }
  336. updateSelfVideoViewPosition();
  337. }
  338. @RequiresApi(api = Build.VERSION_CODES.S)
  339. private void requestBluetoothPermission() {
  340. if (ContextCompat.checkSelfPermission(
  341. getContext(), Manifest.permission.BLUETOOTH_CONNECT) ==
  342. PackageManager.PERMISSION_DENIED) {
  343. requestBluetoothPermissionLauncher.launch(Manifest.permission.BLUETOOTH_CONNECT);
  344. }
  345. }
  346. private void enableBluetoothManager() {
  347. if (audioManager != null) {
  348. audioManager.startBluetoothManager();
  349. }
  350. }
  351. @Override
  352. public void onStart() {
  353. super.onStart();
  354. try {
  355. cache.evictAll();
  356. } catch (IOException e) {
  357. Log.e(TAG, "Failed to evict cache");
  358. }
  359. }
  360. private void initClickListeners() {
  361. binding.pictureInPictureButton.setOnClickListener(l -> enterPipMode());
  362. binding.audioOutputButton.setOnClickListener(v -> {
  363. audioOutputDialog = new AudioOutputDialog(this);
  364. audioOutputDialog.show();
  365. });
  366. if (canPublishAudioStream) {
  367. binding.microphoneButton.setOnClickListener(l -> onMicrophoneClick());
  368. binding.microphoneButton.setOnLongClickListener(l -> {
  369. if (!microphoneOn) {
  370. callControlHandler.removeCallbacksAndMessages(null);
  371. callInfosHandler.removeCallbacksAndMessages(null);
  372. cameraSwitchHandler.removeCallbacksAndMessages(null);
  373. isPushToTalkActive = true;
  374. binding.callControls.setVisibility(View.VISIBLE);
  375. if (!isVoiceOnlyCall) {
  376. binding.switchSelfVideoButton.setVisibility(View.VISIBLE);
  377. }
  378. }
  379. onMicrophoneClick();
  380. return true;
  381. });
  382. } else {
  383. binding.microphoneButton.setOnClickListener(
  384. l -> Toast.makeText(context,
  385. R.string.nc_not_allowed_to_activate_audio,
  386. Toast.LENGTH_SHORT
  387. ).show()
  388. );
  389. }
  390. if (canPublishVideoStream) {
  391. binding.cameraButton.setOnClickListener(l -> onCameraClick());
  392. } else {
  393. binding.cameraButton.setOnClickListener(
  394. l -> Toast.makeText(context,
  395. R.string.nc_not_allowed_to_activate_video,
  396. Toast.LENGTH_SHORT
  397. ).show()
  398. );
  399. }
  400. binding.hangupButton.setOnClickListener(l -> {
  401. hangup(true);
  402. });
  403. binding.switchSelfVideoButton.setOnClickListener(l -> switchCamera());
  404. binding.gridview.setOnItemClickListener((parent, view, position, id) -> animateCallControls(true, 0));
  405. binding.callStates.callStateRelativeLayout.setOnClickListener(l -> {
  406. if (currentCallStatus == CallStatus.CALLING_TIMEOUT) {
  407. setCallState(CallStatus.RECONNECTING);
  408. hangupNetworkCalls(false);
  409. }
  410. });
  411. }
  412. private void createCameraEnumerator() {
  413. boolean camera2EnumeratorIsSupported = false;
  414. try {
  415. camera2EnumeratorIsSupported = Camera2Enumerator.isSupported(this);
  416. } catch (final Throwable t) {
  417. Log.w(TAG, "Camera2Enumerator threw an error", t);
  418. }
  419. if (camera2EnumeratorIsSupported) {
  420. cameraEnumerator = new Camera2Enumerator(this);
  421. } else {
  422. cameraEnumerator = new Camera1Enumerator(MagicWebRTCUtils.shouldEnableVideoHardwareAcceleration());
  423. }
  424. }
  425. private void basicInitialization() {
  426. rootEglBase = EglBase.create();
  427. createCameraEnumerator();
  428. //Create a new PeerConnectionFactory instance.
  429. PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
  430. DefaultVideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(
  431. rootEglBase.getEglBaseContext(), true, true);
  432. DefaultVideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(
  433. rootEglBase.getEglBaseContext());
  434. peerConnectionFactory = PeerConnectionFactory.builder()
  435. .setOptions(options)
  436. .setVideoEncoderFactory(defaultVideoEncoderFactory)
  437. .setVideoDecoderFactory(defaultVideoDecoderFactory)
  438. .createPeerConnectionFactory();
  439. //Create MediaConstraints - Will be useful for specifying video and audio constraints.
  440. audioConstraints = new MediaConstraints();
  441. videoConstraints = new MediaConstraints();
  442. localStream = peerConnectionFactory.createLocalMediaStream("NCMS");
  443. // Create and audio manager that will take care of audio routing,
  444. // audio modes, audio device enumeration etc.
  445. audioManager = WebRtcAudioManager.create(getApplicationContext(), isVoiceOnlyCall);
  446. // Store existing audio settings and change audio mode to
  447. // MODE_IN_COMMUNICATION for best possible VoIP performance.
  448. Log.d(TAG, "Starting the audio manager...");
  449. audioManager.start(this::onAudioManagerDevicesChanged);
  450. if (isVoiceOnlyCall) {
  451. setAudioOutputChannel(WebRtcAudioManager.AudioDevice.EARPIECE);
  452. } else {
  453. setAudioOutputChannel(WebRtcAudioManager.AudioDevice.SPEAKER_PHONE);
  454. }
  455. iceServers = new ArrayList<>();
  456. //create sdpConstraints
  457. sdpConstraints = new MediaConstraints();
  458. sdpConstraintsForMCU = new MediaConstraints();
  459. sdpConstraints.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"));
  460. String offerToReceiveVideoString = "true";
  461. if (isVoiceOnlyCall) {
  462. offerToReceiveVideoString = "false";
  463. }
  464. sdpConstraints.mandatory.add(
  465. new MediaConstraints.KeyValuePair("OfferToReceiveVideo", offerToReceiveVideoString));
  466. sdpConstraintsForMCU.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "false"));
  467. sdpConstraintsForMCU.mandatory.add(new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "false"));
  468. sdpConstraintsForMCU.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true"));
  469. sdpConstraintsForMCU.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
  470. sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("internalSctpDataChannels", "true"));
  471. sdpConstraints.optional.add(new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
  472. if (!isVoiceOnlyCall) {
  473. cameraInitialization();
  474. }
  475. microphoneInitialization();
  476. }
  477. public void setAudioOutputChannel(WebRtcAudioManager.AudioDevice selectedAudioDevice) {
  478. if (audioManager != null) {
  479. audioManager.selectAudioDevice(selectedAudioDevice);
  480. updateAudioOutputButton(audioManager.getCurrentAudioDevice());
  481. }
  482. }
  483. private void updateAudioOutputButton(WebRtcAudioManager.AudioDevice activeAudioDevice) {
  484. switch (activeAudioDevice) {
  485. case BLUETOOTH:
  486. binding.audioOutputButton.setImageResource ( R.drawable.ic_baseline_bluetooth_audio_24);
  487. break;
  488. case SPEAKER_PHONE:
  489. binding.audioOutputButton.setImageResource(R.drawable.ic_volume_up_white_24dp);
  490. break;
  491. case EARPIECE:
  492. binding.audioOutputButton.setImageResource(R.drawable.ic_baseline_phone_in_talk_24);
  493. break;
  494. case WIRED_HEADSET:
  495. binding.audioOutputButton.setImageResource(R.drawable.ic_baseline_headset_mic_24);
  496. break;
  497. default:
  498. Log.e(TAG, "Icon for audio output not available");
  499. break;
  500. }
  501. DrawableCompat.setTint(binding.audioOutputButton.getDrawable(), Color.WHITE);
  502. }
  503. private void handleFromNotification() {
  504. int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
  505. ncApi.getRooms(credentials, ApiUtils.getUrlForRooms(apiVersion, baseUrl), false)
  506. .retry(3)
  507. .subscribeOn(Schedulers.io())
  508. .observeOn(AndroidSchedulers.mainThread())
  509. .subscribe(new Observer<RoomsOverall>() {
  510. @Override
  511. public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
  512. // unused atm
  513. }
  514. @Override
  515. public void onNext(@io.reactivex.annotations.NonNull RoomsOverall roomsOverall) {
  516. for (Conversation conversation : roomsOverall.getOcs().getData()) {
  517. if (roomId.equals(conversation.getRoomId())) {
  518. roomToken = conversation.getToken();
  519. break;
  520. }
  521. }
  522. checkDevicePermissions();
  523. }
  524. @Override
  525. public void onError(@io.reactivex.annotations.NonNull Throwable e) {
  526. // unused atm
  527. }
  528. @Override
  529. public void onComplete() {
  530. // unused atm
  531. }
  532. });
  533. }
  534. @SuppressLint("ClickableViewAccessibility")
  535. private void initViews() {
  536. Log.d(TAG, "initViews");
  537. binding.callInfosLinearLayout.setVisibility(View.VISIBLE);
  538. binding.selfVideoViewWrapper.setVisibility(View.VISIBLE);
  539. if (!isPipModePossible()) {
  540. binding.pictureInPictureButton.setVisibility(View.GONE);
  541. }
  542. if (isVoiceOnlyCall) {
  543. binding.switchSelfVideoButton.setVisibility(View.GONE);
  544. binding.cameraButton.setVisibility(View.GONE);
  545. binding.selfVideoRenderer.setVisibility(View.GONE);
  546. RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
  547. ViewGroup.LayoutParams.WRAP_CONTENT);
  548. params.addRule(RelativeLayout.BELOW, R.id.callInfosLinearLayout);
  549. int callControlsHeight = Math.round(getApplicationContext().getResources().getDimension(R.dimen.call_controls_height));
  550. params.setMargins(0, 0, 0, callControlsHeight);
  551. binding.gridview.setLayoutParams(params);
  552. } else {
  553. RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
  554. ViewGroup.LayoutParams.WRAP_CONTENT);
  555. params.setMargins(0, 0, 0, 0);
  556. binding.gridview.setLayoutParams(params);
  557. if (cameraEnumerator.getDeviceNames().length < 2) {
  558. binding.switchSelfVideoButton.setVisibility(View.GONE);
  559. }
  560. initSelfVideoView();
  561. }
  562. binding.gridview.setOnTouchListener(new View.OnTouchListener() {
  563. public boolean onTouch(View v, MotionEvent me) {
  564. int action = me.getActionMasked();
  565. if (action == MotionEvent.ACTION_DOWN) {
  566. animateCallControls(true, 0);
  567. }
  568. return false;
  569. }
  570. });
  571. binding.conversationRelativeLayout.setOnTouchListener(new View.OnTouchListener() {
  572. public boolean onTouch(View v, MotionEvent me) {
  573. int action = me.getActionMasked();
  574. if (action == MotionEvent.ACTION_DOWN) {
  575. animateCallControls(true, 0);
  576. }
  577. return false;
  578. }
  579. });
  580. animateCallControls(true, 0);
  581. initGridAdapter();
  582. }
  583. @SuppressLint("ClickableViewAccessibility")
  584. private void initSelfVideoView() {
  585. try {
  586. binding.selfVideoRenderer.init(rootEglBase.getEglBaseContext(), null);
  587. } catch (IllegalStateException e) {
  588. Log.d(TAG, "selfVideoRenderer already initialized", e);
  589. }
  590. binding.selfVideoRenderer.setZOrderMediaOverlay(true);
  591. // disabled because it causes some devices to crash
  592. binding.selfVideoRenderer.setEnableHardwareScaler(false);
  593. binding.selfVideoRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
  594. binding.selfVideoRenderer.setOnTouchListener(new SelfVideoTouchListener());
  595. }
  596. private void initGridAdapter() {
  597. Log.d(TAG, "initGridAdapter");
  598. int columns;
  599. int participantsInGrid = participantDisplayItems.size();
  600. if (getResources() != null
  601. && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
  602. if (participantsInGrid > 2) {
  603. columns = 2;
  604. } else {
  605. columns = 1;
  606. }
  607. } else {
  608. if (participantsInGrid > 2) {
  609. columns = 3;
  610. } else if (participantsInGrid > 1) {
  611. columns = 2;
  612. } else {
  613. columns = 1;
  614. }
  615. }
  616. binding.gridview.setNumColumns(columns);
  617. binding.conversationRelativeLayout
  618. .getViewTreeObserver()
  619. .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  620. @Override
  621. public void onGlobalLayout() {
  622. binding.conversationRelativeLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
  623. int height = binding.conversationRelativeLayout.getMeasuredHeight();
  624. binding.gridview.setMinimumHeight(height);
  625. }
  626. });
  627. binding
  628. .callInfosLinearLayout
  629. .getViewTreeObserver()
  630. .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
  631. @Override
  632. public void onGlobalLayout() {
  633. binding.callInfosLinearLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
  634. }
  635. });
  636. participantsAdapter = new ParticipantsAdapter(
  637. this,
  638. participantDisplayItems,
  639. binding.conversationRelativeLayout,
  640. binding.callInfosLinearLayout,
  641. columns,
  642. isVoiceOnlyCall);
  643. binding.gridview.setAdapter(participantsAdapter);
  644. if (isInPipMode) {
  645. updateUiForPipMode();
  646. }
  647. }
  648. private void checkDevicePermissions() {
  649. if (isVoiceOnlyCall) {
  650. onMicrophoneClick();
  651. } else {
  652. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CALL)) {
  653. onPermissionsGranted();
  654. } else {
  655. requestPermissions(PERMISSIONS_CALL, 100);
  656. }
  657. }
  658. }
  659. private boolean isConnectionEstablished() {
  660. return (currentCallStatus == CallStatus.JOINED || currentCallStatus == CallStatus.IN_CONVERSATION);
  661. }
  662. @AfterPermissionGranted(100)
  663. private void onPermissionsGranted() {
  664. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CALL)) {
  665. if (!videoOn && !isVoiceOnlyCall) {
  666. onCameraClick();
  667. }
  668. if (!microphoneOn) {
  669. onMicrophoneClick();
  670. }
  671. if (!isVoiceOnlyCall) {
  672. if (cameraEnumerator.getDeviceNames().length == 0) {
  673. binding.cameraButton.setVisibility(View.GONE);
  674. }
  675. if (cameraEnumerator.getDeviceNames().length > 1) {
  676. binding.switchSelfVideoButton.setVisibility(View.VISIBLE);
  677. }
  678. }
  679. if (!isConnectionEstablished()) {
  680. fetchSignalingSettings();
  681. }
  682. } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_CALL)) {
  683. checkIfSomeAreApproved();
  684. }
  685. }
  686. private void checkIfSomeAreApproved() {
  687. if (!isVoiceOnlyCall) {
  688. if (cameraEnumerator.getDeviceNames().length == 0) {
  689. binding.cameraButton.setVisibility(View.GONE);
  690. }
  691. if (cameraEnumerator.getDeviceNames().length > 1) {
  692. binding.switchSelfVideoButton.setVisibility(View.VISIBLE);
  693. }
  694. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA) && canPublishVideoStream) {
  695. if (!videoOn) {
  696. onCameraClick();
  697. }
  698. } else {
  699. binding.cameraButton.setImageResource(R.drawable.ic_videocam_off_white_24px);
  700. binding.cameraButton.setAlpha(0.7f);
  701. binding.switchSelfVideoButton.setVisibility(View.GONE);
  702. }
  703. }
  704. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE) && canPublishAudioStream) {
  705. if (!microphoneOn) {
  706. onMicrophoneClick();
  707. }
  708. } else {
  709. binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px);
  710. }
  711. if (!isConnectionEstablished()) {
  712. fetchSignalingSettings();
  713. }
  714. }
  715. @AfterPermissionDenied(100)
  716. private void onPermissionsDenied() {
  717. if (!isVoiceOnlyCall) {
  718. if (cameraEnumerator.getDeviceNames().length == 0) {
  719. binding.cameraButton.setVisibility(View.GONE);
  720. } else if (cameraEnumerator.getDeviceNames().length == 1) {
  721. binding.switchSelfVideoButton.setVisibility(View.GONE);
  722. }
  723. }
  724. if ((EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA) ||
  725. EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE))) {
  726. checkIfSomeAreApproved();
  727. } else if (!isConnectionEstablished()) {
  728. fetchSignalingSettings();
  729. }
  730. }
  731. private void onAudioManagerDevicesChanged(
  732. final WebRtcAudioManager.AudioDevice currentDevice,
  733. final Set<WebRtcAudioManager.AudioDevice> availableDevices) {
  734. Log.d(TAG, "onAudioManagerDevicesChanged: " + availableDevices + ", "
  735. + "currentDevice: " + currentDevice);
  736. final boolean shouldDisableProximityLock = (currentDevice == WebRtcAudioManager.AudioDevice.WIRED_HEADSET
  737. || currentDevice == WebRtcAudioManager.AudioDevice.SPEAKER_PHONE
  738. || currentDevice == WebRtcAudioManager.AudioDevice.BLUETOOTH);
  739. if (shouldDisableProximityLock) {
  740. powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITHOUT_PROXIMITY_SENSOR_LOCK);
  741. } else {
  742. powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.WITH_PROXIMITY_SENSOR_LOCK);
  743. }
  744. if (audioOutputDialog != null) {
  745. audioOutputDialog.updateOutputDeviceList();
  746. }
  747. updateAudioOutputButton(currentDevice);
  748. }
  749. private void cameraInitialization() {
  750. videoCapturer = createCameraCapturer(cameraEnumerator);
  751. //Create a VideoSource instance
  752. if (videoCapturer != null) {
  753. SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread",
  754. rootEglBase.getEglBaseContext());
  755. videoSource = peerConnectionFactory.createVideoSource(false);
  756. videoCapturer.initialize(surfaceTextureHelper, getApplicationContext(), videoSource.getCapturerObserver());
  757. }
  758. localVideoTrack = peerConnectionFactory.createVideoTrack("NCv0", videoSource);
  759. localStream.addTrack(localVideoTrack);
  760. localVideoTrack.setEnabled(false);
  761. localVideoTrack.addSink(binding.selfVideoRenderer);
  762. }
  763. private void microphoneInitialization() {
  764. //create an AudioSource instance
  765. audioSource = peerConnectionFactory.createAudioSource(audioConstraints);
  766. localAudioTrack = peerConnectionFactory.createAudioTrack("NCa0", audioSource);
  767. localAudioTrack.setEnabled(false);
  768. localStream.addTrack(localAudioTrack);
  769. }
  770. private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
  771. final String[] deviceNames = enumerator.getDeviceNames();
  772. // First, try to find front facing camera
  773. Logging.d(TAG, "Looking for front facing cameras.");
  774. for (String deviceName : deviceNames) {
  775. if (enumerator.isFrontFacing(deviceName)) {
  776. Logging.d(TAG, "Creating front facing camera capturer.");
  777. VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
  778. if (videoCapturer != null) {
  779. binding.selfVideoRenderer.setMirror(true);
  780. return videoCapturer;
  781. }
  782. }
  783. }
  784. // Front facing camera not found, try something else
  785. Logging.d(TAG, "Looking for other cameras.");
  786. for (String deviceName : deviceNames) {
  787. if (!enumerator.isFrontFacing(deviceName)) {
  788. Logging.d(TAG, "Creating other camera capturer.");
  789. VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
  790. if (videoCapturer != null) {
  791. binding.selfVideoRenderer.setMirror(false);
  792. return videoCapturer;
  793. }
  794. }
  795. }
  796. return null;
  797. }
  798. public void onMicrophoneClick() {
  799. if (!canPublishAudioStream) {
  800. microphoneOn = false;
  801. binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px);
  802. toggleMedia(false, false);
  803. }
  804. if (isVoiceOnlyCall && !isConnectionEstablished()) {
  805. fetchSignalingSettings();
  806. }
  807. if (!canPublishAudioStream) {
  808. // In the case no audio stream will be published it's not needed to check microphone permissions
  809. return;
  810. }
  811. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_MICROPHONE)) {
  812. if (!appPreferences.getPushToTalkIntroShown()) {
  813. int primary = viewThemeUtils.getScheme(binding.audioOutputButton.getContext()).getPrimary();
  814. spotlightView = new SpotlightView.Builder(this)
  815. .introAnimationDuration(300)
  816. .enableRevealAnimation(true)
  817. .performClick(false)
  818. .fadeinTextDuration(400)
  819. .headingTvColor(primary)
  820. .headingTvSize(20)
  821. .headingTvText(getResources().getString(R.string.nc_push_to_talk))
  822. .subHeadingTvColor(getResources().getColor(R.color.bg_default))
  823. .subHeadingTvSize(16)
  824. .subHeadingTvText(getResources().getString(R.string.nc_push_to_talk_desc))
  825. .maskColor(Color.parseColor("#dc000000"))
  826. .target(binding.microphoneButton)
  827. .lineAnimDuration(400)
  828. .lineAndArcColor(primary)
  829. .enableDismissAfterShown(true)
  830. .dismissOnBackPress(true)
  831. .usageId("pushToTalk")
  832. .show();
  833. appPreferences.setPushToTalkIntroShown(true);
  834. }
  835. if (!isPushToTalkActive) {
  836. microphoneOn = !microphoneOn;
  837. if (microphoneOn) {
  838. binding.microphoneButton.setImageResource(R.drawable.ic_mic_white_24px);
  839. updatePictureInPictureActions(R.drawable.ic_mic_white_24px,
  840. getResources().getString(R.string.nc_pip_microphone_mute),
  841. MICROPHONE_PIP_REQUEST_MUTE);
  842. } else {
  843. binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px);
  844. updatePictureInPictureActions(R.drawable.ic_mic_off_white_24px,
  845. getResources().getString(R.string.nc_pip_microphone_unmute),
  846. MICROPHONE_PIP_REQUEST_UNMUTE);
  847. }
  848. toggleMedia(microphoneOn, false);
  849. } else {
  850. binding.microphoneButton.setImageResource(R.drawable.ic_mic_white_24px);
  851. pulseAnimation.start();
  852. toggleMedia(true, false);
  853. }
  854. } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_MICROPHONE)) {
  855. // Microphone permission is permanently denied so we cannot request it normally.
  856. OpenAppDetailsDialogFragment.show(
  857. R.string.nc_microphone_permission_permanently_denied,
  858. R.string.nc_permissions_settings, (AppCompatActivity) this);
  859. } else {
  860. requestPermissions(PERMISSIONS_MICROPHONE, 100);
  861. }
  862. }
  863. public void onCameraClick() {
  864. if (!canPublishVideoStream) {
  865. videoOn = false;
  866. binding.cameraButton.setImageResource(R.drawable.ic_videocam_off_white_24px);
  867. binding.switchSelfVideoButton.setVisibility(View.GONE);
  868. return;
  869. }
  870. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA)) {
  871. videoOn = !videoOn;
  872. if (videoOn) {
  873. binding.cameraButton.setImageResource(R.drawable.ic_videocam_white_24px);
  874. if (cameraEnumerator.getDeviceNames().length > 1) {
  875. binding.switchSelfVideoButton.setVisibility(View.VISIBLE);
  876. }
  877. } else {
  878. binding.cameraButton.setImageResource(R.drawable.ic_videocam_off_white_24px);
  879. binding.switchSelfVideoButton.setVisibility(View.GONE);
  880. }
  881. toggleMedia(videoOn, true);
  882. } else if (EffortlessPermissions.somePermissionPermanentlyDenied(this, PERMISSIONS_CAMERA)) {
  883. // Camera permission is permanently denied so we cannot request it normally.
  884. OpenAppDetailsDialogFragment.show(
  885. R.string.nc_camera_permission_permanently_denied,
  886. R.string.nc_permissions_settings, (AppCompatActivity) this);
  887. } else {
  888. requestPermissions(PERMISSIONS_CAMERA, 100);
  889. }
  890. }
  891. public void switchCamera() {
  892. CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) videoCapturer;
  893. if (cameraVideoCapturer != null) {
  894. cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
  895. @Override
  896. public void onCameraSwitchDone(boolean currentCameraIsFront) {
  897. binding.selfVideoRenderer.setMirror(currentCameraIsFront);
  898. }
  899. @Override
  900. public void onCameraSwitchError(String s) {
  901. }
  902. });
  903. }
  904. }
  905. private void toggleMedia(boolean enable, boolean video) {
  906. String message;
  907. if (video) {
  908. message = "videoOff";
  909. if (enable) {
  910. binding.cameraButton.setAlpha(1.0f);
  911. message = "videoOn";
  912. startVideoCapture();
  913. } else {
  914. binding.cameraButton.setAlpha(0.7f);
  915. if (videoCapturer != null) {
  916. try {
  917. videoCapturer.stopCapture();
  918. } catch (InterruptedException e) {
  919. Log.d(TAG, "Failed to stop capturing video while sensor is near the ear");
  920. }
  921. }
  922. }
  923. if (localStream != null && localStream.videoTracks.size() > 0) {
  924. localStream.videoTracks.get(0).setEnabled(enable);
  925. }
  926. if (enable) {
  927. binding.selfVideoRenderer.setVisibility(View.VISIBLE);
  928. } else {
  929. binding.selfVideoRenderer.setVisibility(View.INVISIBLE);
  930. }
  931. } else {
  932. message = "audioOff";
  933. if (enable) {
  934. message = "audioOn";
  935. binding.microphoneButton.setAlpha(1.0f);
  936. } else {
  937. binding.microphoneButton.setAlpha(0.7f);
  938. }
  939. if (localStream != null && localStream.audioTracks.size() > 0) {
  940. localStream.audioTracks.get(0).setEnabled(enable);
  941. }
  942. }
  943. if (isConnectionEstablished() && peerConnectionWrapperList != null) {
  944. if (!hasMCU) {
  945. for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
  946. peerConnectionWrapper.sendChannelData(new DataChannelMessage(message));
  947. }
  948. } else {
  949. for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
  950. if (peerConnectionWrapper.getSessionId().equals(webSocketClient.getSessionId())) {
  951. peerConnectionWrapper.sendChannelData(new DataChannelMessage(message));
  952. break;
  953. }
  954. }
  955. }
  956. }
  957. }
  958. private void animateCallControls(boolean show, long startDelay) {
  959. if (isVoiceOnlyCall) {
  960. if (spotlightView != null && spotlightView.getVisibility() != View.GONE) {
  961. spotlightView.setVisibility(View.GONE);
  962. }
  963. } else if (!isPushToTalkActive) {
  964. float alpha;
  965. long duration;
  966. if (show) {
  967. callControlHandler.removeCallbacksAndMessages(null);
  968. callInfosHandler.removeCallbacksAndMessages(null);
  969. cameraSwitchHandler.removeCallbacksAndMessages(null);
  970. alpha = 1.0f;
  971. duration = 1000;
  972. if (binding.callControls.getVisibility() != View.VISIBLE) {
  973. binding.callControls.setAlpha(0.0f);
  974. binding.callControls.setVisibility(View.VISIBLE);
  975. binding.callInfosLinearLayout.setAlpha(0.0f);
  976. binding.callInfosLinearLayout.setVisibility(View.VISIBLE);
  977. binding.switchSelfVideoButton.setAlpha(0.0f);
  978. if (videoOn) {
  979. binding.switchSelfVideoButton.setVisibility(View.VISIBLE);
  980. }
  981. } else {
  982. callControlHandler.postDelayed(() -> animateCallControls(false, 0), 5000);
  983. return;
  984. }
  985. } else {
  986. alpha = 0.0f;
  987. duration = 1000;
  988. }
  989. binding.callControls.setEnabled(false);
  990. binding.callControls.animate()
  991. .translationY(0)
  992. .alpha(alpha)
  993. .setDuration(duration)
  994. .setStartDelay(startDelay)
  995. .setListener(new AnimatorListenerAdapter() {
  996. @Override
  997. public void onAnimationEnd(Animator animation) {
  998. super.onAnimationEnd(animation);
  999. if (!show) {
  1000. binding.callControls.setVisibility(View.GONE);
  1001. if (spotlightView != null && spotlightView.getVisibility() != View.GONE) {
  1002. spotlightView.setVisibility(View.GONE);
  1003. }
  1004. } else {
  1005. callControlHandler.postDelayed(new Runnable() {
  1006. @Override
  1007. public void run() {
  1008. if (!isPushToTalkActive) {
  1009. animateCallControls(false, 0);
  1010. }
  1011. }
  1012. }, 7500);
  1013. }
  1014. binding.callControls.setEnabled(true);
  1015. }
  1016. });
  1017. binding.callInfosLinearLayout.setEnabled(false);
  1018. binding.callInfosLinearLayout.animate()
  1019. .translationY(0)
  1020. .alpha(alpha)
  1021. .setDuration(duration)
  1022. .setStartDelay(startDelay)
  1023. .setListener(new AnimatorListenerAdapter() {
  1024. @Override
  1025. public void onAnimationEnd(Animator animation) {
  1026. super.onAnimationEnd(animation);
  1027. if (!show) {
  1028. binding.callInfosLinearLayout.setVisibility(View.GONE);
  1029. } else {
  1030. callInfosHandler.postDelayed(new Runnable() {
  1031. @Override
  1032. public void run() {
  1033. if (!isPushToTalkActive) {
  1034. animateCallControls(false, 0);
  1035. }
  1036. }
  1037. }, 7500);
  1038. }
  1039. binding.callInfosLinearLayout.setEnabled(true);
  1040. }
  1041. });
  1042. binding.switchSelfVideoButton.setEnabled(false);
  1043. binding.switchSelfVideoButton.animate()
  1044. .translationY(0)
  1045. .alpha(alpha)
  1046. .setDuration(duration)
  1047. .setStartDelay(startDelay)
  1048. .setListener(new AnimatorListenerAdapter() {
  1049. @Override
  1050. public void onAnimationEnd(Animator animation) {
  1051. super.onAnimationEnd(animation);
  1052. if (!show) {
  1053. binding.switchSelfVideoButton.setVisibility(View.GONE);
  1054. }
  1055. binding.switchSelfVideoButton.setEnabled(true);
  1056. }
  1057. });
  1058. }
  1059. }
  1060. @Override
  1061. public void onDestroy() {
  1062. signalingMessageReceiver.removeListener(participantListMessageListener);
  1063. signalingMessageReceiver.removeListener(offerMessageListener);
  1064. if (localStream != null) {
  1065. localStream.dispose();
  1066. localStream = null;
  1067. Log.d(TAG, "Disposed localStream");
  1068. } else {
  1069. Log.d(TAG, "localStream is null");
  1070. }
  1071. if (currentCallStatus != CallStatus.LEAVING) {
  1072. hangup(true);
  1073. }
  1074. powerManagerUtils.updatePhoneState(PowerManagerUtils.PhoneState.IDLE);
  1075. super.onDestroy();
  1076. }
  1077. private void fetchSignalingSettings() {
  1078. Log.d(TAG, "fetchSignalingSettings");
  1079. int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, new int[]{ApiUtils.APIv3, 2, 1});
  1080. ncApi.getSignalingSettings(credentials, ApiUtils.getUrlForSignalingSettings(apiVersion, baseUrl))
  1081. .subscribeOn(Schedulers.io())
  1082. .retry(3)
  1083. .observeOn(AndroidSchedulers.mainThread())
  1084. .subscribe(new Observer<SignalingSettingsOverall>() {
  1085. @Override
  1086. public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
  1087. // unused atm
  1088. }
  1089. @Override
  1090. public void onNext(@io.reactivex.annotations.NonNull SignalingSettingsOverall signalingSettingsOverall) {
  1091. if (signalingSettingsOverall.getOcs() != null
  1092. && signalingSettingsOverall.getOcs().getSettings() != null) {
  1093. externalSignalingServer = new ExternalSignalingServer();
  1094. if (!TextUtils.isEmpty(
  1095. signalingSettingsOverall.getOcs().getSettings().getExternalSignalingServer()) &&
  1096. !TextUtils.isEmpty(
  1097. signalingSettingsOverall.getOcs().getSettings().getExternalSignalingTicket())) {
  1098. externalSignalingServer = new ExternalSignalingServer();
  1099. externalSignalingServer.setExternalSignalingServer(
  1100. signalingSettingsOverall.getOcs().getSettings().getExternalSignalingServer());
  1101. externalSignalingServer.setExternalSignalingTicket(
  1102. signalingSettingsOverall.getOcs().getSettings().getExternalSignalingTicket());
  1103. hasExternalSignalingServer = true;
  1104. } else {
  1105. hasExternalSignalingServer = false;
  1106. }
  1107. Log.d(TAG, " hasExternalSignalingServer: " + hasExternalSignalingServer);
  1108. if (!"?".equals(conversationUser.getUserId()) && conversationUser.getId() != null) {
  1109. Log.d(TAG, "Update externalSignalingServer for: " + conversationUser.getId() +
  1110. " / " + conversationUser.getUserId());
  1111. userManager.updateExternalSignalingServer(conversationUser.getId(), externalSignalingServer)
  1112. .subscribeOn(Schedulers.io())
  1113. .subscribe();
  1114. } else {
  1115. conversationUser.setExternalSignalingServer(externalSignalingServer);
  1116. }
  1117. if (signalingSettingsOverall.getOcs().getSettings().getStunServers() != null) {
  1118. List<IceServer> stunServers =
  1119. signalingSettingsOverall.getOcs().getSettings().getStunServers();
  1120. if (apiVersion == ApiUtils.APIv3) {
  1121. for (IceServer stunServer : stunServers) {
  1122. if (stunServer.getUrls() != null) {
  1123. for (String url : stunServer.getUrls()) {
  1124. Log.d(TAG, " STUN server url: " + url);
  1125. iceServers.add(new PeerConnection.IceServer(url));
  1126. }
  1127. }
  1128. }
  1129. } else {
  1130. if (signalingSettingsOverall.getOcs().getSettings().getStunServers() != null) {
  1131. for (IceServer stunServer : stunServers) {
  1132. Log.d(TAG, " STUN server url: " + stunServer.getUrl());
  1133. iceServers.add(new PeerConnection.IceServer(stunServer.getUrl()));
  1134. }
  1135. }
  1136. }
  1137. }
  1138. if (signalingSettingsOverall.getOcs().getSettings().getTurnServers() != null) {
  1139. List<IceServer> turnServers =
  1140. signalingSettingsOverall.getOcs().getSettings().getTurnServers();
  1141. for (IceServer turnServer : turnServers) {
  1142. if (turnServer.getUrls() != null) {
  1143. for (String url : turnServer.getUrls()) {
  1144. Log.d(TAG, " TURN server url: " + url);
  1145. iceServers.add(new PeerConnection.IceServer(
  1146. url, turnServer.getUsername(), turnServer.getCredential()
  1147. ));
  1148. }
  1149. }
  1150. }
  1151. }
  1152. }
  1153. checkCapabilities();
  1154. }
  1155. @Override
  1156. public void onError(@io.reactivex.annotations.NonNull Throwable e) {
  1157. Log.e(TAG, e.getMessage(), e);
  1158. }
  1159. @Override
  1160. public void onComplete() {
  1161. // unused atm
  1162. }
  1163. });
  1164. }
  1165. private void checkCapabilities() {
  1166. ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl))
  1167. .retry(3)
  1168. .subscribeOn(Schedulers.io())
  1169. .observeOn(AndroidSchedulers.mainThread())
  1170. .subscribe(new Observer<CapabilitiesOverall>() {
  1171. @Override
  1172. public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
  1173. // unused atm
  1174. }
  1175. @Override
  1176. public void onNext(@io.reactivex.annotations.NonNull CapabilitiesOverall capabilitiesOverall) {
  1177. // FIXME check for compatible Call API version
  1178. if (hasExternalSignalingServer) {
  1179. setupAndInitiateWebSocketsConnection();
  1180. } else {
  1181. signalingMessageReceiver = internalSignalingMessageReceiver;
  1182. signalingMessageReceiver.addListener(participantListMessageListener);
  1183. signalingMessageReceiver.addListener(offerMessageListener);
  1184. signalingMessageSender = internalSignalingMessageSender;
  1185. joinRoomAndCall();
  1186. }
  1187. }
  1188. @Override
  1189. public void onError(@io.reactivex.annotations.NonNull Throwable e) {
  1190. // unused atm
  1191. }
  1192. @Override
  1193. public void onComplete() {
  1194. // unused atm
  1195. }
  1196. });
  1197. }
  1198. private void joinRoomAndCall() {
  1199. callSession = ApplicationWideCurrentRoomHolder.getInstance().getSession();
  1200. int apiVersion = ApiUtils.getConversationApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
  1201. Log.d(TAG, "joinRoomAndCall");
  1202. Log.d(TAG, " baseUrl= " + baseUrl);
  1203. Log.d(TAG, " roomToken= " + roomToken);
  1204. Log.d(TAG, " callSession= " + callSession);
  1205. String url = ApiUtils.getUrlForParticipantsActive(apiVersion, baseUrl, roomToken);
  1206. Log.d(TAG, " url= " + url);
  1207. if (TextUtils.isEmpty(callSession)) {
  1208. ncApi.joinRoom(credentials, url, conversationPassword)
  1209. .subscribeOn(Schedulers.io())
  1210. .observeOn(AndroidSchedulers.mainThread())
  1211. .retry(3)
  1212. .subscribe(new Observer<RoomOverall>() {
  1213. @Override
  1214. public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
  1215. // unused atm
  1216. }
  1217. @Override
  1218. public void onNext(@io.reactivex.annotations.NonNull RoomOverall roomOverall) {
  1219. callSession = roomOverall.getOcs().getData().getSessionId();
  1220. Log.d(TAG, " new callSession by joinRoom= " + callSession);
  1221. ApplicationWideCurrentRoomHolder.getInstance().setSession(callSession);
  1222. ApplicationWideCurrentRoomHolder.getInstance().setCurrentRoomId(roomId);
  1223. ApplicationWideCurrentRoomHolder.getInstance().setCurrentRoomToken(roomToken);
  1224. ApplicationWideCurrentRoomHolder.getInstance().setUserInRoom(conversationUser);
  1225. callOrJoinRoomViaWebSocket();
  1226. }
  1227. @Override
  1228. public void onError(@io.reactivex.annotations.NonNull Throwable e) {
  1229. Log.e(TAG, "joinRoom onError", e);
  1230. }
  1231. @Override
  1232. public void onComplete() {
  1233. Log.d(TAG, "joinRoom onComplete");
  1234. }
  1235. });
  1236. } else {
  1237. // we are in a room and start a call -> same session needs to be used
  1238. callOrJoinRoomViaWebSocket();
  1239. }
  1240. }
  1241. private void callOrJoinRoomViaWebSocket() {
  1242. if (hasExternalSignalingServer) {
  1243. webSocketClient.joinRoomWithRoomTokenAndSession(roomToken, callSession);
  1244. } else {
  1245. performCall();
  1246. }
  1247. }
  1248. private void performCall() {
  1249. int inCallFlag = Participant.InCallFlags.IN_CALL;
  1250. if (canPublishAudioStream) {
  1251. inCallFlag += Participant.InCallFlags.WITH_AUDIO;
  1252. }
  1253. if (!isVoiceOnlyCall && canPublishVideoStream) {
  1254. inCallFlag += Participant.InCallFlags.WITH_VIDEO;
  1255. }
  1256. int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
  1257. ncApi.joinCall(
  1258. credentials,
  1259. ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken),
  1260. inCallFlag,
  1261. isCallWithoutNotification)
  1262. .subscribeOn(Schedulers.io())
  1263. .retry(3)
  1264. .observeOn(AndroidSchedulers.mainThread())
  1265. .subscribe(new Observer<GenericOverall>() {
  1266. @Override
  1267. public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
  1268. // unused atm
  1269. }
  1270. @Override
  1271. public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) {
  1272. if (currentCallStatus != CallStatus.LEAVING) {
  1273. if (currentCallStatus != CallStatus.IN_CONVERSATION) {
  1274. setCallState(CallStatus.JOINED);
  1275. }
  1276. ApplicationWideCurrentRoomHolder.getInstance().setInCall(true);
  1277. ApplicationWideCurrentRoomHolder.getInstance().setDialing(false);
  1278. if (!TextUtils.isEmpty(roomToken)) {
  1279. NotificationUtils.INSTANCE.cancelExistingNotificationsForRoom(getApplicationContext(),
  1280. conversationUser,
  1281. roomToken);
  1282. }
  1283. if (!hasExternalSignalingServer) {
  1284. int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser,
  1285. new int[]{ApiUtils.APIv3, 2, 1});
  1286. AtomicInteger delayOnError = new AtomicInteger(0);
  1287. ncApi.pullSignalingMessages(credentials,
  1288. ApiUtils.getUrlForSignaling(apiVersion,
  1289. baseUrl,
  1290. roomToken))
  1291. .subscribeOn(Schedulers.io())
  1292. .observeOn(AndroidSchedulers.mainThread())
  1293. .repeatWhen(observable -> observable)
  1294. .takeWhile(observable -> isConnectionEstablished())
  1295. .doOnNext(value -> delayOnError.set(0))
  1296. .retryWhen(errors -> errors
  1297. .flatMap(error -> {
  1298. if (!isConnectionEstablished()) {
  1299. return Observable.error(error);
  1300. }
  1301. if (delayOnError.get() == 0) {
  1302. delayOnError.set(1);
  1303. } else if (delayOnError.get() < 16) {
  1304. delayOnError.set(delayOnError.get() * 2);
  1305. }
  1306. return Observable.timer(delayOnError.get(), TimeUnit.SECONDS);
  1307. })
  1308. )
  1309. .subscribe(new Observer<SignalingOverall>() {
  1310. @Override
  1311. public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
  1312. signalingDisposable = d;
  1313. }
  1314. @Override
  1315. public void onNext(
  1316. @io.reactivex.annotations.NonNull
  1317. SignalingOverall signalingOverall) {
  1318. receivedSignalingMessages(signalingOverall.getOcs().getSignalings());
  1319. }
  1320. @Override
  1321. public void onError(@io.reactivex.annotations.NonNull Throwable e) {
  1322. dispose(signalingDisposable);
  1323. }
  1324. @Override
  1325. public void onComplete() {
  1326. dispose(signalingDisposable);
  1327. }
  1328. });
  1329. }
  1330. }
  1331. }
  1332. @Override
  1333. public void onError(@io.reactivex.annotations.NonNull Throwable e) {
  1334. // unused atm
  1335. }
  1336. @Override
  1337. public void onComplete() {
  1338. // unused atm
  1339. }
  1340. });
  1341. }
  1342. private void setupAndInitiateWebSocketsConnection() {
  1343. if (webSocketConnectionHelper == null) {
  1344. webSocketConnectionHelper = new WebSocketConnectionHelper();
  1345. }
  1346. if (webSocketClient == null) {
  1347. webSocketClient = WebSocketConnectionHelper.getExternalSignalingInstanceForServer(
  1348. externalSignalingServer.getExternalSignalingServer(),
  1349. conversationUser, externalSignalingServer.getExternalSignalingTicket(),
  1350. TextUtils.isEmpty(credentials));
  1351. // Although setupAndInitiateWebSocketsConnection could be called several times the web socket is
  1352. // initialized just once, so the message receiver is also initialized just once.
  1353. signalingMessageReceiver = webSocketClient.getSignalingMessageReceiver();
  1354. signalingMessageReceiver.addListener(participantListMessageListener);
  1355. signalingMessageReceiver.addListener(offerMessageListener);
  1356. signalingMessageSender = webSocketClient.getSignalingMessageSender();
  1357. } else {
  1358. if (webSocketClient.isConnected() && currentCallStatus == CallStatus.PUBLISHER_FAILED) {
  1359. webSocketClient.restartWebSocket();
  1360. }
  1361. }
  1362. joinRoomAndCall();
  1363. }
  1364. private void initiateCall() {
  1365. if (!TextUtils.isEmpty(roomToken)) {
  1366. checkDevicePermissions();
  1367. } else {
  1368. handleFromNotification();
  1369. }
  1370. }
  1371. @Subscribe(threadMode = ThreadMode.BACKGROUND)
  1372. public void onMessageEvent(WebSocketCommunicationEvent webSocketCommunicationEvent) {
  1373. if (currentCallStatus == CallStatus.LEAVING) {
  1374. return;
  1375. }
  1376. switch (webSocketCommunicationEvent.getType()) {
  1377. case "hello":
  1378. Log.d(TAG, "onMessageEvent 'hello'");
  1379. if (!webSocketCommunicationEvent.getHashMap().containsKey("oldResumeId")) {
  1380. if (currentCallStatus == CallStatus.RECONNECTING) {
  1381. hangup(false);
  1382. } else {
  1383. setCallState(CallStatus.RECONNECTING);
  1384. runOnUiThread(this::initiateCall);
  1385. }
  1386. }
  1387. break;
  1388. case "roomJoined":
  1389. Log.d(TAG, "onMessageEvent 'roomJoined'");
  1390. startSendingNick();
  1391. if (webSocketCommunicationEvent.getHashMap().get("roomToken").equals(roomToken)) {
  1392. performCall();
  1393. }
  1394. break;
  1395. case "peerReadyForRequestingOffer":
  1396. Log.d(TAG, "onMessageEvent 'peerReadyForRequestingOffer'");
  1397. NCSignalingMessage ncSignalingMessage = new NCSignalingMessage();
  1398. // "to" property is not actually needed in the "requestoffer" signaling message, but it is used to
  1399. // set the recipient session ID in the assembled call message.
  1400. ncSignalingMessage.setTo(webSocketCommunicationEvent.getHashMap().get("sessionId"));
  1401. ncSignalingMessage.setRoomType("video");
  1402. ncSignalingMessage.setType("requestoffer");
  1403. signalingMessageSender.send(ncSignalingMessage);
  1404. break;
  1405. }
  1406. }
  1407. private void dispose(@Nullable Disposable disposable) {
  1408. if (disposable != null && !disposable.isDisposed()) {
  1409. disposable.dispose();
  1410. } else if (disposable == null) {
  1411. if (signalingDisposable != null && !signalingDisposable.isDisposed()) {
  1412. signalingDisposable.dispose();
  1413. signalingDisposable = null;
  1414. }
  1415. }
  1416. }
  1417. private void receivedSignalingMessages(@Nullable List<Signaling> signalingList) {
  1418. if (signalingList != null) {
  1419. for (Signaling signaling : signalingList) {
  1420. try {
  1421. receivedSignalingMessage(signaling);
  1422. } catch (IOException e) {
  1423. Log.e(TAG, "Failed to process received signaling message", e);
  1424. }
  1425. }
  1426. }
  1427. }
  1428. private void receivedSignalingMessage(Signaling signaling) throws IOException {
  1429. String messageType = signaling.getType();
  1430. if (!isConnectionEstablished() && currentCallStatus != CallStatus.CONNECTING) {
  1431. return;
  1432. }
  1433. if ("usersInRoom".equals(messageType)) {
  1434. internalSignalingMessageReceiver.process((List<Map<String, Object>>) signaling.getMessageWrapper());
  1435. } else if ("message".equals(messageType)) {
  1436. NCSignalingMessage ncSignalingMessage = LoganSquare.parse(signaling.getMessageWrapper().toString(),
  1437. NCSignalingMessage.class);
  1438. internalSignalingMessageReceiver.process(ncSignalingMessage);
  1439. } else {
  1440. Log.e(TAG, "unexpected message type when receiving signaling message");
  1441. }
  1442. }
  1443. private void hangup(boolean shutDownView) {
  1444. Log.d(TAG, "hangup! shutDownView=" + shutDownView);
  1445. if (shutDownView) {
  1446. setCallState(CallStatus.LEAVING);
  1447. }
  1448. stopCallingSound();
  1449. dispose(null);
  1450. if (shutDownView) {
  1451. if (videoCapturer != null) {
  1452. try {
  1453. videoCapturer.stopCapture();
  1454. } catch (InterruptedException e) {
  1455. Log.e(TAG, "Failed to stop capturing while hanging up");
  1456. }
  1457. videoCapturer.dispose();
  1458. videoCapturer = null;
  1459. }
  1460. binding.selfVideoRenderer.release();
  1461. if (audioSource != null) {
  1462. audioSource.dispose();
  1463. audioSource = null;
  1464. }
  1465. runOnUiThread(() -> {
  1466. if (audioManager != null) {
  1467. audioManager.stop();
  1468. audioManager = null;
  1469. }
  1470. });
  1471. if (videoSource != null) {
  1472. videoSource = null;
  1473. }
  1474. if (peerConnectionFactory != null) {
  1475. peerConnectionFactory = null;
  1476. }
  1477. localAudioTrack = null;
  1478. localVideoTrack = null;
  1479. if (TextUtils.isEmpty(credentials) && hasExternalSignalingServer) {
  1480. WebSocketConnectionHelper.deleteExternalSignalingInstanceForUserEntity(-1);
  1481. }
  1482. }
  1483. List<String> sessionIdsToEnd = new ArrayList<String>(peerConnectionWrapperList.size());
  1484. for (PeerConnectionWrapper wrapper : peerConnectionWrapperList) {
  1485. sessionIdsToEnd.add(wrapper.getSessionId());
  1486. }
  1487. for (String sessionId : sessionIdsToEnd) {
  1488. endPeerConnection(sessionId, false);
  1489. }
  1490. hangupNetworkCalls(shutDownView);
  1491. ApplicationWideCurrentRoomHolder.getInstance().setInCall(false);
  1492. }
  1493. private void hangupNetworkCalls(boolean shutDownView) {
  1494. Log.d(TAG, "hangupNetworkCalls. shutDownView=" + shutDownView);
  1495. int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
  1496. ncApi.leaveCall(credentials, ApiUtils.getUrlForCall(apiVersion, baseUrl, roomToken))
  1497. .subscribeOn(Schedulers.io())
  1498. .observeOn(AndroidSchedulers.mainThread())
  1499. .subscribe(new Observer<GenericOverall>() {
  1500. @Override
  1501. public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
  1502. // unused atm
  1503. }
  1504. @Override
  1505. public void onNext(@io.reactivex.annotations.NonNull GenericOverall genericOverall) {
  1506. if (shutDownView) {
  1507. finish();
  1508. } else if (currentCallStatus == CallStatus.RECONNECTING
  1509. || currentCallStatus == CallStatus.PUBLISHER_FAILED) {
  1510. initiateCall();
  1511. }
  1512. }
  1513. @Override
  1514. public void onError(@io.reactivex.annotations.NonNull Throwable e) {
  1515. // unused atm
  1516. }
  1517. @Override
  1518. public void onComplete() {
  1519. // unused atm
  1520. }
  1521. });
  1522. }
  1523. private void startVideoCapture() {
  1524. if (videoCapturer != null) {
  1525. videoCapturer.startCapture(1280, 720, 30);
  1526. }
  1527. }
  1528. private void processUsersInRoom(List<Participant> participants) {
  1529. Log.d(TAG, "processUsersInRoom");
  1530. List<String> newSessions = new ArrayList<>();
  1531. Set<String> oldSessions = new HashSet<>();
  1532. Map<String, String> userIdsBySessionId = new HashMap<>();
  1533. hasMCU = hasExternalSignalingServer && webSocketClient != null && webSocketClient.hasMCU();
  1534. Log.d(TAG, " hasMCU is " + hasMCU);
  1535. // The signaling session is the same as the Nextcloud session only when the MCU is not used.
  1536. String currentSessionId = callSession;
  1537. if (hasMCU) {
  1538. currentSessionId = webSocketClient.getSessionId();
  1539. }
  1540. Log.d(TAG, " currentSessionId is " + currentSessionId);
  1541. boolean isSelfInCall = false;
  1542. for (Participant participant : participants) {
  1543. long inCallFlag = participant.getInCall();
  1544. if (!participant.getSessionId().equals(currentSessionId)) {
  1545. Log.d(TAG, " inCallFlag of participant "
  1546. + participant.getSessionId().substring(0, 4)
  1547. + " : "
  1548. + inCallFlag);
  1549. boolean isInCall = inCallFlag != 0;
  1550. if (isInCall) {
  1551. newSessions.add(participant.getSessionId());
  1552. }
  1553. userIdsBySessionId.put(participant.getSessionId(), participant.getUserId());
  1554. } else {
  1555. Log.d(TAG, " inCallFlag of currentSessionId: " + inCallFlag);
  1556. isSelfInCall = inCallFlag != 0;
  1557. if (inCallFlag == 0 && currentCallStatus != CallStatus.LEAVING && ApplicationWideCurrentRoomHolder.getInstance().isInCall()) {
  1558. Log.d(TAG, "Most probably a moderator ended the call for all.");
  1559. hangup(true);
  1560. return;
  1561. }
  1562. }
  1563. }
  1564. for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
  1565. if (!peerConnectionWrapper.isMCUPublisher()) {
  1566. oldSessions.add(peerConnectionWrapper.getSessionId());
  1567. }
  1568. }
  1569. if (!isSelfInCall) {
  1570. Log.d(TAG, "Self not in call, disconnecting from all other sessions");
  1571. for (String sessionId : oldSessions) {
  1572. Log.d(TAG, " oldSession that will be removed is: " + sessionId);
  1573. endPeerConnection(sessionId, false);
  1574. }
  1575. return;
  1576. }
  1577. // Calculate sessions that left the call
  1578. List<String> disconnectedSessions = new ArrayList<>(oldSessions);
  1579. disconnectedSessions.removeAll(newSessions);
  1580. // Calculate sessions that join the call
  1581. newSessions.removeAll(oldSessions);
  1582. if (currentCallStatus == CallStatus.LEAVING) {
  1583. return;
  1584. }
  1585. if (newSessions.size() > 0 && !hasMCU) {
  1586. getPeersForCall();
  1587. }
  1588. if (hasMCU) {
  1589. // Ensure that own publishing peer is set up.
  1590. getOrCreatePeerConnectionWrapperForSessionIdAndType(webSocketClient.getSessionId(), VIDEO_STREAM_TYPE_VIDEO, true);
  1591. }
  1592. for (String sessionId : newSessions) {
  1593. Log.d(TAG, " newSession joined: " + sessionId);
  1594. getOrCreatePeerConnectionWrapperForSessionIdAndType(sessionId, VIDEO_STREAM_TYPE_VIDEO, false);
  1595. String userId = userIdsBySessionId.get(sessionId);
  1596. runOnUiThread(() -> {
  1597. setupVideoStreamForLayout(
  1598. null,
  1599. sessionId,
  1600. userId,
  1601. false,
  1602. VIDEO_STREAM_TYPE_VIDEO);
  1603. });
  1604. }
  1605. if (newSessions.size() > 0 && currentCallStatus != CallStatus.IN_CONVERSATION) {
  1606. setCallState(CallStatus.IN_CONVERSATION);
  1607. }
  1608. for (String sessionId : disconnectedSessions) {
  1609. Log.d(TAG, " oldSession that will be removed is: " + sessionId);
  1610. endPeerConnection(sessionId, false);
  1611. }
  1612. }
  1613. private void getPeersForCall() {
  1614. Log.d(TAG, "getPeersForCall");
  1615. int apiVersion = ApiUtils.getCallApiVersion(conversationUser, new int[]{ApiUtils.APIv4, 1});
  1616. ncApi.getPeersForCall(
  1617. credentials,
  1618. ApiUtils.getUrlForCall(
  1619. apiVersion,
  1620. baseUrl,
  1621. roomToken))
  1622. .subscribeOn(Schedulers.io())
  1623. .subscribe(new Observer<ParticipantsOverall>() {
  1624. @Override
  1625. public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
  1626. // unused atm
  1627. }
  1628. @Override
  1629. public void onNext(@io.reactivex.annotations.NonNull ParticipantsOverall participantsOverall) {
  1630. participantMap = new HashMap<>();
  1631. for (Participant participant : participantsOverall.getOcs().getData()) {
  1632. participantMap.put(participant.getSessionId(), participant);
  1633. }
  1634. }
  1635. @Override
  1636. public void onError(@io.reactivex.annotations.NonNull Throwable e) {
  1637. Log.e(TAG, "error while executing getPeersForCall", e);
  1638. }
  1639. @Override
  1640. public void onComplete() {
  1641. // unused atm
  1642. }
  1643. });
  1644. }
  1645. private void deletePeerConnection(PeerConnectionWrapper peerConnectionWrapper) {
  1646. peerConnectionWrapper.removePeerConnection();
  1647. peerConnectionWrapperList.remove(peerConnectionWrapper);
  1648. }
  1649. private PeerConnectionWrapper getPeerConnectionWrapperForSessionIdAndType(String sessionId, String type) {
  1650. for (PeerConnectionWrapper wrapper : peerConnectionWrapperList) {
  1651. if (wrapper.getSessionId().equals(sessionId)
  1652. && wrapper.getVideoStreamType().equals(type)) {
  1653. return wrapper;
  1654. }
  1655. }
  1656. return null;
  1657. }
  1658. private PeerConnectionWrapper getOrCreatePeerConnectionWrapperForSessionIdAndType(String sessionId,
  1659. String type,
  1660. boolean publisher) {
  1661. PeerConnectionWrapper peerConnectionWrapper;
  1662. if ((peerConnectionWrapper = getPeerConnectionWrapperForSessionIdAndType(sessionId, type)) != null) {
  1663. return peerConnectionWrapper;
  1664. } else {
  1665. if (peerConnectionFactory == null) {
  1666. Log.e(TAG, "peerConnectionFactory was null in getOrCreatePeerConnectionWrapperForSessionIdAndType.");
  1667. Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry),
  1668. Toast.LENGTH_LONG).show();
  1669. hangup(true);
  1670. return null;
  1671. }
  1672. if (hasMCU && publisher) {
  1673. peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
  1674. iceServers,
  1675. sdpConstraintsForMCU,
  1676. sessionId,
  1677. callSession,
  1678. localStream,
  1679. true,
  1680. true,
  1681. type,
  1682. signalingMessageReceiver);
  1683. } else if (hasMCU) {
  1684. peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
  1685. iceServers,
  1686. sdpConstraints,
  1687. sessionId,
  1688. callSession,
  1689. null,
  1690. false,
  1691. true,
  1692. type,
  1693. signalingMessageReceiver);
  1694. } else {
  1695. if (!"screen".equals(type)) {
  1696. peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
  1697. iceServers,
  1698. sdpConstraints,
  1699. sessionId,
  1700. callSession,
  1701. localStream,
  1702. false,
  1703. false,
  1704. type,
  1705. signalingMessageReceiver);
  1706. } else {
  1707. peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
  1708. iceServers,
  1709. sdpConstraints,
  1710. sessionId,
  1711. callSession,
  1712. null,
  1713. false,
  1714. false,
  1715. type,
  1716. signalingMessageReceiver);
  1717. }
  1718. }
  1719. peerConnectionWrapperList.add(peerConnectionWrapper);
  1720. // Currently there is no separation between call participants and peer connections, so any video peer
  1721. // connection (except the own publisher connection) is treated as a call participant.
  1722. if (!publisher && "video".equals(type)) {
  1723. SignalingMessageReceiver.CallParticipantMessageListener callParticipantMessageListener =
  1724. new CallActivityCallParticipantMessageListener(sessionId);
  1725. callParticipantMessageListeners.put(sessionId, callParticipantMessageListener);
  1726. signalingMessageReceiver.addListener(callParticipantMessageListener, sessionId);
  1727. }
  1728. if (publisher) {
  1729. startSendingNick();
  1730. }
  1731. return peerConnectionWrapper;
  1732. }
  1733. }
  1734. private List<PeerConnectionWrapper> getPeerConnectionWrapperListForSessionId(String sessionId) {
  1735. List<PeerConnectionWrapper> internalList = new ArrayList<>();
  1736. for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
  1737. if (peerConnectionWrapper.getSessionId().equals(sessionId)) {
  1738. internalList.add(peerConnectionWrapper);
  1739. }
  1740. }
  1741. return internalList;
  1742. }
  1743. private void endPeerConnection(String sessionId, boolean justScreen) {
  1744. List<PeerConnectionWrapper> peerConnectionWrappers;
  1745. if (!(peerConnectionWrappers = getPeerConnectionWrapperListForSessionId(sessionId)).isEmpty()) {
  1746. for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrappers) {
  1747. if (peerConnectionWrapper.getSessionId().equals(sessionId)) {
  1748. String videoStreamType = peerConnectionWrapper.getVideoStreamType();
  1749. if (VIDEO_STREAM_TYPE_SCREEN.equals(videoStreamType) || !justScreen) {
  1750. runOnUiThread(() -> removeMediaStream(sessionId, videoStreamType));
  1751. deletePeerConnection(peerConnectionWrapper);
  1752. }
  1753. }
  1754. }
  1755. }
  1756. if (!justScreen) {
  1757. SignalingMessageReceiver.CallParticipantMessageListener listener = callParticipantMessageListeners.remove(sessionId);
  1758. signalingMessageReceiver.removeListener(listener);
  1759. }
  1760. }
  1761. private void removeMediaStream(String sessionId, String videoStreamType) {
  1762. Log.d(TAG, "removeMediaStream");
  1763. participantDisplayItems.remove(sessionId + "-" + videoStreamType);
  1764. if (!isDestroyed()) {
  1765. initGridAdapter();
  1766. }
  1767. }
  1768. @Subscribe(threadMode = ThreadMode.MAIN)
  1769. public void onMessageEvent(ConfigurationChangeEvent configurationChangeEvent) {
  1770. powerManagerUtils.setOrientation(Objects.requireNonNull(getResources()).getConfiguration().orientation);
  1771. initGridAdapter();
  1772. updateSelfVideoViewPosition();
  1773. }
  1774. private void updateSelfVideoViewConnected(boolean connected) {
  1775. // FIXME In voice only calls there is no video view, so the progress bar would appear floating in the middle of
  1776. // nowhere. However, a way to signal that the local participant is not connected to the HPB is still need in
  1777. // that case.
  1778. if (!connected && !isVoiceOnlyCall) {
  1779. binding.selfVideoViewProgressBar.setVisibility(View.VISIBLE);
  1780. } else {
  1781. binding.selfVideoViewProgressBar.setVisibility(View.GONE);
  1782. }
  1783. }
  1784. private void updateSelfVideoViewPosition() {
  1785. Log.d(TAG, "updateSelfVideoViewPosition");
  1786. if (!isInPipMode) {
  1787. FrameLayout.LayoutParams layoutParams =
  1788. (FrameLayout.LayoutParams) binding.selfVideoRenderer.getLayoutParams();
  1789. DisplayMetrics displayMetrics = getApplicationContext().getResources().getDisplayMetrics();
  1790. int screenWidthPx = displayMetrics.widthPixels;
  1791. int screenWidthDp = (int) DisplayUtils.convertPixelToDp(screenWidthPx, getApplicationContext());
  1792. float newXafterRotate = 0;
  1793. float newYafterRotate;
  1794. if (binding.callInfosLinearLayout.getVisibility() == View.VISIBLE) {
  1795. newYafterRotate = 250;
  1796. } else {
  1797. newYafterRotate = 20;
  1798. }
  1799. if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
  1800. layoutParams.height = (int) getResources().getDimension(R.dimen.call_self_video_short_side_length);
  1801. layoutParams.width = (int) getResources().getDimension(R.dimen.call_self_video_long_side_length);
  1802. newXafterRotate = (float) (screenWidthDp - getResources().getDimension(R.dimen.call_self_video_short_side_length) * 0.8);
  1803. } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
  1804. layoutParams.height = (int) getResources().getDimension(R.dimen.call_self_video_long_side_length);
  1805. layoutParams.width = (int) getResources().getDimension(R.dimen.call_self_video_short_side_length);
  1806. newXafterRotate = (float) (screenWidthDp - getResources().getDimension(R.dimen.call_self_video_short_side_length) * 0.5);
  1807. }
  1808. binding.selfVideoRenderer.setLayoutParams(layoutParams);
  1809. int newXafterRotatePx = (int) DisplayUtils.convertDpToPixel(newXafterRotate, getApplicationContext());
  1810. binding.selfVideoViewWrapper.setY(newYafterRotate);
  1811. binding.selfVideoViewWrapper.setX(newXafterRotatePx);
  1812. }
  1813. }
  1814. @Subscribe(threadMode = ThreadMode.MAIN)
  1815. public void onMessageEvent(PeerConnectionEvent peerConnectionEvent) {
  1816. String sessionId = peerConnectionEvent.getSessionId();
  1817. String participantDisplayItemId = sessionId + "-" + peerConnectionEvent.getVideoStreamType();
  1818. if (peerConnectionEvent.getPeerConnectionEventType() ==
  1819. PeerConnectionEvent.PeerConnectionEventType.PEER_CONNECTED) {
  1820. if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
  1821. updateSelfVideoViewConnected(true);
  1822. } else if (participantDisplayItems.get(participantDisplayItemId) != null) {
  1823. participantDisplayItems.get(participantDisplayItemId).setConnected(true);
  1824. participantsAdapter.notifyDataSetChanged();
  1825. }
  1826. } else if (peerConnectionEvent.getPeerConnectionEventType() ==
  1827. PeerConnectionEvent.PeerConnectionEventType.PEER_DISCONNECTED) {
  1828. if (webSocketClient != null && webSocketClient.getSessionId() != null && webSocketClient.getSessionId().equals(sessionId)) {
  1829. updateSelfVideoViewConnected(false);
  1830. } else if (participantDisplayItems.get(participantDisplayItemId) != null) {
  1831. participantDisplayItems.get(participantDisplayItemId).setConnected(false);
  1832. participantsAdapter.notifyDataSetChanged();
  1833. }
  1834. } else if (peerConnectionEvent.getPeerConnectionEventType() ==
  1835. PeerConnectionEvent.PeerConnectionEventType.PEER_CLOSED) {
  1836. endPeerConnection(sessionId, VIDEO_STREAM_TYPE_SCREEN.equals(peerConnectionEvent.getVideoStreamType()));
  1837. } else if (peerConnectionEvent.getPeerConnectionEventType() ==
  1838. PeerConnectionEvent.PeerConnectionEventType.SENSOR_FAR ||
  1839. peerConnectionEvent.getPeerConnectionEventType() ==
  1840. PeerConnectionEvent.PeerConnectionEventType.SENSOR_NEAR) {
  1841. if (!isVoiceOnlyCall) {
  1842. boolean enableVideo = peerConnectionEvent.getPeerConnectionEventType() ==
  1843. PeerConnectionEvent.PeerConnectionEventType.SENSOR_FAR && videoOn;
  1844. if (EffortlessPermissions.hasPermissions(this, PERMISSIONS_CAMERA) &&
  1845. (currentCallStatus == CallStatus.CONNECTING || isConnectionEstablished()) && videoOn
  1846. && enableVideo != localVideoTrack.enabled()) {
  1847. toggleMedia(enableVideo, true);
  1848. }
  1849. }
  1850. } else if (peerConnectionEvent.getPeerConnectionEventType() ==
  1851. PeerConnectionEvent.PeerConnectionEventType.NICK_CHANGE) {
  1852. if (participantDisplayItems.get(participantDisplayItemId) != null) {
  1853. participantDisplayItems.get(participantDisplayItemId).setNick(peerConnectionEvent.getNick());
  1854. participantsAdapter.notifyDataSetChanged();
  1855. }
  1856. } else if (peerConnectionEvent.getPeerConnectionEventType() ==
  1857. PeerConnectionEvent.PeerConnectionEventType.VIDEO_CHANGE && !isVoiceOnlyCall) {
  1858. if (participantDisplayItems.get(participantDisplayItemId) != null) {
  1859. participantDisplayItems.get(participantDisplayItemId).setStreamEnabled(peerConnectionEvent.getChangeValue());
  1860. participantsAdapter.notifyDataSetChanged();
  1861. }
  1862. } else if (peerConnectionEvent.getPeerConnectionEventType() ==
  1863. PeerConnectionEvent.PeerConnectionEventType.AUDIO_CHANGE) {
  1864. if (participantDisplayItems.get(participantDisplayItemId) != null) {
  1865. participantDisplayItems.get(participantDisplayItemId).setAudioEnabled(peerConnectionEvent.getChangeValue());
  1866. participantsAdapter.notifyDataSetChanged();
  1867. }
  1868. } else if (peerConnectionEvent.getPeerConnectionEventType() ==
  1869. PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED) {
  1870. setCallState(CallStatus.PUBLISHER_FAILED);
  1871. webSocketClient.clearResumeId();
  1872. hangup(false);
  1873. }
  1874. }
  1875. private void startSendingNick() {
  1876. DataChannelMessageNick dataChannelMessage = new DataChannelMessageNick();
  1877. dataChannelMessage.setType("nickChanged");
  1878. HashMap<String, String> nickChangedPayload = new HashMap<>();
  1879. nickChangedPayload.put("userid", conversationUser.getUserId());
  1880. nickChangedPayload.put("name", conversationUser.getDisplayName());
  1881. dataChannelMessage.setPayload(nickChangedPayload);
  1882. for (PeerConnectionWrapper peerConnectionWrapper : peerConnectionWrapperList) {
  1883. if (peerConnectionWrapper.isMCUPublisher()) {
  1884. Observable
  1885. .interval(1, TimeUnit.SECONDS)
  1886. .repeatUntil(() -> (!isConnectionEstablished() || isDestroyed()))
  1887. .observeOn(Schedulers.io())
  1888. .subscribe(new Observer<Long>() {
  1889. @Override
  1890. public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
  1891. // unused atm
  1892. }
  1893. @Override
  1894. public void onNext(@io.reactivex.annotations.NonNull Long aLong) {
  1895. peerConnectionWrapper.sendNickChannelData(dataChannelMessage);
  1896. }
  1897. @Override
  1898. public void onError(@io.reactivex.annotations.NonNull Throwable e) {
  1899. // unused atm
  1900. }
  1901. @Override
  1902. public void onComplete() {
  1903. // unused atm
  1904. }
  1905. });
  1906. break;
  1907. }
  1908. }
  1909. }
  1910. @Subscribe(threadMode = ThreadMode.MAIN)
  1911. public void onMessageEvent(MediaStreamEvent mediaStreamEvent) {
  1912. if (mediaStreamEvent.getMediaStream() != null) {
  1913. boolean hasAtLeastOneVideoStream = mediaStreamEvent.getMediaStream().videoTracks != null
  1914. && mediaStreamEvent.getMediaStream().videoTracks.size() > 0;
  1915. setupVideoStreamForLayout(
  1916. mediaStreamEvent.getMediaStream(),
  1917. mediaStreamEvent.getSession(),
  1918. null,
  1919. hasAtLeastOneVideoStream,
  1920. mediaStreamEvent.getVideoStreamType());
  1921. } else {
  1922. setupVideoStreamForLayout(
  1923. null,
  1924. mediaStreamEvent.getSession(),
  1925. null,
  1926. false,
  1927. mediaStreamEvent.getVideoStreamType());
  1928. }
  1929. }
  1930. @Subscribe(threadMode = ThreadMode.BACKGROUND)
  1931. public void onMessageEvent(SessionDescriptionSendEvent sessionDescriptionSend) {
  1932. NCSignalingMessage ncSignalingMessage = new NCSignalingMessage();
  1933. ncSignalingMessage.setTo(sessionDescriptionSend.getPeerId());
  1934. ncSignalingMessage.setRoomType(sessionDescriptionSend.getVideoStreamType());
  1935. ncSignalingMessage.setType(sessionDescriptionSend.getType());
  1936. NCMessagePayload ncMessagePayload = new NCMessagePayload();
  1937. ncMessagePayload.setType(sessionDescriptionSend.getType());
  1938. if (!"candidate".equals(sessionDescriptionSend.getType())) {
  1939. ncMessagePayload.setSdp(sessionDescriptionSend.getSessionDescription().description);
  1940. ncMessagePayload.setNick(conversationUser.getDisplayName());
  1941. } else {
  1942. ncMessagePayload.setIceCandidate(sessionDescriptionSend.getNcIceCandidate());
  1943. }
  1944. ncSignalingMessage.setPayload(ncMessagePayload);
  1945. signalingMessageSender.send(ncSignalingMessage);
  1946. }
  1947. @Override
  1948. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
  1949. @NonNull int[] grantResults) {
  1950. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  1951. EffortlessPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults,
  1952. this);
  1953. }
  1954. private void setupVideoStreamForLayout(@Nullable MediaStream mediaStream,
  1955. String session,
  1956. String userId,
  1957. boolean videoStreamEnabled,
  1958. String videoStreamType) {
  1959. PeerConnectionWrapper peerConnectionWrapper = getPeerConnectionWrapperForSessionIdAndType(session,
  1960. videoStreamType);
  1961. boolean connected = false;
  1962. if (peerConnectionWrapper != null) {
  1963. PeerConnection.IceConnectionState iceConnectionState = peerConnectionWrapper.getPeerConnection().iceConnectionState();
  1964. connected = iceConnectionState == PeerConnection.IceConnectionState.CONNECTED ||
  1965. iceConnectionState == PeerConnection.IceConnectionState.COMPLETED;
  1966. }
  1967. String nick;
  1968. if (hasExternalSignalingServer) {
  1969. nick = webSocketClient.getDisplayNameForSession(session);
  1970. } else {
  1971. nick = peerConnectionWrapper != null ? peerConnectionWrapper.getNick() : "";
  1972. }
  1973. String userId4Usage = userId;
  1974. if (userId4Usage == null) {
  1975. if (hasMCU) {
  1976. userId4Usage = webSocketClient.getUserIdForSession(session);
  1977. } else if (participantMap.get(session) != null && participantMap.get(session).getCalculatedActorType() == Participant.ActorType.USERS) {
  1978. userId4Usage = participantMap.get(session).getCalculatedActorId();
  1979. }
  1980. }
  1981. ParticipantDisplayItem participantDisplayItem = new ParticipantDisplayItem(baseUrl,
  1982. userId4Usage,
  1983. session,
  1984. connected,
  1985. nick,
  1986. mediaStream,
  1987. videoStreamType,
  1988. videoStreamEnabled,
  1989. rootEglBase);
  1990. participantDisplayItems.put(session + "-" + videoStreamType, participantDisplayItem);
  1991. initGridAdapter();
  1992. }
  1993. private void setCallState(CallStatus callState) {
  1994. if (currentCallStatus == null || currentCallStatus != callState) {
  1995. currentCallStatus = callState;
  1996. if (handler == null) {
  1997. handler = new Handler(Looper.getMainLooper());
  1998. } else {
  1999. handler.removeCallbacksAndMessages(null);
  2000. }
  2001. switch (callState) {
  2002. case CONNECTING:
  2003. handler.post(() -> {
  2004. playCallingSound();
  2005. if (isIncomingCallFromNotification) {
  2006. binding.callStates.callStateTextView.setText(R.string.nc_call_incoming);
  2007. } else {
  2008. binding.callStates.callStateTextView.setText(R.string.nc_call_ringing);
  2009. }
  2010. binding.callConversationNameTextView.setText(conversationName);
  2011. binding.callModeTextView.setText(getDescriptionForCallType());
  2012. if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) {
  2013. binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE);
  2014. }
  2015. if (binding.gridview.getVisibility() != View.INVISIBLE) {
  2016. binding.gridview.setVisibility(View.INVISIBLE);
  2017. }
  2018. if (binding.callStates.callStateProgressBar.getVisibility() != View.VISIBLE) {
  2019. binding.callStates.callStateProgressBar.setVisibility(View.VISIBLE);
  2020. }
  2021. if (binding.callStates.errorImageView.getVisibility() != View.GONE) {
  2022. binding.callStates.errorImageView.setVisibility(View.GONE);
  2023. }
  2024. });
  2025. break;
  2026. case CALLING_TIMEOUT:
  2027. handler.post(() -> {
  2028. hangup(false);
  2029. binding.callStates.callStateTextView.setText(R.string.nc_call_timeout);
  2030. binding.callModeTextView.setText(getDescriptionForCallType());
  2031. if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) {
  2032. binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE);
  2033. }
  2034. if (binding.callStates.callStateProgressBar.getVisibility() != View.GONE) {
  2035. binding.callStates.callStateProgressBar.setVisibility(View.GONE);
  2036. }
  2037. if (binding.gridview.getVisibility() != View.INVISIBLE) {
  2038. binding.gridview.setVisibility(View.INVISIBLE);
  2039. }
  2040. binding.callStates.errorImageView.setImageResource(R.drawable.ic_av_timer_timer_24dp);
  2041. if (binding.callStates.errorImageView.getVisibility() != View.VISIBLE) {
  2042. binding.callStates.errorImageView.setVisibility(View.VISIBLE);
  2043. }
  2044. });
  2045. break;
  2046. case PUBLISHER_FAILED:
  2047. handler.post(() -> {
  2048. // No calling sound when the publisher failed
  2049. binding.callStates.callStateTextView.setText(R.string.nc_call_reconnecting);
  2050. binding.callModeTextView.setText(getDescriptionForCallType());
  2051. if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) {
  2052. binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE);
  2053. }
  2054. if (binding.gridview.getVisibility() != View.INVISIBLE) {
  2055. binding.gridview.setVisibility(View.INVISIBLE);
  2056. }
  2057. if (binding.callStates.callStateProgressBar.getVisibility() != View.VISIBLE) {
  2058. binding.callStates.callStateProgressBar.setVisibility(View.VISIBLE);
  2059. }
  2060. if (binding.callStates.errorImageView.getVisibility() != View.GONE) {
  2061. binding.callStates.errorImageView.setVisibility(View.GONE);
  2062. }
  2063. });
  2064. break;
  2065. case RECONNECTING:
  2066. handler.post(() -> {
  2067. playCallingSound();
  2068. binding.callStates.callStateTextView.setText(R.string.nc_call_reconnecting);
  2069. binding.callModeTextView.setText(getDescriptionForCallType());
  2070. if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) {
  2071. binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE);
  2072. }
  2073. if (binding.gridview.getVisibility() != View.INVISIBLE) {
  2074. binding.gridview.setVisibility(View.INVISIBLE);
  2075. }
  2076. if (binding.callStates.callStateProgressBar.getVisibility() != View.VISIBLE) {
  2077. binding.callStates.callStateProgressBar.setVisibility(View.VISIBLE);
  2078. }
  2079. if (binding.callStates.errorImageView.getVisibility() != View.GONE) {
  2080. binding.callStates.errorImageView.setVisibility(View.GONE);
  2081. }
  2082. });
  2083. break;
  2084. case JOINED:
  2085. handler.postDelayed(() -> setCallState(CallStatus.CALLING_TIMEOUT), 45000);
  2086. handler.post(() -> {
  2087. binding.callModeTextView.setText(getDescriptionForCallType());
  2088. if (isIncomingCallFromNotification) {
  2089. binding.callStates.callStateTextView.setText(R.string.nc_call_incoming);
  2090. } else {
  2091. binding.callStates.callStateTextView.setText(R.string.nc_call_ringing);
  2092. }
  2093. if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) {
  2094. binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE);
  2095. }
  2096. if (binding.callStates.callStateProgressBar.getVisibility() != View.VISIBLE) {
  2097. binding.callStates.callStateProgressBar.setVisibility(View.VISIBLE);
  2098. }
  2099. if (binding.gridview.getVisibility() != View.INVISIBLE) {
  2100. binding.gridview.setVisibility(View.INVISIBLE);
  2101. }
  2102. if (binding.callStates.errorImageView.getVisibility() != View.GONE) {
  2103. binding.callStates.errorImageView.setVisibility(View.GONE);
  2104. }
  2105. });
  2106. break;
  2107. case IN_CONVERSATION:
  2108. handler.post(() -> {
  2109. stopCallingSound();
  2110. binding.callModeTextView.setText(getDescriptionForCallType());
  2111. if (!isVoiceOnlyCall) {
  2112. binding.callInfosLinearLayout.setVisibility(View.GONE);
  2113. }
  2114. if (!isPushToTalkActive) {
  2115. animateCallControls(false, 5000);
  2116. }
  2117. if (binding.callStates.callStateRelativeLayout.getVisibility() != View.INVISIBLE) {
  2118. binding.callStates.callStateRelativeLayout.setVisibility(View.INVISIBLE);
  2119. }
  2120. if (binding.callStates.callStateProgressBar.getVisibility() != View.GONE) {
  2121. binding.callStates.callStateProgressBar.setVisibility(View.GONE);
  2122. }
  2123. if (binding.gridview.getVisibility() != View.VISIBLE) {
  2124. binding.gridview.setVisibility(View.VISIBLE);
  2125. }
  2126. if (binding.callStates.errorImageView.getVisibility() != View.GONE) {
  2127. binding.callStates.errorImageView.setVisibility(View.GONE);
  2128. }
  2129. });
  2130. break;
  2131. case OFFLINE:
  2132. handler.post(() -> {
  2133. stopCallingSound();
  2134. binding.callStates.callStateTextView.setText(R.string.nc_offline);
  2135. if (binding.callStates.callStateRelativeLayout.getVisibility() != View.VISIBLE) {
  2136. binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE);
  2137. }
  2138. if (binding.gridview.getVisibility() != View.INVISIBLE) {
  2139. binding.gridview.setVisibility(View.INVISIBLE);
  2140. }
  2141. if (binding.callStates.callStateProgressBar.getVisibility() != View.GONE) {
  2142. binding.callStates.callStateProgressBar.setVisibility(View.GONE);
  2143. }
  2144. binding.callStates.errorImageView.setImageResource(R.drawable.ic_signal_wifi_off_white_24dp);
  2145. if (binding.callStates.errorImageView.getVisibility() != View.VISIBLE) {
  2146. binding.callStates.errorImageView.setVisibility(View.VISIBLE);
  2147. }
  2148. });
  2149. break;
  2150. case LEAVING:
  2151. handler.post(() -> {
  2152. if (!isDestroyed()) {
  2153. stopCallingSound();
  2154. binding.callModeTextView.setText(getDescriptionForCallType());
  2155. binding.callStates.callStateTextView.setText(R.string.nc_leaving_call);
  2156. binding.callStates.callStateRelativeLayout.setVisibility(View.VISIBLE);
  2157. binding.gridview.setVisibility(View.INVISIBLE);
  2158. binding.callStates.callStateProgressBar.setVisibility(View.VISIBLE);
  2159. binding.callStates.errorImageView.setVisibility(View.GONE);
  2160. }
  2161. });
  2162. break;
  2163. default:
  2164. }
  2165. }
  2166. }
  2167. private String getDescriptionForCallType() {
  2168. String appName = getResources().getString(R.string.nc_app_product_name);
  2169. if (isVoiceOnlyCall) {
  2170. return String.format(getResources().getString(R.string.nc_call_voice), appName);
  2171. } else {
  2172. return String.format(getResources().getString(R.string.nc_call_video), appName);
  2173. }
  2174. }
  2175. private void playCallingSound() {
  2176. stopCallingSound();
  2177. Uri ringtoneUri;
  2178. if (isIncomingCallFromNotification) {
  2179. ringtoneUri = NotificationUtils.INSTANCE.getCallRingtoneUri(getApplicationContext(), appPreferences);
  2180. } else {
  2181. ringtoneUri = Uri.parse("android.resource://" + getApplicationContext().getPackageName() + "/raw" +
  2182. "/tr110_1_kap8_3_freiton1");
  2183. }
  2184. if (ringtoneUri != null) {
  2185. mediaPlayer = new MediaPlayer();
  2186. try {
  2187. mediaPlayer.setDataSource(this, ringtoneUri);
  2188. mediaPlayer.setLooping(true);
  2189. AudioAttributes audioAttributes = new AudioAttributes.Builder().setContentType(
  2190. AudioAttributes.CONTENT_TYPE_SONIFICATION)
  2191. .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
  2192. .build();
  2193. mediaPlayer.setAudioAttributes(audioAttributes);
  2194. mediaPlayer.setOnPreparedListener(mp -> mediaPlayer.start());
  2195. mediaPlayer.prepareAsync();
  2196. } catch (IOException e) {
  2197. Log.e(TAG, "Failed to play sound");
  2198. }
  2199. }
  2200. }
  2201. private void stopCallingSound() {
  2202. if (mediaPlayer != null) {
  2203. if (mediaPlayer.isPlaying()) {
  2204. mediaPlayer.stop();
  2205. }
  2206. mediaPlayer.release();
  2207. mediaPlayer = null;
  2208. }
  2209. }
  2210. /**
  2211. * Temporary implementation of SignalingMessageReceiver until signaling related code is extracted from CallActivity.
  2212. *
  2213. * All listeners are called in the main thread.
  2214. */
  2215. private static class InternalSignalingMessageReceiver extends SignalingMessageReceiver {
  2216. public void process(List<Map<String, Object>> users) {
  2217. processUsersInRoom(users);
  2218. }
  2219. public void process(NCSignalingMessage message) {
  2220. processSignalingMessage(message);
  2221. }
  2222. }
  2223. private class CallActivityCallParticipantMessageListener implements SignalingMessageReceiver.CallParticipantMessageListener {
  2224. private final String sessionId;
  2225. public CallActivityCallParticipantMessageListener(String sessionId) {
  2226. this.sessionId = sessionId;
  2227. }
  2228. @Override
  2229. public void onUnshareScreen() {
  2230. endPeerConnection(sessionId, true);
  2231. }
  2232. }
  2233. private class InternalSignalingMessageSender implements SignalingMessageSender {
  2234. @Override
  2235. public void send(NCSignalingMessage ncSignalingMessage) {
  2236. String serializedNcSignalingMessage;
  2237. try {
  2238. serializedNcSignalingMessage = LoganSquare.serialize(ncSignalingMessage);
  2239. } catch (IOException e) {
  2240. Log.e(TAG, "Failed to serialize signaling message", e);
  2241. return;
  2242. }
  2243. // The message wrapper can not be defined in a JSON model to be directly serialized, as sent messages
  2244. // need to be serialized twice; first the signaling message, and then the wrapper as a whole. Received
  2245. // messages, on the other hand, just need to be deserialized once.
  2246. StringBuilder stringBuilder = new StringBuilder();
  2247. stringBuilder.append('{')
  2248. .append("\"fn\":\"")
  2249. .append(StringEscapeUtils.escapeJson(serializedNcSignalingMessage))
  2250. .append('\"')
  2251. .append(',')
  2252. .append("\"sessionId\":")
  2253. .append('\"').append(StringEscapeUtils.escapeJson(callSession)).append('\"')
  2254. .append(',')
  2255. .append("\"ev\":\"message\"")
  2256. .append('}');
  2257. List<String> strings = new ArrayList<>();
  2258. String stringToSend = stringBuilder.toString();
  2259. strings.add(stringToSend);
  2260. int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, new int[]{ApiUtils.APIv3, 2, 1});
  2261. ncApi.sendSignalingMessages(credentials, ApiUtils.getUrlForSignaling(apiVersion, baseUrl, roomToken),
  2262. strings.toString())
  2263. .retry(3)
  2264. .subscribeOn(Schedulers.io())
  2265. .subscribe(new Observer<SignalingOverall>() {
  2266. @Override
  2267. public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
  2268. }
  2269. @Override
  2270. public void onNext(@io.reactivex.annotations.NonNull SignalingOverall signalingOverall) {
  2271. // When sending messages to the internal signaling server the response has been empty since
  2272. // Talk v2.9.0, so it is not really needed to process it, but there is no harm either in
  2273. // doing that, as technically messages could be returned.
  2274. receivedSignalingMessages(signalingOverall.getOcs().getSignalings());
  2275. }
  2276. @Override
  2277. public void onError(@io.reactivex.annotations.NonNull Throwable e) {
  2278. Log.e(TAG, "", e);
  2279. }
  2280. @Override
  2281. public void onComplete() {
  2282. }
  2283. });
  2284. }
  2285. }
  2286. private class MicrophoneButtonTouchListener implements View.OnTouchListener {
  2287. @SuppressLint("ClickableViewAccessibility")
  2288. @Override
  2289. public boolean onTouch(View v, MotionEvent event) {
  2290. v.onTouchEvent(event);
  2291. if (event.getAction() == MotionEvent.ACTION_UP && isPushToTalkActive) {
  2292. isPushToTalkActive = false;
  2293. binding.microphoneButton.setImageResource(R.drawable.ic_mic_off_white_24px);
  2294. pulseAnimation.stop();
  2295. toggleMedia(false, false);
  2296. animateCallControls(false, 5000);
  2297. }
  2298. return true;
  2299. }
  2300. }
  2301. @Subscribe(threadMode = ThreadMode.BACKGROUND)
  2302. public void onMessageEvent(NetworkEvent networkEvent) {
  2303. if (networkEvent.getNetworkConnectionEvent() == NetworkEvent.NetworkConnectionEvent.NETWORK_CONNECTED) {
  2304. if (handler != null) {
  2305. handler.removeCallbacksAndMessages(null);
  2306. }
  2307. } else if (networkEvent.getNetworkConnectionEvent() ==
  2308. NetworkEvent.NetworkConnectionEvent.NETWORK_DISCONNECTED) {
  2309. if (handler != null) {
  2310. handler.removeCallbacksAndMessages(null);
  2311. }
  2312. }
  2313. }
  2314. @RequiresApi(api = Build.VERSION_CODES.O)
  2315. @Override
  2316. public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
  2317. super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
  2318. Log.d(TAG, "onPictureInPictureModeChanged");
  2319. Log.d(TAG, "isInPictureInPictureMode= " + isInPictureInPictureMode);
  2320. isInPipMode = isInPictureInPictureMode;
  2321. if (isInPictureInPictureMode) {
  2322. mReceiver =
  2323. new BroadcastReceiver() {
  2324. @Override
  2325. public void onReceive(Context context, Intent intent) {
  2326. if (intent == null || !MICROPHONE_PIP_INTENT_NAME.equals(intent.getAction())) {
  2327. return;
  2328. }
  2329. final int action = intent.getIntExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, 0);
  2330. switch (action) {
  2331. case MICROPHONE_PIP_REQUEST_MUTE:
  2332. case MICROPHONE_PIP_REQUEST_UNMUTE:
  2333. onMicrophoneClick();
  2334. break;
  2335. }
  2336. }
  2337. };
  2338. registerReceiver(mReceiver,
  2339. new IntentFilter(MICROPHONE_PIP_INTENT_NAME),
  2340. permissionUtil.getPrivateBroadcastPermission(),
  2341. null);
  2342. updateUiForPipMode();
  2343. } else {
  2344. unregisterReceiver(mReceiver);
  2345. mReceiver = null;
  2346. updateUiForNormalMode();
  2347. }
  2348. }
  2349. void updatePictureInPictureActions(
  2350. @DrawableRes int iconId,
  2351. String title,
  2352. int requestCode) {
  2353. if (isGreaterEqualOreo() && isPipModePossible()) {
  2354. final ArrayList<RemoteAction> actions = new ArrayList<>();
  2355. final Icon icon = Icon.createWithResource(this, iconId);
  2356. int intentFlag;
  2357. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
  2358. intentFlag = FLAG_IMMUTABLE;
  2359. } else {
  2360. intentFlag = 0;
  2361. }
  2362. final PendingIntent intent =
  2363. PendingIntent.getBroadcast(
  2364. this,
  2365. requestCode,
  2366. new Intent(MICROPHONE_PIP_INTENT_NAME).putExtra(MICROPHONE_PIP_INTENT_EXTRA_ACTION, requestCode),
  2367. intentFlag);
  2368. actions.add(new RemoteAction(icon, title, title, intent));
  2369. mPictureInPictureParamsBuilder.setActions(actions);
  2370. setPictureInPictureParams(mPictureInPictureParamsBuilder.build());
  2371. }
  2372. }
  2373. public void updateUiForPipMode() {
  2374. Log.d(TAG, "updateUiForPipMode");
  2375. RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
  2376. ViewGroup.LayoutParams.WRAP_CONTENT);
  2377. params.setMargins(0, 0, 0, 0);
  2378. binding.gridview.setLayoutParams(params);
  2379. binding.callControls.setVisibility(View.GONE);
  2380. binding.callInfosLinearLayout.setVisibility(View.GONE);
  2381. binding.selfVideoViewWrapper.setVisibility(View.GONE);
  2382. binding.callStates.callStateRelativeLayout.setVisibility(View.GONE);
  2383. if (participantDisplayItems.size() > 1) {
  2384. binding.pipCallConversationNameTextView.setText(conversationName);
  2385. binding.pipGroupCallOverlay.setVisibility(View.VISIBLE);
  2386. } else {
  2387. binding.pipGroupCallOverlay.setVisibility(View.GONE);
  2388. }
  2389. binding.selfVideoRenderer.release();
  2390. }
  2391. public void updateUiForNormalMode() {
  2392. Log.d(TAG, "updateUiForNormalMode");
  2393. if (isVoiceOnlyCall) {
  2394. binding.callControls.setVisibility(View.VISIBLE);
  2395. } else {
  2396. // animateCallControls needs this to be invisible for a check.
  2397. binding.callControls.setVisibility(View.INVISIBLE);
  2398. }
  2399. initViews();
  2400. binding.callInfosLinearLayout.setVisibility(View.VISIBLE);
  2401. binding.selfVideoViewWrapper.setVisibility(View.VISIBLE);
  2402. binding.pipGroupCallOverlay.setVisibility(View.GONE);
  2403. }
  2404. @Override
  2405. void suppressFitsSystemWindows() {
  2406. binding.controllerCallLayout.setFitsSystemWindows(false);
  2407. }
  2408. public void onConfigurationChanged(Configuration newConfig) {
  2409. super.onConfigurationChanged(newConfig);
  2410. eventBus.post(new ConfigurationChangeEvent());
  2411. }
  2412. private class SelfVideoTouchListener implements View.OnTouchListener {
  2413. @SuppressLint("ClickableViewAccessibility")
  2414. @Override
  2415. public boolean onTouch(View view, MotionEvent event) {
  2416. long duration = event.getEventTime() - event.getDownTime();
  2417. if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
  2418. float newY = event.getRawY() - binding.selfVideoViewWrapper.getHeight() / (float) 2;
  2419. float newX = event.getRawX() - binding.selfVideoViewWrapper.getWidth() / (float) 2;
  2420. binding.selfVideoViewWrapper.setY(newY);
  2421. binding.selfVideoViewWrapper.setX(newX);
  2422. } else if (event.getActionMasked() == MotionEvent.ACTION_UP && duration < 100) {
  2423. switchCamera();
  2424. }
  2425. return true;
  2426. }
  2427. }
  2428. }