RtpSessionActivity.java

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