CallActivity.java 133 KB

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