RtpSessionActivity.java

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