1package eu.siacs.conversations.ui;
2
3import android.Manifest;
4import android.annotation.SuppressLint;
5import android.app.PictureInPictureParams;
6import android.content.ActivityNotFoundException;
7import android.content.Context;
8import android.content.Intent;
9import android.content.pm.PackageManager;
10import android.os.Build;
11import android.os.Bundle;
12import android.os.Handler;
13import android.os.PowerManager;
14import android.os.SystemClock;
15import android.util.Log;
16import android.util.Rational;
17import android.view.KeyEvent;
18import android.view.Menu;
19import android.view.MenuItem;
20import android.view.View;
21import android.view.WindowManager;
22import android.widget.Toast;
23
24import androidx.annotation.NonNull;
25import androidx.annotation.RequiresApi;
26import androidx.annotation.StringRes;
27import androidx.databinding.DataBindingUtil;
28
29import com.google.common.base.Optional;
30import com.google.common.base.Preconditions;
31import com.google.common.base.Throwables;
32import com.google.common.collect.ImmutableList;
33import com.google.common.collect.ImmutableSet;
34import com.google.common.util.concurrent.FutureCallback;
35import com.google.common.util.concurrent.Futures;
36
37import org.checkerframework.checker.nullness.compatqual.NullableDecl;
38import org.webrtc.SurfaceViewRenderer;
39import org.webrtc.VideoTrack;
40
41import java.lang.ref.WeakReference;
42import java.util.Arrays;
43import java.util.Collections;
44import java.util.List;
45import java.util.Set;
46
47import eu.siacs.conversations.Config;
48import eu.siacs.conversations.R;
49import eu.siacs.conversations.databinding.ActivityRtpSessionBinding;
50import eu.siacs.conversations.entities.Account;
51import eu.siacs.conversations.entities.Contact;
52import eu.siacs.conversations.entities.Conversation;
53import eu.siacs.conversations.services.AppRTCAudioManager;
54import eu.siacs.conversations.services.XmppConnectionService;
55import eu.siacs.conversations.ui.util.AvatarWorkerTask;
56import eu.siacs.conversations.ui.util.MainThreadExecutor;
57import eu.siacs.conversations.utils.PermissionUtils;
58import eu.siacs.conversations.utils.TimeFrameUtils;
59import eu.siacs.conversations.xml.Namespace;
60import eu.siacs.conversations.xmpp.Jid;
61import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
62import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
63import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
64import eu.siacs.conversations.xmpp.jingle.Media;
65import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
66
67import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied;
68import static java.util.Arrays.asList;
69
70public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate {
71
72 public static final String EXTRA_WITH = "with";
73 public static final String EXTRA_SESSION_ID = "session_id";
74 public static final String EXTRA_LAST_REPORTED_STATE = "last_reported_state";
75 public static final String EXTRA_LAST_ACTION = "last_action";
76 public static final String ACTION_ACCEPT_CALL = "action_accept_call";
77 public static final String ACTION_MAKE_VOICE_CALL = "action_make_voice_call";
78 public static final String ACTION_MAKE_VIDEO_CALL = "action_make_video_call";
79
80 private static final int CALL_DURATION_UPDATE_INTERVAL = 333;
81
82 private static final List<RtpEndUserState> END_CARD = Arrays.asList(
83 RtpEndUserState.APPLICATION_ERROR,
84 RtpEndUserState.SECURITY_ERROR,
85 RtpEndUserState.DECLINED_OR_BUSY,
86 RtpEndUserState.CONNECTIVITY_ERROR,
87 RtpEndUserState.CONNECTIVITY_LOST_ERROR,
88 RtpEndUserState.RETRACTED
89 );
90 private static final List<RtpEndUserState> STATES_SHOWING_HELP_BUTTON = Arrays.asList(
91 RtpEndUserState.APPLICATION_ERROR,
92 RtpEndUserState.CONNECTIVITY_ERROR,
93 RtpEndUserState.SECURITY_ERROR
94 );
95 private static final List<RtpEndUserState> STATES_SHOWING_SWITCH_TO_CHAT = Arrays.asList(
96 RtpEndUserState.CONNECTING,
97 RtpEndUserState.CONNECTED
98 );
99 private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session";
100 private static final int REQUEST_ACCEPT_CALL = 0x1111;
101 private WeakReference<JingleRtpConnection> rtpConnectionReference;
102
103 private ActivityRtpSessionBinding binding;
104 private PowerManager.WakeLock mProximityWakeLock;
105
106 private final Handler mHandler = new Handler();
107 private final Runnable mTickExecutor = new Runnable() {
108 @Override
109 public void run() {
110 updateCallDuration();
111 mHandler.postDelayed(mTickExecutor, CALL_DURATION_UPDATE_INTERVAL);
112 }
113 };
114
115 private static Set<Media> actionToMedia(final String action) {
116 if (ACTION_MAKE_VIDEO_CALL.equals(action)) {
117 return ImmutableSet.of(Media.AUDIO, Media.VIDEO);
118 } else {
119 return ImmutableSet.of(Media.AUDIO);
120 }
121 }
122
123 private static void addSink(final VideoTrack videoTrack, final SurfaceViewRenderer surfaceViewRenderer) {
124 try {
125 videoTrack.addSink(surfaceViewRenderer);
126 } catch (final IllegalStateException e) {
127 Log.e(Config.LOGTAG, "possible race condition on trying to display video track. ignoring", e);
128 }
129 }
130
131 @Override
132 public void onCreate(Bundle savedInstanceState) {
133 super.onCreate(savedInstanceState);
134 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
135 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
136 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
137 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
138 this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session);
139 setSupportActionBar(binding.toolbar);
140 }
141
142 @Override
143 public boolean onCreateOptionsMenu(final Menu menu) {
144 getMenuInflater().inflate(R.menu.activity_rtp_session, menu);
145 final MenuItem help = menu.findItem(R.id.action_help);
146 final MenuItem gotoChat = menu.findItem(R.id.action_goto_chat);
147 help.setVisible(isHelpButtonVisible());
148 gotoChat.setVisible(isSwitchToConversationVisible());
149 return super.onCreateOptionsMenu(menu);
150 }
151
152 @Override
153 public boolean onKeyDown(final int keyCode, final KeyEvent event) {
154 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
155 if (xmppConnectionService != null) {
156 if (xmppConnectionService.getNotificationService().stopSoundAndVibration()) {
157 return true;
158 }
159 }
160 }
161 return super.onKeyDown(keyCode, event);
162 }
163
164 private boolean isHelpButtonVisible() {
165 try {
166 return STATES_SHOWING_HELP_BUTTON.contains(requireRtpConnection().getEndUserState());
167 } catch (IllegalStateException e) {
168 final Intent intent = getIntent();
169 final String state = intent != null ? intent.getStringExtra(EXTRA_LAST_REPORTED_STATE) : null;
170 if (state != null) {
171 return STATES_SHOWING_HELP_BUTTON.contains(RtpEndUserState.valueOf(state));
172 } else {
173 return false;
174 }
175 }
176 }
177
178 private boolean isSwitchToConversationVisible() {
179 final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
180 return connection != null && STATES_SHOWING_SWITCH_TO_CHAT.contains(connection.getEndUserState());
181 }
182
183 private void switchToConversation() {
184 final Contact contact = getWith();
185 final Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
186 switchToConversation(conversation);
187 }
188
189 public boolean onOptionsItemSelected(final MenuItem item) {
190 switch (item.getItemId()) {
191 case R.id.action_help:
192 launchHelpInBrowser();
193 break;
194 case R.id.action_goto_chat:
195 switchToConversation();
196 break;
197 }
198 return super.onOptionsItemSelected(item);
199 }
200
201 private void launchHelpInBrowser() {
202 final Intent intent = new Intent(Intent.ACTION_VIEW, Config.HELP);
203 try {
204 startActivity(intent);
205 } catch (final ActivityNotFoundException e) {
206 Toast.makeText(this, R.string.no_application_found_to_open_link, Toast.LENGTH_LONG).show();
207 }
208 }
209
210 private void endCall(View view) {
211 endCall();
212 }
213
214 private void endCall() {
215 if (this.rtpConnectionReference == null) {
216 retractSessionProposal();
217 finish();
218 } else {
219 requireRtpConnection().endCall();
220 }
221 }
222
223 private void retractSessionProposal() {
224 final Intent intent = getIntent();
225 final String action = intent.getAction();
226 final Account account = extractAccount(intent);
227 final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
228 final String state = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
229 if (!Intent.ACTION_VIEW.equals(action) || state == null || !END_CARD.contains(RtpEndUserState.valueOf(state))) {
230 resetIntent(account, with, RtpEndUserState.RETRACTED, actionToMedia(intent.getAction()));
231 }
232 xmppConnectionService.getJingleConnectionManager().retractSessionProposal(account, with.asBareJid());
233 }
234
235 private void rejectCall(View view) {
236 requireRtpConnection().rejectCall();
237 finish();
238 }
239
240 private void acceptCall(View view) {
241 requestPermissionsAndAcceptCall();
242 }
243
244 private void requestPermissionsAndAcceptCall() {
245 final List<String> permissions;
246 if (getMedia().contains(Media.VIDEO)) {
247 permissions = ImmutableList.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO);
248 } else {
249 permissions = ImmutableList.of(Manifest.permission.RECORD_AUDIO);
250 }
251 if (PermissionUtils.hasPermission(this, permissions, REQUEST_ACCEPT_CALL)) {
252 putScreenInCallMode();
253 checkRecorderAndAcceptCall();
254 }
255 }
256
257 private void checkRecorderAndAcceptCall() {
258 checkMicrophoneAvailability();
259 try {
260 requireRtpConnection().acceptCall();
261 } catch (final IllegalStateException e) {
262 Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
263 }
264 }
265
266 private void checkMicrophoneAvailability() {
267 new Thread(() -> {
268 final long start = SystemClock.elapsedRealtime();
269 final boolean isMicrophoneAvailable = AppRTCAudioManager.isMicrophoneAvailable();
270 final long stop = SystemClock.elapsedRealtime();
271 Log.d(Config.LOGTAG, "checking microphone availability took " + (stop - start) + "ms");
272 if (isMicrophoneAvailable) {
273 return;
274 }
275 runOnUiThread(() -> Toast.makeText(this, R.string.microphone_unavailable, Toast.LENGTH_LONG).show());
276 }
277 ).start();
278 }
279
280 private void putScreenInCallMode() {
281 putScreenInCallMode(requireRtpConnection().getMedia());
282 }
283
284 private void putScreenInCallMode(final Set<Media> media) {
285 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
286 if (!media.contains(Media.VIDEO)) {
287 final JingleRtpConnection rtpConnection = rtpConnectionReference != null ? rtpConnectionReference.get() : null;
288 final AppRTCAudioManager audioManager = rtpConnection == null ? null : rtpConnection.getAudioManager();
289 if (audioManager == null || audioManager.getSelectedAudioDevice() == AppRTCAudioManager.AudioDevice.EARPIECE) {
290 acquireProximityWakeLock();
291 }
292 }
293 }
294
295 @SuppressLint("WakelockTimeout")
296 private void acquireProximityWakeLock() {
297 final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
298 if (powerManager == null) {
299 Log.e(Config.LOGTAG, "power manager not available");
300 return;
301 }
302 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
303 if (this.mProximityWakeLock == null) {
304 this.mProximityWakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, PROXIMITY_WAKE_LOCK_TAG);
305 }
306 if (!this.mProximityWakeLock.isHeld()) {
307 Log.d(Config.LOGTAG, "acquiring proximity wake lock");
308 this.mProximityWakeLock.acquire();
309 }
310 }
311 }
312
313 private void releaseProximityWakeLock() {
314 if (this.mProximityWakeLock != null && mProximityWakeLock.isHeld()) {
315 Log.d(Config.LOGTAG, "releasing proximity wake lock");
316 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
317 this.mProximityWakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
318 } else {
319 this.mProximityWakeLock.release();
320 }
321 this.mProximityWakeLock = null;
322 }
323 }
324
325 private void putProximityWakeLockInProperState(final AppRTCAudioManager.AudioDevice audioDevice) {
326 if (audioDevice == AppRTCAudioManager.AudioDevice.EARPIECE) {
327 acquireProximityWakeLock();
328 } else {
329 releaseProximityWakeLock();
330 }
331 }
332
333 @Override
334 protected void refreshUiReal() {
335
336 }
337
338 @Override
339 public void onNewIntent(final Intent intent) {
340 Log.d(Config.LOGTAG, this.getClass().getName() + ".onNewIntent()");
341 super.onNewIntent(intent);
342 setIntent(intent);
343 if (xmppConnectionService == null) {
344 Log.d(Config.LOGTAG, "RtpSessionActivity: background service wasn't bound in onNewIntent()");
345 return;
346 }
347 final Account account = extractAccount(intent);
348 final String action = intent.getAction();
349 final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
350 final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
351 if (sessionId != null) {
352 Log.d(Config.LOGTAG, "reinitializing from onNewIntent()");
353 if (initializeActivityWithRunningRtpSession(account, with, sessionId)) {
354 return;
355 }
356 if (ACTION_ACCEPT_CALL.equals(intent.getAction())) {
357 Log.d(Config.LOGTAG, "accepting call from onNewIntent()");
358 requestPermissionsAndAcceptCall();
359 resetIntent(intent.getExtras());
360 }
361 } else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(action)) {
362 proposeJingleRtpSession(account, with, actionToMedia(action));
363 binding.with.setText(account.getRoster().getContact(with).getDisplayName());
364 } else {
365 throw new IllegalStateException("received onNewIntent without sessionId");
366 }
367 }
368
369 @Override
370 void onBackendConnected() {
371 final Intent intent = getIntent();
372 final String action = intent.getAction();
373 final Account account = extractAccount(intent);
374 final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
375 final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
376 if (sessionId != null) {
377 if (initializeActivityWithRunningRtpSession(account, with, sessionId)) {
378 return;
379 }
380 if (ACTION_ACCEPT_CALL.equals(intent.getAction())) {
381 Log.d(Config.LOGTAG, "intent action was accept");
382 requestPermissionsAndAcceptCall();
383 resetIntent(intent.getExtras());
384 }
385 } else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(action)) {
386 proposeJingleRtpSession(account, with, actionToMedia(action));
387 binding.with.setText(account.getRoster().getContact(with).getDisplayName());
388 } else if (Intent.ACTION_VIEW.equals(action)) {
389 final String extraLastState = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
390 final RtpEndUserState state = extraLastState == null ? null : RtpEndUserState.valueOf(extraLastState);
391 if (state != null) {
392 Log.d(Config.LOGTAG, "restored last state from intent extra");
393 updateButtonConfiguration(state);
394 updateVerifiedShield(false);
395 updateStateDisplay(state);
396 updateProfilePicture(state);
397 invalidateOptionsMenu();
398 }
399 binding.with.setText(account.getRoster().getContact(with).getDisplayName());
400 if (xmppConnectionService.getJingleConnectionManager().fireJingleRtpConnectionStateUpdates()) {
401 return;
402 }
403 if (END_CARD.contains(state) || xmppConnectionService.getJingleConnectionManager().hasMatchingProposal(account, with)) {
404 return;
405 }
406 Log.d(Config.LOGTAG, "restored state (" + state + ") was not an end card. finishing");
407 finish();
408 }
409 }
410
411 private void proposeJingleRtpSession(final Account account, final Jid with, final Set<Media> media) {
412 checkMicrophoneAvailability();
413 if (with.isBareJid()) {
414 xmppConnectionService.getJingleConnectionManager().proposeJingleRtpSession(account, with, media);
415 } else {
416 final String sessionId = xmppConnectionService.getJingleConnectionManager().initializeRtpSession(account, with, media);
417 initializeActivityWithRunningRtpSession(account, with, sessionId);
418 resetIntent(account, with, sessionId);
419 }
420 putScreenInCallMode(media);
421 }
422
423 @Override
424 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
425 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
426 if (PermissionUtils.allGranted(grantResults)) {
427 if (requestCode == REQUEST_ACCEPT_CALL) {
428 checkRecorderAndAcceptCall();
429 }
430 } else {
431 @StringRes int res;
432 final String firstDenied = getFirstDenied(grantResults, permissions);
433 if (Manifest.permission.RECORD_AUDIO.equals(firstDenied)) {
434 res = R.string.no_microphone_permission;
435 } else if (Manifest.permission.CAMERA.equals(firstDenied)) {
436 res = R.string.no_camera_permission;
437 } else {
438 throw new IllegalStateException("Invalid permission result request");
439 }
440 Toast.makeText(this, getString(res, getString(R.string.app_name)), Toast.LENGTH_SHORT).show();
441 }
442 }
443
444 @Override
445 public void onStart() {
446 super.onStart();
447 mHandler.postDelayed(mTickExecutor, CALL_DURATION_UPDATE_INTERVAL);
448 }
449
450 @Override
451 public void onStop() {
452 mHandler.removeCallbacks(mTickExecutor);
453 binding.remoteVideo.release();
454 binding.localVideo.release();
455 final WeakReference<JingleRtpConnection> weakReference = this.rtpConnectionReference;
456 final JingleRtpConnection jingleRtpConnection = weakReference == null ? null : weakReference.get();
457 if (jingleRtpConnection != null) {
458 releaseVideoTracks(jingleRtpConnection);
459 }
460 releaseProximityWakeLock();
461 super.onStop();
462 }
463
464 private void releaseVideoTracks(final JingleRtpConnection jingleRtpConnection) {
465 final Optional<VideoTrack> remoteVideo = jingleRtpConnection.getRemoteVideoTrack();
466 if (remoteVideo.isPresent()) {
467 remoteVideo.get().removeSink(binding.remoteVideo);
468 }
469 final Optional<VideoTrack> localVideo = jingleRtpConnection.getLocalVideoTrack();
470 if (localVideo.isPresent()) {
471 localVideo.get().removeSink(binding.localVideo);
472 }
473 }
474
475 @Override
476 public void onBackPressed() {
477 if (isConnected()) {
478 if (switchToPictureInPicture()) {
479 return;
480 }
481 } else {
482 endCall();
483 }
484 super.onBackPressed();
485 }
486
487 @Override
488 public void onUserLeaveHint() {
489 super.onUserLeaveHint();
490 if (switchToPictureInPicture()) {
491 return;
492 }
493 //TODO apparently this method is not getting called on Android 10 when using the task switcher
494 if (emptyReference(rtpConnectionReference) && xmppConnectionService != null) {
495 retractSessionProposal();
496 }
497 }
498
499 private boolean isConnected() {
500 final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
501 return connection != null && connection.getEndUserState() == RtpEndUserState.CONNECTED;
502 }
503
504 private boolean switchToPictureInPicture() {
505 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && deviceSupportsPictureInPicture()) {
506 if (shouldBePictureInPicture()) {
507 startPictureInPicture();
508 return true;
509 }
510 }
511 return false;
512 }
513
514 @RequiresApi(api = Build.VERSION_CODES.O)
515 private void startPictureInPicture() {
516 try {
517 enterPictureInPictureMode(
518 new PictureInPictureParams.Builder()
519 .setAspectRatio(new Rational(10, 16))
520 .build()
521 );
522 } catch (final IllegalStateException e) {
523 //this sometimes happens on Samsung phones (possibly when Knox is enabled)
524 Log.w(Config.LOGTAG, "unable to enter picture in picture mode", e);
525 }
526 }
527
528 private boolean deviceSupportsPictureInPicture() {
529 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
530 return getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
531 } else {
532 return false;
533 }
534 }
535
536 private boolean shouldBePictureInPicture() {
537 try {
538 final JingleRtpConnection rtpConnection = requireRtpConnection();
539 return rtpConnection.getMedia().contains(Media.VIDEO) && Arrays.asList(
540 RtpEndUserState.ACCEPTING_CALL,
541 RtpEndUserState.CONNECTING,
542 RtpEndUserState.CONNECTED
543 ).contains(rtpConnection.getEndUserState());
544 } catch (final IllegalStateException e) {
545 return false;
546 }
547 }
548
549 private boolean initializeActivityWithRunningRtpSession(final Account account, Jid with, String sessionId) {
550 final WeakReference<JingleRtpConnection> reference = xmppConnectionService.getJingleConnectionManager()
551 .findJingleRtpConnection(account, with, sessionId);
552 if (reference == null || reference.get() == null) {
553 final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession = xmppConnectionService
554 .getJingleConnectionManager().getTerminalSessionState(with, sessionId);
555 if (terminatedRtpSession == null) {
556 throw new IllegalStateException("failed to initialize activity with running rtp session. session not found");
557 }
558 initializeWithTerminatedSessionState(account, with, terminatedRtpSession);
559 return true;
560 }
561 this.rtpConnectionReference = reference;
562 final RtpEndUserState currentState = requireRtpConnection().getEndUserState();
563 final boolean verified = requireRtpConnection().isVerified();
564 if (currentState == RtpEndUserState.ENDED) {
565 reference.get().throwStateTransitionException();
566 finish();
567 return true;
568 }
569 final Set<Media> media = getMedia();
570 if (currentState == RtpEndUserState.INCOMING_CALL) {
571 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
572 }
573 if (JingleRtpConnection.STATES_SHOWING_ONGOING_CALL.contains(requireRtpConnection().getState())) {
574 putScreenInCallMode();
575 }
576 binding.with.setText(getWith().getDisplayName());
577 updateVideoViews(currentState);
578 updateStateDisplay(currentState, media);
579 updateVerifiedShield(verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(currentState));
580 updateButtonConfiguration(currentState, media);
581 updateProfilePicture(currentState);
582 invalidateOptionsMenu();
583 return false;
584 }
585
586 private void initializeWithTerminatedSessionState(final Account account, final Jid with, final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession) {
587 Log.d(Config.LOGTAG, "initializeWithTerminatedSessionState()");
588 if (terminatedRtpSession.state == RtpEndUserState.ENDED) {
589 finish();
590 return;
591 }
592 RtpEndUserState state = terminatedRtpSession.state;
593 resetIntent(account, with, terminatedRtpSession.state, terminatedRtpSession.media);
594 updateButtonConfiguration(state);
595 updateStateDisplay(state);
596 updateProfilePicture(state);
597 updateCallDuration();
598 updateVerifiedShield(false);
599 invalidateOptionsMenu();
600 binding.with.setText(account.getRoster().getContact(with).getDisplayName());
601 }
602
603 private void reInitializeActivityWithRunningRtpSession(final Account account, Jid with, String sessionId) {
604 runOnUiThread(() -> initializeActivityWithRunningRtpSession(account, with, sessionId));
605 resetIntent(account, with, sessionId);
606 }
607
608 private void resetIntent(final Account account, final Jid with, final String sessionId) {
609 final Intent intent = new Intent(Intent.ACTION_VIEW);
610 intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString());
611 intent.putExtra(EXTRA_WITH, with.toEscapedString());
612 intent.putExtra(EXTRA_SESSION_ID, sessionId);
613 setIntent(intent);
614 }
615
616 private void ensureSurfaceViewRendererIsSetup(final SurfaceViewRenderer surfaceViewRenderer) {
617 surfaceViewRenderer.setVisibility(View.VISIBLE);
618 try {
619 surfaceViewRenderer.init(requireRtpConnection().getEglBaseContext(), null);
620 } catch (IllegalStateException e) {
621 Log.d(Config.LOGTAG, "SurfaceViewRenderer was already initialized");
622 }
623 surfaceViewRenderer.setEnableHardwareScaler(true);
624 }
625
626 private void updateStateDisplay(final RtpEndUserState state) {
627 updateStateDisplay(state, Collections.emptySet());
628 }
629
630 private void updateStateDisplay(final RtpEndUserState state, final Set<Media> media) {
631 switch (state) {
632 case INCOMING_CALL:
633 Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
634 if (media.contains(Media.VIDEO)) {
635 setTitle(R.string.rtp_state_incoming_video_call);
636 } else {
637 setTitle(R.string.rtp_state_incoming_call);
638 }
639 break;
640 case CONNECTING:
641 setTitle(R.string.rtp_state_connecting);
642 break;
643 case CONNECTED:
644 setTitle(R.string.rtp_state_connected);
645 break;
646 case ACCEPTING_CALL:
647 setTitle(R.string.rtp_state_accepting_call);
648 break;
649 case ENDING_CALL:
650 setTitle(R.string.rtp_state_ending_call);
651 break;
652 case FINDING_DEVICE:
653 setTitle(R.string.rtp_state_finding_device);
654 break;
655 case RINGING:
656 setTitle(R.string.rtp_state_ringing);
657 break;
658 case DECLINED_OR_BUSY:
659 setTitle(R.string.rtp_state_declined_or_busy);
660 break;
661 case CONNECTIVITY_ERROR:
662 setTitle(R.string.rtp_state_connectivity_error);
663 break;
664 case CONNECTIVITY_LOST_ERROR:
665 setTitle(R.string.rtp_state_connectivity_lost_error);
666 break;
667 case RETRACTED:
668 setTitle(R.string.rtp_state_retracted);
669 break;
670 case APPLICATION_ERROR:
671 setTitle(R.string.rtp_state_application_failure);
672 break;
673 case SECURITY_ERROR:
674 setTitle(R.string.rtp_state_security_error);
675 break;
676 case ENDED:
677 throw new IllegalStateException("Activity should have called finishAndReleaseWakeLock();");
678 default:
679 throw new IllegalStateException(String.format("State %s has not been handled in UI", state));
680 }
681 }
682
683 private void updateVerifiedShield(final boolean verified) {
684 if (isPictureInPicture()) {
685 this.binding.verified.setVisibility(View.GONE);
686 return;
687 }
688 this.binding.verified.setVisibility(verified ? View.VISIBLE : View.GONE);
689 }
690
691 private void updateProfilePicture(final RtpEndUserState state) {
692 updateProfilePicture(state, null);
693 }
694
695 private void updateProfilePicture(final RtpEndUserState state, final Contact contact) {
696 if (state == RtpEndUserState.INCOMING_CALL || state == RtpEndUserState.ACCEPTING_CALL) {
697 final boolean show = getResources().getBoolean(R.bool.show_avatar_incoming_call);
698 if (show) {
699 binding.contactPhoto.setVisibility(View.VISIBLE);
700 if (contact == null) {
701 AvatarWorkerTask.loadAvatar(getWith(), binding.contactPhoto, R.dimen.publish_avatar_size);
702 } else {
703 AvatarWorkerTask.loadAvatar(contact, binding.contactPhoto, R.dimen.publish_avatar_size);
704 }
705 } else {
706 binding.contactPhoto.setVisibility(View.GONE);
707 }
708 } else {
709 binding.contactPhoto.setVisibility(View.GONE);
710 }
711 }
712
713 private Set<Media> getMedia() {
714 return requireRtpConnection().getMedia();
715 }
716
717 private void updateButtonConfiguration(final RtpEndUserState state) {
718 updateButtonConfiguration(state, Collections.emptySet());
719 }
720
721 @SuppressLint("RestrictedApi")
722 private void updateButtonConfiguration(final RtpEndUserState state, final Set<Media> media) {
723 if (state == RtpEndUserState.ENDING_CALL || isPictureInPicture()) {
724 this.binding.rejectCall.setVisibility(View.INVISIBLE);
725 this.binding.endCall.setVisibility(View.INVISIBLE);
726 this.binding.acceptCall.setVisibility(View.INVISIBLE);
727 } else if (state == RtpEndUserState.INCOMING_CALL) {
728 this.binding.rejectCall.setContentDescription(getString(R.string.dismiss_call));
729 this.binding.rejectCall.setOnClickListener(this::rejectCall);
730 this.binding.rejectCall.setImageResource(R.drawable.ic_call_end_white_48dp);
731 this.binding.rejectCall.setVisibility(View.VISIBLE);
732 this.binding.endCall.setVisibility(View.INVISIBLE);
733 this.binding.acceptCall.setContentDescription(getString(R.string.answer_call));
734 this.binding.acceptCall.setOnClickListener(this::acceptCall);
735 this.binding.acceptCall.setImageResource(R.drawable.ic_call_white_48dp);
736 this.binding.acceptCall.setVisibility(View.VISIBLE);
737 } else if (state == RtpEndUserState.DECLINED_OR_BUSY) {
738 this.binding.rejectCall.setContentDescription(getString(R.string.exit));
739 this.binding.rejectCall.setOnClickListener(this::exit);
740 this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
741 this.binding.rejectCall.setVisibility(View.VISIBLE);
742 this.binding.endCall.setVisibility(View.INVISIBLE);
743 this.binding.acceptCall.setContentDescription(getString(R.string.record_voice_mail));
744 this.binding.acceptCall.setOnClickListener(this::recordVoiceMail);
745 this.binding.acceptCall.setImageResource(R.drawable.ic_voicemail_white_24dp);
746 this.binding.acceptCall.setVisibility(View.VISIBLE);
747 } else if (asList(
748 RtpEndUserState.CONNECTIVITY_ERROR,
749 RtpEndUserState.CONNECTIVITY_LOST_ERROR,
750 RtpEndUserState.APPLICATION_ERROR,
751 RtpEndUserState.RETRACTED,
752 RtpEndUserState.SECURITY_ERROR
753 ).contains(state)) {
754 this.binding.rejectCall.setContentDescription(getString(R.string.exit));
755 this.binding.rejectCall.setOnClickListener(this::exit);
756 this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
757 this.binding.rejectCall.setVisibility(View.VISIBLE);
758 this.binding.endCall.setVisibility(View.INVISIBLE);
759 this.binding.acceptCall.setContentDescription(getString(R.string.try_again));
760 this.binding.acceptCall.setOnClickListener(this::retry);
761 this.binding.acceptCall.setImageResource(R.drawable.ic_replay_white_48dp);
762 this.binding.acceptCall.setVisibility(View.VISIBLE);
763 } else {
764 this.binding.rejectCall.setVisibility(View.INVISIBLE);
765 this.binding.endCall.setContentDescription(getString(R.string.hang_up));
766 this.binding.endCall.setOnClickListener(this::endCall);
767 this.binding.endCall.setImageResource(R.drawable.ic_call_end_white_48dp);
768 this.binding.endCall.setVisibility(View.VISIBLE);
769 this.binding.acceptCall.setVisibility(View.INVISIBLE);
770 }
771 updateInCallButtonConfiguration(state, media);
772 }
773
774 private boolean isPictureInPicture() {
775 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
776 return isInPictureInPictureMode();
777 } else {
778 return false;
779 }
780 }
781
782 private void updateInCallButtonConfiguration() {
783 updateInCallButtonConfiguration(requireRtpConnection().getEndUserState(), requireRtpConnection().getMedia());
784 }
785
786 @SuppressLint("RestrictedApi")
787 private void updateInCallButtonConfiguration(final RtpEndUserState state, final Set<Media> media) {
788 if (state == RtpEndUserState.CONNECTED && !isPictureInPicture()) {
789 Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
790 if (media.contains(Media.VIDEO)) {
791 final JingleRtpConnection rtpConnection = requireRtpConnection();
792 updateInCallButtonConfigurationVideo(rtpConnection.isVideoEnabled(), rtpConnection.isCameraSwitchable());
793 } else {
794 final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
795 updateInCallButtonConfigurationSpeaker(
796 audioManager.getSelectedAudioDevice(),
797 audioManager.getAudioDevices().size()
798 );
799 this.binding.inCallActionFarRight.setVisibility(View.GONE);
800 }
801 if (media.contains(Media.AUDIO)) {
802 updateInCallButtonConfigurationMicrophone(requireRtpConnection().isMicrophoneEnabled());
803 } else {
804 this.binding.inCallActionLeft.setVisibility(View.GONE);
805 }
806 } else {
807 this.binding.inCallActionLeft.setVisibility(View.GONE);
808 this.binding.inCallActionRight.setVisibility(View.GONE);
809 this.binding.inCallActionFarRight.setVisibility(View.GONE);
810 }
811 }
812
813 @SuppressLint("RestrictedApi")
814 private void updateInCallButtonConfigurationSpeaker(final AppRTCAudioManager.AudioDevice selectedAudioDevice, final int numberOfChoices) {
815 switch (selectedAudioDevice) {
816 case EARPIECE:
817 this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_off_black_24dp);
818 if (numberOfChoices >= 2) {
819 this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker);
820 } else {
821 this.binding.inCallActionRight.setOnClickListener(null);
822 this.binding.inCallActionRight.setClickable(false);
823 }
824 break;
825 case WIRED_HEADSET:
826 this.binding.inCallActionRight.setImageResource(R.drawable.ic_headset_black_24dp);
827 this.binding.inCallActionRight.setOnClickListener(null);
828 this.binding.inCallActionRight.setClickable(false);
829 break;
830 case SPEAKER_PHONE:
831 this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_up_black_24dp);
832 if (numberOfChoices >= 2) {
833 this.binding.inCallActionRight.setOnClickListener(this::switchToEarpiece);
834 } else {
835 this.binding.inCallActionRight.setOnClickListener(null);
836 this.binding.inCallActionRight.setClickable(false);
837 }
838 break;
839 case BLUETOOTH:
840 this.binding.inCallActionRight.setImageResource(R.drawable.ic_bluetooth_audio_black_24dp);
841 this.binding.inCallActionRight.setOnClickListener(null);
842 this.binding.inCallActionRight.setClickable(false);
843 break;
844 }
845 this.binding.inCallActionRight.setVisibility(View.VISIBLE);
846 }
847
848 @SuppressLint("RestrictedApi")
849 private void updateInCallButtonConfigurationVideo(final boolean videoEnabled, final boolean isCameraSwitchable) {
850 this.binding.inCallActionRight.setVisibility(View.VISIBLE);
851 if (isCameraSwitchable) {
852 this.binding.inCallActionFarRight.setImageResource(R.drawable.ic_flip_camera_android_black_24dp);
853 this.binding.inCallActionFarRight.setVisibility(View.VISIBLE);
854 this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera);
855 } else {
856 this.binding.inCallActionFarRight.setVisibility(View.GONE);
857 }
858 if (videoEnabled) {
859 this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_black_24dp);
860 this.binding.inCallActionRight.setOnClickListener(this::disableVideo);
861 } else {
862 this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_off_black_24dp);
863 this.binding.inCallActionRight.setOnClickListener(this::enableVideo);
864 }
865 }
866
867 private void switchCamera(final View view) {
868 Futures.addCallback(requireRtpConnection().switchCamera(), new FutureCallback<Boolean>() {
869 @Override
870 public void onSuccess(@NullableDecl Boolean isFrontCamera) {
871 binding.localVideo.setMirror(isFrontCamera);
872 }
873
874 @Override
875 public void onFailure(@NonNull final Throwable throwable) {
876 Log.d(Config.LOGTAG, "could not switch camera", Throwables.getRootCause(throwable));
877 Toast.makeText(RtpSessionActivity.this, R.string.could_not_switch_camera, Toast.LENGTH_LONG).show();
878 }
879 }, MainThreadExecutor.getInstance());
880 }
881
882 private void enableVideo(View view) {
883 try {
884 requireRtpConnection().setVideoEnabled(true);
885 } catch (final IllegalStateException e) {
886 Toast.makeText(this, R.string.unable_to_enable_video, Toast.LENGTH_SHORT).show();
887 return;
888 }
889 updateInCallButtonConfigurationVideo(true, requireRtpConnection().isCameraSwitchable());
890 }
891
892 private void disableVideo(View view) {
893 requireRtpConnection().setVideoEnabled(false);
894 updateInCallButtonConfigurationVideo(false, requireRtpConnection().isCameraSwitchable());
895
896 }
897
898 @SuppressLint("RestrictedApi")
899 private void updateInCallButtonConfigurationMicrophone(final boolean microphoneEnabled) {
900 if (microphoneEnabled) {
901 this.binding.inCallActionLeft.setImageResource(R.drawable.ic_mic_black_24dp);
902 this.binding.inCallActionLeft.setOnClickListener(this::disableMicrophone);
903 } else {
904 this.binding.inCallActionLeft.setImageResource(R.drawable.ic_mic_off_black_24dp);
905 this.binding.inCallActionLeft.setOnClickListener(this::enableMicrophone);
906 }
907 this.binding.inCallActionLeft.setVisibility(View.VISIBLE);
908 }
909
910 private void updateCallDuration() {
911 final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
912 if (connection == null || connection.getMedia().contains(Media.VIDEO)) {
913 this.binding.duration.setVisibility(View.GONE);
914 return;
915 }
916 final long rtpConnectionStarted = connection.getRtpConnectionStarted();
917 final long rtpConnectionEnded = connection.getRtpConnectionEnded();
918 if (rtpConnectionStarted != 0) {
919 final long ended = rtpConnectionEnded == 0 ? SystemClock.elapsedRealtime() : rtpConnectionEnded;
920 this.binding.duration.setText(TimeFrameUtils.formatTimePassed(rtpConnectionStarted, ended, false));
921 this.binding.duration.setVisibility(View.VISIBLE);
922 } else {
923 this.binding.duration.setVisibility(View.GONE);
924 }
925 }
926
927 private void updateVideoViews(final RtpEndUserState state) {
928 if (END_CARD.contains(state) || state == RtpEndUserState.ENDING_CALL) {
929 binding.localVideo.setVisibility(View.GONE);
930 binding.localVideo.release();
931 binding.remoteVideo.setVisibility(View.GONE);
932 binding.remoteVideo.release();
933 binding.pipLocalMicOffIndicator.setVisibility(View.GONE);
934 if (isPictureInPicture()) {
935 binding.appBarLayout.setVisibility(View.GONE);
936 binding.pipPlaceholder.setVisibility(View.VISIBLE);
937 if (Arrays.asList(
938 RtpEndUserState.APPLICATION_ERROR,
939 RtpEndUserState.CONNECTIVITY_ERROR,
940 RtpEndUserState.SECURITY_ERROR)
941 .contains(state)) {
942 binding.pipWarning.setVisibility(View.VISIBLE);
943 binding.pipWaiting.setVisibility(View.GONE);
944 } else {
945 binding.pipWarning.setVisibility(View.GONE);
946 binding.pipWaiting.setVisibility(View.GONE);
947 }
948 } else {
949 binding.appBarLayout.setVisibility(View.VISIBLE);
950 binding.pipPlaceholder.setVisibility(View.GONE);
951 }
952 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
953 return;
954 }
955 if (isPictureInPicture() && (state == RtpEndUserState.CONNECTING || state == RtpEndUserState.ACCEPTING_CALL)) {
956 binding.localVideo.setVisibility(View.GONE);
957 binding.remoteVideo.setVisibility(View.GONE);
958 binding.appBarLayout.setVisibility(View.GONE);
959 binding.pipPlaceholder.setVisibility(View.VISIBLE);
960 binding.pipWarning.setVisibility(View.GONE);
961 binding.pipWaiting.setVisibility(View.VISIBLE);
962 binding.pipLocalMicOffIndicator.setVisibility(View.GONE);
963 return;
964 }
965 final Optional<VideoTrack> localVideoTrack = getLocalVideoTrack();
966 if (localVideoTrack.isPresent() && !isPictureInPicture()) {
967 ensureSurfaceViewRendererIsSetup(binding.localVideo);
968 //paint local view over remote view
969 binding.localVideo.setZOrderMediaOverlay(true);
970 binding.localVideo.setMirror(requireRtpConnection().isFrontCamera());
971 addSink(localVideoTrack.get(), binding.localVideo);
972 } else {
973 binding.localVideo.setVisibility(View.GONE);
974 }
975 final Optional<VideoTrack> remoteVideoTrack = getRemoteVideoTrack();
976 if (remoteVideoTrack.isPresent()) {
977 ensureSurfaceViewRendererIsSetup(binding.remoteVideo);
978 addSink(remoteVideoTrack.get(), binding.remoteVideo);
979 if (state == RtpEndUserState.CONNECTED) {
980 binding.appBarLayout.setVisibility(View.GONE);
981 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
982 } else {
983 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
984 binding.remoteVideo.setVisibility(View.GONE);
985 }
986 if (isPictureInPicture() && !requireRtpConnection().isMicrophoneEnabled()) {
987 binding.pipLocalMicOffIndicator.setVisibility(View.VISIBLE);
988 } else {
989 binding.pipLocalMicOffIndicator.setVisibility(View.GONE);
990 }
991 } else {
992 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
993 binding.remoteVideo.setVisibility(View.GONE);
994 binding.pipLocalMicOffIndicator.setVisibility(View.GONE);
995 }
996 }
997
998 private Optional<VideoTrack> getLocalVideoTrack() {
999 final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
1000 if (connection == null) {
1001 return Optional.absent();
1002 }
1003 return connection.getLocalVideoTrack();
1004 }
1005
1006 private Optional<VideoTrack> getRemoteVideoTrack() {
1007 final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
1008 if (connection == null) {
1009 return Optional.absent();
1010 }
1011 return connection.getRemoteVideoTrack();
1012 }
1013
1014 private void disableMicrophone(View view) {
1015 final JingleRtpConnection rtpConnection = requireRtpConnection();
1016 if (rtpConnection.setMicrophoneEnabled(false)) {
1017 updateInCallButtonConfiguration();
1018 }
1019 }
1020
1021 private void enableMicrophone(View view) {
1022 final JingleRtpConnection rtpConnection = requireRtpConnection();
1023 if (rtpConnection.setMicrophoneEnabled(true)) {
1024 updateInCallButtonConfiguration();
1025 }
1026 }
1027
1028 private void switchToEarpiece(View view) {
1029 requireRtpConnection().getAudioManager().setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE);
1030 acquireProximityWakeLock();
1031 }
1032
1033 private void switchToSpeaker(View view) {
1034 requireRtpConnection().getAudioManager().setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
1035 releaseProximityWakeLock();
1036 }
1037
1038 private void retry(View view) {
1039 final Intent intent = getIntent();
1040 final Account account = extractAccount(intent);
1041 final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
1042 final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION);
1043 final String action = intent.getAction();
1044 final Set<Media> media = actionToMedia(lastAction == null ? action : lastAction);
1045 this.rtpConnectionReference = null;
1046 Log.d(Config.LOGTAG, "attempting retry with " + with.toEscapedString());
1047 proposeJingleRtpSession(account, with, media);
1048 }
1049
1050 private void exit(final View view) {
1051 finish();
1052 }
1053
1054 private void recordVoiceMail(final View view) {
1055 final Intent intent = getIntent();
1056 final Account account = extractAccount(intent);
1057 final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
1058 final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, with, false, true);
1059 final Intent launchIntent = new Intent(this, ConversationsActivity.class);
1060 launchIntent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
1061 launchIntent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
1062 launchIntent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1063 launchIntent.putExtra(ConversationsActivity.EXTRA_POST_INIT_ACTION, ConversationsActivity.POST_ACTION_RECORD_VOICE);
1064 startActivity(launchIntent);
1065 finish();
1066 }
1067
1068 private Contact getWith() {
1069 final AbstractJingleConnection.Id id = requireRtpConnection().getId();
1070 final Account account = id.account;
1071 return account.getRoster().getContact(id.with);
1072 }
1073
1074 private JingleRtpConnection requireRtpConnection() {
1075 final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
1076 if (connection == null) {
1077 throw new IllegalStateException("No RTP connection found");
1078 }
1079 return connection;
1080 }
1081
1082 @Override
1083 public void onJingleRtpConnectionUpdate(Account account, Jid with, final String sessionId, RtpEndUserState state) {
1084 Log.d(Config.LOGTAG, "onJingleRtpConnectionUpdate(" + state + ")");
1085 if (END_CARD.contains(state)) {
1086 Log.d(Config.LOGTAG, "end card reached");
1087 releaseProximityWakeLock();
1088 runOnUiThread(() -> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
1089 }
1090 if (with.isBareJid()) {
1091 updateRtpSessionProposalState(account, with, state);
1092 return;
1093 }
1094 if (emptyReference(this.rtpConnectionReference)) {
1095 if (END_CARD.contains(state)) {
1096 Log.d(Config.LOGTAG, "not reinitializing session");
1097 return;
1098 }
1099 //this happens when going from proposed session to actual session
1100 reInitializeActivityWithRunningRtpSession(account, with, sessionId);
1101 return;
1102 }
1103 final AbstractJingleConnection.Id id = requireRtpConnection().getId();
1104 final boolean verified = requireRtpConnection().isVerified();
1105 final Set<Media> media = getMedia();
1106 final Contact contact = getWith();
1107 if (account == id.account && id.with.equals(with) && id.sessionId.equals(sessionId)) {
1108 if (state == RtpEndUserState.ENDED) {
1109 finish();
1110 return;
1111 }
1112 runOnUiThread(() -> {
1113 updateStateDisplay(state, media);
1114 updateVerifiedShield(verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(state));
1115 updateButtonConfiguration(state, media);
1116 updateVideoViews(state);
1117 updateProfilePicture(state, contact);
1118 invalidateOptionsMenu();
1119 });
1120 if (END_CARD.contains(state)) {
1121 final JingleRtpConnection rtpConnection = requireRtpConnection();
1122 resetIntent(account, with, state, rtpConnection.getMedia());
1123 releaseVideoTracks(rtpConnection);
1124 this.rtpConnectionReference = null;
1125 }
1126 } else {
1127 Log.d(Config.LOGTAG, "received update for other rtp session");
1128 }
1129 }
1130
1131 @Override
1132 public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
1133 Log.d(Config.LOGTAG, "onAudioDeviceChanged in activity: selected:" + selectedAudioDevice + ", available:" + availableAudioDevices);
1134 try {
1135 if (getMedia().contains(Media.VIDEO)) {
1136 Log.d(Config.LOGTAG, "nothing to do; in video mode");
1137 return;
1138 }
1139 final RtpEndUserState endUserState = requireRtpConnection().getEndUserState();
1140 if (endUserState == RtpEndUserState.CONNECTED) {
1141 final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
1142 updateInCallButtonConfigurationSpeaker(
1143 audioManager.getSelectedAudioDevice(),
1144 audioManager.getAudioDevices().size()
1145 );
1146 } else if (END_CARD.contains(endUserState)) {
1147 Log.d(Config.LOGTAG, "onAudioDeviceChanged() nothing to do because end card has been reached");
1148 } else {
1149 putProximityWakeLockInProperState(selectedAudioDevice);
1150 }
1151 } catch (IllegalStateException e) {
1152 Log.d(Config.LOGTAG, "RTP connection was not available when audio device changed");
1153 }
1154 }
1155
1156 private void updateRtpSessionProposalState(final Account account, final Jid with, final RtpEndUserState state) {
1157 final Intent currentIntent = getIntent();
1158 final String withExtra = currentIntent == null ? null : currentIntent.getStringExtra(EXTRA_WITH);
1159 if (withExtra == null) {
1160 return;
1161 }
1162 if (Jid.ofEscaped(withExtra).asBareJid().equals(with)) {
1163 runOnUiThread(() -> {
1164 updateVerifiedShield(false);
1165 updateStateDisplay(state);
1166 updateButtonConfiguration(state);
1167 updateProfilePicture(state);
1168 invalidateOptionsMenu();
1169 });
1170 resetIntent(account, with, state, actionToMedia(currentIntent.getAction()));
1171 }
1172 }
1173
1174 private void resetIntent(final Bundle extras) {
1175 final Intent intent = new Intent(Intent.ACTION_VIEW);
1176 intent.putExtras(extras);
1177 setIntent(intent);
1178 }
1179
1180 private void resetIntent(final Account account, Jid with, final RtpEndUserState state, final Set<Media> media) {
1181 final Intent intent = new Intent(Intent.ACTION_VIEW);
1182 intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString());
1183 if (account.getRoster().getContact(with).getPresences().anySupport(Namespace.JINGLE_MESSAGE)) {
1184 intent.putExtra(EXTRA_WITH, with.asBareJid().toEscapedString());
1185 } else {
1186 intent.putExtra(EXTRA_WITH, with.toEscapedString());
1187 }
1188 intent.putExtra(EXTRA_LAST_REPORTED_STATE, state.toString());
1189 intent.putExtra(EXTRA_LAST_ACTION, media.contains(Media.VIDEO) ? ACTION_MAKE_VIDEO_CALL : ACTION_MAKE_VOICE_CALL);
1190 setIntent(intent);
1191 }
1192
1193 private static boolean emptyReference(final WeakReference<?> weakReference) {
1194 return weakReference == null || weakReference.get() == null;
1195 }
1196}