1package eu.siacs.conversations.ui;
2
3import android.Manifest;
4import android.annotation.SuppressLint;
5import android.content.Context;
6import android.content.Intent;
7import android.databinding.DataBindingUtil;
8import android.os.Build;
9import android.os.Bundle;
10import android.os.PowerManager;
11import android.support.annotation.NonNull;
12import android.support.annotation.StringRes;
13import android.util.Log;
14import android.view.View;
15import android.view.WindowManager;
16import android.widget.Toast;
17
18import com.google.common.base.Optional;
19import com.google.common.collect.ImmutableList;
20import com.google.common.collect.ImmutableSet;
21
22import org.webrtc.RendererCommon;
23import org.webrtc.SurfaceViewRenderer;
24import org.webrtc.VideoTrack;
25
26import java.lang.ref.WeakReference;
27import java.util.Arrays;
28import java.util.List;
29import java.util.Set;
30
31import eu.siacs.conversations.Config;
32import eu.siacs.conversations.R;
33import eu.siacs.conversations.databinding.ActivityRtpSessionBinding;
34import eu.siacs.conversations.entities.Account;
35import eu.siacs.conversations.entities.Contact;
36import eu.siacs.conversations.services.AppRTCAudioManager;
37import eu.siacs.conversations.services.XmppConnectionService;
38import eu.siacs.conversations.utils.PermissionUtils;
39import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
40import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
41import eu.siacs.conversations.xmpp.jingle.Media;
42import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
43import rocks.xmpp.addr.Jid;
44
45import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied;
46import static java.util.Arrays.asList;
47
48public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate {
49
50 private static final List<RtpEndUserState> END_CARD = Arrays.asList(
51 RtpEndUserState.APPLICATION_ERROR,
52 RtpEndUserState.DECLINED_OR_BUSY,
53 RtpEndUserState.CONNECTIVITY_ERROR
54 );
55
56 private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session";
57
58 private static final int REQUEST_ACCEPT_CALL = 0x1111;
59
60 public static final String EXTRA_WITH = "with";
61 public static final String EXTRA_SESSION_ID = "session_id";
62 public static final String EXTRA_LAST_REPORTED_STATE = "last_reported_state";
63 public static final String EXTRA_LAST_ACTION = "last_action";
64
65 public static final String ACTION_ACCEPT_CALL = "action_accept_call";
66 public static final String ACTION_MAKE_VOICE_CALL = "action_make_voice_call";
67 public static final String ACTION_MAKE_VIDEO_CALL = "action_make_video_call";
68
69
70 private WeakReference<JingleRtpConnection> rtpConnectionReference;
71
72 private ActivityRtpSessionBinding binding;
73 private PowerManager.WakeLock mProximityWakeLock;
74
75 @Override
76 public void onCreate(Bundle savedInstanceState) {
77 super.onCreate(savedInstanceState);
78 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
79 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
80 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
81 | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
82 Log.d(Config.LOGTAG, "RtpSessionActivity.onCreate()");
83 this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session);
84 setSupportActionBar(binding.toolbar);
85 }
86
87 @Override
88 public void onStart() {
89 super.onStart();
90 Log.d(Config.LOGTAG, "RtpSessionActivity.onStart()");
91 }
92
93 private void endCall(View view) {
94 endCall();
95 }
96
97 private void endCall() {
98 if (this.rtpConnectionReference == null) {
99 final Intent intent = getIntent();
100 final Account account = extractAccount(intent);
101 final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH));
102 xmppConnectionService.getJingleConnectionManager().retractSessionProposal(account, with.asBareJid());
103 finish();
104 } else {
105 requireRtpConnection().endCall();
106 }
107 }
108
109 private void rejectCall(View view) {
110 requireRtpConnection().rejectCall();
111 finish();
112 }
113
114 private void acceptCall(View view) {
115 requestPermissionsAndAcceptCall();
116 }
117
118 private void requestPermissionsAndAcceptCall() {
119 final List<String> permissions;
120 if (getMedia().contains(Media.VIDEO)) {
121 permissions = ImmutableList.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO);
122 } else {
123 permissions = ImmutableList.of(Manifest.permission.RECORD_AUDIO);
124 }
125 if (PermissionUtils.hasPermission(this, permissions, REQUEST_ACCEPT_CALL)) {
126 putScreenInCallMode();
127 requireRtpConnection().acceptCall();
128 }
129 }
130
131 private void putScreenInCallMode() {
132 putScreenInCallMode(requireRtpConnection().getMedia());
133 }
134
135 private void putScreenInCallMode(final Set<Media> media) {
136 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
137 if (!media.contains(Media.VIDEO)) {
138 final JingleRtpConnection rtpConnection = rtpConnectionReference != null ? rtpConnectionReference.get() : null;
139 final AppRTCAudioManager audioManager = rtpConnection == null ? null : rtpConnection.getAudioManager();
140 if (audioManager == null || audioManager.getSelectedAudioDevice() == AppRTCAudioManager.AudioDevice.EARPIECE) {
141 acquireProximityWakeLock();
142 }
143 }
144 }
145
146 @SuppressLint("WakelockTimeout")
147 private void acquireProximityWakeLock() {
148 final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
149 if (powerManager == null) {
150 Log.e(Config.LOGTAG, "power manager not available");
151 return;
152 }
153 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
154 if (this.mProximityWakeLock == null) {
155 this.mProximityWakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, PROXIMITY_WAKE_LOCK_TAG);
156 }
157 if (!this.mProximityWakeLock.isHeld()) {
158 Log.d(Config.LOGTAG, "acquiring proximity wake lock");
159 this.mProximityWakeLock.acquire();
160 }
161 }
162 }
163
164 private void releaseProximityWakeLock() {
165 if (this.mProximityWakeLock != null && mProximityWakeLock.isHeld()) {
166 Log.d(Config.LOGTAG, "releasing proximity wake lock");
167 this.mProximityWakeLock.release();
168 this.mProximityWakeLock = null;
169 }
170 }
171
172 private void putProximityWakeLockInProperState() {
173 if (requireRtpConnection().getAudioManager().getSelectedAudioDevice() == AppRTCAudioManager.AudioDevice.EARPIECE) {
174 acquireProximityWakeLock();
175 } else {
176 releaseProximityWakeLock();
177 }
178 }
179
180 @Override
181 protected void refreshUiReal() {
182
183 }
184
185 @Override
186 public void onNewIntent(final Intent intent) {
187 super.onNewIntent(intent);
188 setIntent(intent);
189 if (xmppConnectionService == null) {
190 Log.d(Config.LOGTAG, "RtpSessionActivity: background service wasn't bound in onNewIntent()");
191 return;
192 }
193 final Account account = extractAccount(intent);
194 final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH));
195 final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
196 if (sessionId != null) {
197 Log.d(Config.LOGTAG, "reinitializing from onNewIntent()");
198 initializeActivityWithRunningRtpSession(account, with, sessionId);
199 if (ACTION_ACCEPT_CALL.equals(intent.getAction())) {
200 Log.d(Config.LOGTAG, "accepting call from onNewIntent()");
201 requestPermissionsAndAcceptCall();
202 resetIntent(intent.getExtras());
203 }
204 } else {
205 throw new IllegalStateException("received onNewIntent without sessionId");
206 }
207 }
208
209 @Override
210 void onBackendConnected() {
211 final Intent intent = getIntent();
212 final String action = intent.getAction();
213 final Account account = extractAccount(intent);
214 final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH));
215 final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
216 if (sessionId != null) {
217 initializeActivityWithRunningRtpSession(account, with, sessionId);
218 if (ACTION_ACCEPT_CALL.equals(intent.getAction())) {
219 Log.d(Config.LOGTAG, "intent action was accept");
220 requestPermissionsAndAcceptCall();
221 resetIntent(intent.getExtras());
222 }
223 } else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(action)) {
224 proposeJingleRtpSession(account, with, actionToMedia(action));
225 binding.with.setText(account.getRoster().getContact(with).getDisplayName());
226 } else if (Intent.ACTION_VIEW.equals(action)) {
227 final String extraLastState = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
228 if (extraLastState != null) {
229 Log.d(Config.LOGTAG, "restored last state from intent extra");
230 RtpEndUserState state = RtpEndUserState.valueOf(extraLastState);
231 updateButtonConfiguration(state);
232 updateStateDisplay(state);
233 }
234 binding.with.setText(account.getRoster().getContact(with).getDisplayName());
235 }
236 }
237
238 private static Set<Media> actionToMedia(final String action) {
239 if (ACTION_MAKE_VIDEO_CALL.equals(action)) {
240 return ImmutableSet.of(Media.AUDIO, Media.VIDEO);
241 } else {
242 return ImmutableSet.of(Media.AUDIO);
243 }
244 }
245
246 private void proposeJingleRtpSession(final Account account, final Jid with, final Set<Media> media) {
247 xmppConnectionService.getJingleConnectionManager().proposeJingleRtpSession(account, with, media);
248 putScreenInCallMode(media);
249 }
250
251 @Override
252 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
253 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
254 if (PermissionUtils.allGranted(grantResults)) {
255 if (requestCode == REQUEST_ACCEPT_CALL) {
256 requireRtpConnection().acceptCall();
257 }
258 } else {
259 @StringRes int res;
260 final String firstDenied = getFirstDenied(grantResults, permissions);
261 if (Manifest.permission.RECORD_AUDIO.equals(firstDenied)) {
262 res = R.string.no_microphone_permission;
263 } else if (Manifest.permission.CAMERA.equals(firstDenied)) {
264 res = R.string.no_camera_permission;
265 } else {
266 throw new IllegalStateException("Invalid permission result request");
267 }
268 Toast.makeText(this, res, Toast.LENGTH_SHORT).show();
269 }
270 }
271
272 @Override
273 public void onStop() {
274 binding.remoteVideo.release();
275 binding.localVideo.release();
276 releaseProximityWakeLock();
277 //TODO maybe we want to finish if call had ended
278 super.onStop();
279 }
280
281 @Override
282 public void onBackPressed() {
283 endCall();
284 super.onBackPressed();
285 }
286
287
288 private void initializeActivityWithRunningRtpSession(final Account account, Jid with, String sessionId) {
289 final WeakReference<JingleRtpConnection> reference = xmppConnectionService.getJingleConnectionManager()
290 .findJingleRtpConnection(account, with, sessionId);
291 if (reference == null || reference.get() == null) {
292 finish();
293 return;
294 }
295 this.rtpConnectionReference = reference;
296 final RtpEndUserState currentState = requireRtpConnection().getEndUserState();
297 if (currentState == RtpEndUserState.ENDED) {
298 finish();
299 return;
300 }
301 if (currentState == RtpEndUserState.INCOMING_CALL) {
302 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
303 }
304 if (JingleRtpConnection.STATES_SHOWING_ONGOING_CALL.contains(requireRtpConnection().getState())) {
305 putScreenInCallMode();
306 }
307 binding.with.setText(getWith().getDisplayName());
308 updateVideoViews(currentState);
309 updateStateDisplay(currentState);
310 updateButtonConfiguration(currentState);
311 }
312
313 private void reInitializeActivityWithRunningRapSession(final Account account, Jid with, String sessionId) {
314 runOnUiThread(() -> {
315 initializeActivityWithRunningRtpSession(account, with, sessionId);
316 });
317 final Intent intent = new Intent(Intent.ACTION_VIEW);
318 intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString());
319 intent.putExtra(EXTRA_WITH, with.toEscapedString());
320 intent.putExtra(EXTRA_SESSION_ID, sessionId);
321 setIntent(intent);
322 }
323
324 private void ensureSurfaceViewRendererIsSetup(final SurfaceViewRenderer surfaceViewRenderer) {
325 surfaceViewRenderer.setVisibility(View.VISIBLE);
326 try {
327 surfaceViewRenderer.init(requireRtpConnection().getEglBaseContext(), null);
328 } catch (IllegalStateException e) {
329 Log.d(Config.LOGTAG, "SurfaceViewRenderer was already initialized");
330 }
331 surfaceViewRenderer.setEnableHardwareScaler(true);
332 }
333
334 private void updateStateDisplay(final RtpEndUserState state) {
335 switch (state) {
336 case INCOMING_CALL:
337 if (getMedia().contains(Media.VIDEO)) {
338 setTitle(R.string.rtp_state_incoming_video_call);
339 } else {
340 setTitle(R.string.rtp_state_incoming_call);
341 }
342 break;
343 case CONNECTING:
344 setTitle(R.string.rtp_state_connecting);
345 break;
346 case CONNECTED:
347 setTitle(R.string.rtp_state_connected);
348 break;
349 case ACCEPTING_CALL:
350 setTitle(R.string.rtp_state_accepting_call);
351 break;
352 case ENDING_CALL:
353 setTitle(R.string.rtp_state_ending_call);
354 break;
355 case FINDING_DEVICE:
356 setTitle(R.string.rtp_state_finding_device);
357 break;
358 case RINGING:
359 setTitle(R.string.rtp_state_ringing);
360 break;
361 case DECLINED_OR_BUSY:
362 setTitle(R.string.rtp_state_declined_or_busy);
363 break;
364 case CONNECTIVITY_ERROR:
365 setTitle(R.string.rtp_state_connectivity_error);
366 break;
367 case APPLICATION_ERROR:
368 setTitle(R.string.rtp_state_application_failure);
369 break;
370 case ENDED:
371 throw new IllegalStateException("Activity should have called finishAndReleaseWakeLock();");
372 default:
373 throw new IllegalStateException(String.format("State %s has not been handled in UI", state));
374 }
375 }
376
377 private Set<Media> getMedia() {
378 return requireRtpConnection().getMedia();
379 }
380
381 @SuppressLint("RestrictedApi")
382 private void updateButtonConfiguration(final RtpEndUserState state) {
383 if (state == RtpEndUserState.INCOMING_CALL) {
384 this.binding.rejectCall.setOnClickListener(this::rejectCall);
385 this.binding.rejectCall.setImageResource(R.drawable.ic_call_end_white_48dp);
386 this.binding.rejectCall.setVisibility(View.VISIBLE);
387 this.binding.endCall.setVisibility(View.INVISIBLE);
388 this.binding.acceptCall.setOnClickListener(this::acceptCall);
389 this.binding.acceptCall.setImageResource(R.drawable.ic_call_white_48dp);
390 this.binding.acceptCall.setVisibility(View.VISIBLE);
391 } else if (state == RtpEndUserState.ENDING_CALL) {
392 this.binding.rejectCall.setVisibility(View.INVISIBLE);
393 this.binding.endCall.setVisibility(View.INVISIBLE);
394 this.binding.acceptCall.setVisibility(View.INVISIBLE);
395 } else if (state == RtpEndUserState.DECLINED_OR_BUSY) {
396 this.binding.rejectCall.setVisibility(View.INVISIBLE);
397 this.binding.endCall.setOnClickListener(this::exit);
398 this.binding.endCall.setImageResource(R.drawable.ic_clear_white_48dp);
399 this.binding.endCall.setVisibility(View.VISIBLE);
400 this.binding.acceptCall.setVisibility(View.INVISIBLE);
401 } else if (state == RtpEndUserState.CONNECTIVITY_ERROR || state == RtpEndUserState.APPLICATION_ERROR) {
402 this.binding.rejectCall.setOnClickListener(this::exit);
403 this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
404 this.binding.rejectCall.setVisibility(View.VISIBLE);
405 this.binding.endCall.setVisibility(View.INVISIBLE);
406 this.binding.acceptCall.setOnClickListener(this::retry);
407 this.binding.acceptCall.setImageResource(R.drawable.ic_replay_white_48dp);
408 this.binding.acceptCall.setVisibility(View.VISIBLE);
409 } else {
410 this.binding.rejectCall.setVisibility(View.INVISIBLE);
411 this.binding.endCall.setOnClickListener(this::endCall);
412 this.binding.endCall.setImageResource(R.drawable.ic_call_end_white_48dp);
413 this.binding.endCall.setVisibility(View.VISIBLE);
414 this.binding.acceptCall.setVisibility(View.INVISIBLE);
415 }
416 updateInCallButtonConfiguration(state);
417 }
418
419 private void updateInCallButtonConfiguration() {
420 updateInCallButtonConfiguration(requireRtpConnection().getEndUserState());
421 }
422
423 @SuppressLint("RestrictedApi")
424 private void updateInCallButtonConfiguration(final RtpEndUserState state) {
425 if (state == RtpEndUserState.CONNECTED) {
426 if (getMedia().contains(Media.VIDEO)) {
427 updateInCallButtonConfigurationVideo(requireRtpConnection().isVideoEnabled());
428 } else {
429 final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
430 updateInCallButtonConfigurationSpeaker(
431 audioManager.getSelectedAudioDevice(),
432 audioManager.getAudioDevices().size()
433 );
434 }
435 updateInCallButtonConfigurationMicrophone(requireRtpConnection().isMicrophoneEnabled());
436 } else {
437 this.binding.inCallActionLeft.setVisibility(View.GONE);
438 this.binding.inCallActionRight.setVisibility(View.GONE);
439 }
440 }
441
442 @SuppressLint("RestrictedApi")
443 private void updateInCallButtonConfigurationSpeaker(final AppRTCAudioManager.AudioDevice selectedAudioDevice, final int numberOfChoices) {
444 switch (selectedAudioDevice) {
445 case EARPIECE:
446 this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_off_black_24dp);
447 if (numberOfChoices >= 2) {
448 this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker);
449 } else {
450 this.binding.inCallActionRight.setOnClickListener(null);
451 this.binding.inCallActionRight.setClickable(false);
452 }
453 break;
454 case WIRED_HEADSET:
455 this.binding.inCallActionRight.setImageResource(R.drawable.ic_headset_black_24dp);
456 this.binding.inCallActionRight.setOnClickListener(null);
457 this.binding.inCallActionRight.setClickable(false);
458 break;
459 case SPEAKER_PHONE:
460 this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_up_black_24dp);
461 if (numberOfChoices >= 2) {
462 this.binding.inCallActionRight.setOnClickListener(this::switchToEarpiece);
463 } else {
464 this.binding.inCallActionRight.setOnClickListener(null);
465 this.binding.inCallActionRight.setClickable(false);
466 }
467 break;
468 case BLUETOOTH:
469 this.binding.inCallActionRight.setImageResource(R.drawable.ic_bluetooth_audio_black_24dp);
470 this.binding.inCallActionRight.setOnClickListener(null);
471 this.binding.inCallActionRight.setClickable(false);
472 break;
473 }
474 this.binding.inCallActionRight.setVisibility(View.VISIBLE);
475 }
476
477 @SuppressLint("RestrictedApi")
478 private void updateInCallButtonConfigurationVideo(final boolean videoEnabled) {
479 this.binding.inCallActionRight.setVisibility(View.VISIBLE);
480 if (videoEnabled) {
481 this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_black_24dp);
482 this.binding.inCallActionRight.setOnClickListener(this::disableVideo);
483 } else {
484 this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_off_black_24dp);
485 this.binding.inCallActionRight.setOnClickListener(this::enableVideo);
486 }
487 }
488
489 private void enableVideo(View view) {
490 requireRtpConnection().setVideoEnabled(true);
491 updateInCallButtonConfigurationVideo(true);
492 }
493
494 private void disableVideo(View view) {
495 requireRtpConnection().setVideoEnabled(false);
496 updateInCallButtonConfigurationVideo(false);
497
498 }
499
500 @SuppressLint("RestrictedApi")
501 private void updateInCallButtonConfigurationMicrophone(final boolean microphoneEnabled) {
502 if (microphoneEnabled) {
503 this.binding.inCallActionLeft.setImageResource(R.drawable.ic_mic_black_24dp);
504 this.binding.inCallActionLeft.setOnClickListener(this::disableMicrophone);
505 } else {
506 this.binding.inCallActionLeft.setImageResource(R.drawable.ic_mic_off_black_24dp);
507 this.binding.inCallActionLeft.setOnClickListener(this::enableMicrophone);
508 }
509 this.binding.inCallActionLeft.setVisibility(View.VISIBLE);
510 }
511
512 private void updateVideoViews(final RtpEndUserState state) {
513 if (END_CARD.contains(state) || state == RtpEndUserState.ENDING_CALL) {
514 binding.localVideo.setVisibility(View.GONE);
515 binding.remoteVideo.setVisibility(View.GONE);
516 binding.appBarLayout.setVisibility(View.VISIBLE);
517 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
518 return;
519 }
520 final Optional<VideoTrack> localVideoTrack = requireRtpConnection().geLocalVideoTrack();
521 if (localVideoTrack.isPresent()) {
522 ensureSurfaceViewRendererIsSetup(binding.localVideo);
523 //paint local view over remote view
524 binding.localVideo.setZOrderMediaOverlay(true);
525 binding.localVideo.setMirror(true);
526 localVideoTrack.get().addSink(binding.localVideo);
527 } else {
528 binding.localVideo.setVisibility(View.GONE);
529 }
530 final Optional<VideoTrack> remoteVideoTrack = requireRtpConnection().getRemoteVideoTrack();
531 if (remoteVideoTrack.isPresent()) {
532 ensureSurfaceViewRendererIsSetup(binding.remoteVideo);
533 remoteVideoTrack.get().addSink(binding.remoteVideo);
534 if (state == RtpEndUserState.CONNECTED) {
535 binding.appBarLayout.setVisibility(View.GONE);
536 getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
537 } else {
538 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
539 binding.remoteVideo.setVisibility(View.GONE);
540 }
541 } else {
542 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
543 binding.remoteVideo.setVisibility(View.GONE);
544 }
545 }
546
547 private void disableMicrophone(View view) {
548 JingleRtpConnection rtpConnection = requireRtpConnection();
549 rtpConnection.setMicrophoneEnabled(false);
550 updateInCallButtonConfiguration();
551 }
552
553 private void enableMicrophone(View view) {
554 JingleRtpConnection rtpConnection = requireRtpConnection();
555 rtpConnection.setMicrophoneEnabled(true);
556 updateInCallButtonConfiguration();
557 }
558
559 private void switchToEarpiece(View view) {
560 requireRtpConnection().getAudioManager().setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE);
561 acquireProximityWakeLock();
562 }
563
564 private void switchToSpeaker(View view) {
565 requireRtpConnection().getAudioManager().setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
566 releaseProximityWakeLock();
567 }
568
569 private void retry(View view) {
570 Log.d(Config.LOGTAG, "attempting retry");
571 final Intent intent = getIntent();
572 final Account account = extractAccount(intent);
573 final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH));
574 final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION);
575 final String action = intent.getAction();
576 final Set<Media> media = actionToMedia(lastAction == null ? action : lastAction);
577 this.rtpConnectionReference = null;
578 proposeJingleRtpSession(account, with, media);
579 }
580
581 private void exit(View view) {
582 finish();
583 }
584
585 private Contact getWith() {
586 final AbstractJingleConnection.Id id = requireRtpConnection().getId();
587 final Account account = id.account;
588 return account.getRoster().getContact(id.with);
589 }
590
591 private JingleRtpConnection requireRtpConnection() {
592 final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
593 if (connection == null) {
594 throw new IllegalStateException("No RTP connection found");
595 }
596 return connection;
597 }
598
599 @Override
600 public void onJingleRtpConnectionUpdate(Account account, Jid with, final String sessionId, RtpEndUserState state) {
601 Log.d(Config.LOGTAG, "onJingleRtpConnectionUpdate(" + state + ")");
602 if (END_CARD.contains(state)) {
603 Log.d(Config.LOGTAG,"end card reached");
604 releaseProximityWakeLock();
605 runOnUiThread(()-> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
606 }
607 if (with.isBareJid()) {
608 updateRtpSessionProposalState(account, with, state);
609 return;
610 }
611 if (this.rtpConnectionReference == null) {
612 if (END_CARD.contains(state)) {
613 Log.d(Config.LOGTAG,"not reinitializing session");
614 return;
615 }
616 //this happens when going from proposed session to actual session
617 reInitializeActivityWithRunningRapSession(account, with, sessionId);
618 return;
619 }
620 final AbstractJingleConnection.Id id = requireRtpConnection().getId();
621 if (account == id.account && id.with.equals(with) && id.sessionId.equals(sessionId)) {
622 if (state == RtpEndUserState.ENDED) {
623 finish();
624 return;
625 } else if (END_CARD.contains(state)) {
626 resetIntent(account, with, state, requireRtpConnection().getMedia());
627 }
628 runOnUiThread(() -> {
629 updateStateDisplay(state);
630 updateButtonConfiguration(state);
631 updateVideoViews(state);
632 });
633 } else {
634 Log.d(Config.LOGTAG, "received update for other rtp session");
635 //TODO if we only ever have one; we might just switch over? Maybe!
636 }
637 }
638
639 @Override
640 public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
641 Log.d(Config.LOGTAG, "onAudioDeviceChanged in activity: selected:" + selectedAudioDevice + ", available:" + availableAudioDevices);
642 try {
643 if (getMedia().contains(Media.VIDEO)) {
644 Log.d(Config.LOGTAG,"nothing to do; in video mode");
645 return;
646 }
647 final RtpEndUserState endUserState = requireRtpConnection().getEndUserState();
648 if (endUserState == RtpEndUserState.CONNECTED) {
649 final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
650 updateInCallButtonConfigurationSpeaker(
651 audioManager.getSelectedAudioDevice(),
652 audioManager.getAudioDevices().size()
653 );
654 } else if (END_CARD.contains(endUserState)) {
655 Log.d(Config.LOGTAG,"onAudioDeviceChanged() nothing to do because end card has been reached");
656 } else {
657 putProximityWakeLockInProperState();
658 }
659 } catch (IllegalStateException e) {
660 Log.d(Config.LOGTAG, "RTP connection was not available when audio device changed");
661 }
662 }
663
664 private void updateRtpSessionProposalState(final Account account, final Jid with, final RtpEndUserState state) {
665 final Intent currentIntent = getIntent();
666 final String withExtra = currentIntent == null ? null : currentIntent.getStringExtra(EXTRA_WITH);
667 if (withExtra == null) {
668 return;
669 }
670 if (Jid.ofEscaped(withExtra).asBareJid().equals(with)) {
671 runOnUiThread(() -> {
672 updateStateDisplay(state);
673 updateButtonConfiguration(state);
674 });
675 resetIntent(account, with, state, actionToMedia(currentIntent.getAction()));
676 }
677 }
678
679 private void resetIntent(final Bundle extras) {
680 final Intent intent = new Intent(Intent.ACTION_VIEW);
681 intent.putExtras(extras);
682 setIntent(intent);
683 }
684
685 private void resetIntent(final Account account, Jid with, final RtpEndUserState state, final Set<Media> media) {
686 final Intent intent = new Intent(Intent.ACTION_VIEW);
687 intent.putExtra(EXTRA_WITH, with.asBareJid().toEscapedString());
688 intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString());
689 intent.putExtra(EXTRA_LAST_REPORTED_STATE, state.toString());
690 intent.putExtra(EXTRA_LAST_ACTION, media.contains(Media.VIDEO) ? ACTION_MAKE_VIDEO_CALL : ACTION_MAKE_VOICE_CALL);
691 setIntent(intent);
692 }
693}