RtpSessionActivity.java

  1package eu.siacs.conversations.ui;
  2
  3import android.Manifest;
  4import android.app.Activity;
  5import android.content.Intent;
  6import android.content.pm.PackageManager;
  7import android.databinding.DataBindingUtil;
  8import android.os.Build;
  9import android.os.Bundle;
 10import android.support.annotation.NonNull;
 11import android.support.annotation.StringRes;
 12import android.support.v4.app.ActivityCompat;
 13import android.util.Log;
 14import android.view.View;
 15import android.view.WindowManager;
 16import android.widget.Toast;
 17
 18import com.google.common.collect.ImmutableList;
 19
 20import java.lang.ref.WeakReference;
 21import java.util.List;
 22
 23import eu.siacs.conversations.Config;
 24import eu.siacs.conversations.R;
 25import eu.siacs.conversations.databinding.ActivityRtpSessionBinding;
 26import eu.siacs.conversations.entities.Account;
 27import eu.siacs.conversations.entities.Contact;
 28import eu.siacs.conversations.services.XmppConnectionService;
 29import eu.siacs.conversations.utils.PermissionUtils;
 30import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
 31import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
 32import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
 33import rocks.xmpp.addr.Jid;
 34
 35import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied;
 36import static java.util.Arrays.asList;
 37
 38//TODO if last state was BUSY (or RETRY); we want to reset action to view or something so we don’t automatically call again on recreate
 39
 40public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate {
 41
 42    private static final int REQUEST_ACCEPT_CALL = 0x1111;
 43
 44    public static final String EXTRA_WITH = "with";
 45    public static final String EXTRA_SESSION_ID = "session_id";
 46    public static final String EXTRA_LAST_REPORTED_STATE = "last_reported_state";
 47
 48    public static final String ACTION_ACCEPT_CALL = "action_accept_call";
 49    public static final String ACTION_MAKE_VOICE_CALL = "action_make_voice_call";
 50    public static final String ACTION_MAKE_VIDEO_CALL = "action_make_video_call";
 51
 52    private WeakReference<JingleRtpConnection> rtpConnectionReference;
 53
 54    private ActivityRtpSessionBinding binding;
 55
 56    @Override
 57    public void onCreate(Bundle savedInstanceState) {
 58        super.onCreate(savedInstanceState);
 59        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
 60                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
 61                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
 62                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
 63        ;
 64        Log.d(Config.LOGTAG, "RtpSessionActivity.onCreate()");
 65        this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session);
 66    }
 67
 68    @Override
 69    public void onStart() {
 70        super.onStart();
 71        Log.d(Config.LOGTAG, "RtpSessionActivity.onStart()");
 72    }
 73
 74    private void endCall(View view) {
 75        if (this.rtpConnectionReference == null) {
 76            final Intent intent = getIntent();
 77            final Account account = extractAccount(intent);
 78            final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH));
 79            xmppConnectionService.getJingleConnectionManager().retractSessionProposal(account, with.asBareJid());
 80            finish();
 81        } else {
 82            requireRtpConnection().endCall();
 83        }
 84    }
 85
 86    private void rejectCall(View view) {
 87        requireRtpConnection().rejectCall();
 88        finish();
 89    }
 90
 91    private void acceptCall(View view) {
 92        requestPermissionsAndAcceptCall();
 93    }
 94
 95    private void requestPermissionsAndAcceptCall() {
 96        if (PermissionUtils.hasPermission(this, ImmutableList.of(Manifest.permission.RECORD_AUDIO), REQUEST_ACCEPT_CALL)) {
 97            requireRtpConnection().acceptCall();
 98        }
 99    }
100
101    @Override
102    protected void refreshUiReal() {
103
104    }
105
106    @Override
107    public void onNewIntent(final Intent intent) {
108        super.onNewIntent(intent);
109        //TODO reinitialize
110        if (ACTION_ACCEPT_CALL.equals(intent.getAction())) {
111            Log.d(Config.LOGTAG, "accepting through onNewIntent()");
112            requestPermissionsAndAcceptCall();
113        }
114    }
115
116    @Override
117    void onBackendConnected() {
118        final Intent intent = getIntent();
119        final Account account = extractAccount(intent);
120        final Jid with = Jid.of(intent.getStringExtra(EXTRA_WITH));
121        final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
122        if (sessionId != null) {
123            initializeActivityWithRunningRapSession(account, with, sessionId);
124            if (ACTION_ACCEPT_CALL.equals(intent.getAction())) {
125                Log.d(Config.LOGTAG, "intent action was accept");
126                requestPermissionsAndAcceptCall();
127            }
128        } else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(intent.getAction())) {
129            xmppConnectionService.getJingleConnectionManager().proposeJingleRtpSession(account, with);
130            binding.with.setText(account.getRoster().getContact(with).getDisplayName());
131        } else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
132            final String extraLastState = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
133            if (extraLastState != null) {
134                Log.d(Config.LOGTAG, "restored last state from intent extra");
135                RtpEndUserState state = RtpEndUserState.valueOf(extraLastState);
136                updateButtonConfiguration(state);
137                updateStateDisplay(state);
138            }
139            binding.with.setText(account.getRoster().getContact(with).getDisplayName());
140        }
141    }
142
143    @Override
144    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
145        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
146        if (PermissionUtils.allGranted(grantResults)) {
147            if (requestCode == REQUEST_ACCEPT_CALL) {
148                requireRtpConnection().acceptCall();
149            }
150        } else {
151            @StringRes int res;
152            final String firstDenied = getFirstDenied(grantResults, permissions);
153            if (Manifest.permission.RECORD_AUDIO.equals(firstDenied)) {
154                res = R.string.no_microphone_permission;
155            } else if (Manifest.permission.CAMERA.equals(firstDenied)) {
156                res = R.string.no_camera_permission;
157            } else {
158                throw new IllegalStateException("Invalid permission result request");
159            }
160            Toast.makeText(this, res, Toast.LENGTH_SHORT).show();
161        }
162    }
163
164
165    private void initializeActivityWithRunningRapSession(final Account account, Jid with, String sessionId) {
166        final WeakReference<JingleRtpConnection> reference = xmppConnectionService.getJingleConnectionManager()
167                .findJingleRtpConnection(account, with, sessionId);
168        if (reference == null || reference.get() == null) {
169            finish();
170            return;
171        }
172        this.rtpConnectionReference = reference;
173        final RtpEndUserState currentState = requireRtpConnection().getEndUserState();
174        if (currentState == RtpEndUserState.ENDED) {
175            finish();
176            return;
177        }
178        binding.with.setText(getWith().getDisplayName());
179        updateStateDisplay(currentState);
180        updateButtonConfiguration(currentState);
181    }
182
183    private void reInitializeActivityWithRunningRapSession(final Account account, Jid with, String sessionId) {
184        runOnUiThread(() -> {
185            initializeActivityWithRunningRapSession(account, with, sessionId);
186        });
187        final Intent intent = new Intent(Intent.ACTION_VIEW);
188        intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString());
189        intent.putExtra(EXTRA_WITH, with.toEscapedString());
190        intent.putExtra(EXTRA_SESSION_ID, sessionId);
191        setIntent(intent);
192    }
193
194    private void updateStateDisplay(final RtpEndUserState state) {
195        switch (state) {
196            case INCOMING_CALL:
197                binding.status.setText(R.string.rtp_state_incoming_call);
198                break;
199            case CONNECTING:
200                binding.status.setText(R.string.rtp_state_connecting);
201                break;
202            case CONNECTED:
203                binding.status.setText(R.string.rtp_state_connected);
204                break;
205            case ACCEPTING_CALL:
206                binding.status.setText(R.string.rtp_state_accepting_call);
207                break;
208            case ENDING_CALL:
209                binding.status.setText(R.string.rtp_state_ending_call);
210                break;
211            case FINDING_DEVICE:
212                binding.status.setText(R.string.rtp_state_finding_device);
213                break;
214            case RINGING:
215                binding.status.setText(R.string.rtp_state_ringing);
216                break;
217            case DECLINED_OR_BUSY:
218                binding.status.setText(R.string.rtp_state_declined_or_busy);
219                break;
220            case CONNECTIVITY_ERROR:
221                binding.status.setText(R.string.rtp_state_connectivity_error);
222                break;
223            case APPLICATION_ERROR:
224                binding.status.setText(R.string.rtp_state_application_failure);
225                break;
226            case ENDED:
227                throw new IllegalStateException("Activity should have called finish()");
228            default:
229                throw new IllegalStateException(String.format("State %s has not been handled in UI", state));
230        }
231    }
232
233    private void updateButtonConfiguration(final RtpEndUserState state) {
234        if (state == RtpEndUserState.INCOMING_CALL) {
235            this.binding.rejectCall.setOnClickListener(this::rejectCall);
236            this.binding.rejectCall.setImageResource(R.drawable.ic_call_end_white_48dp);
237            this.binding.rejectCall.show();
238            this.binding.endCall.hide();
239            this.binding.acceptCall.setOnClickListener(this::acceptCall);
240            this.binding.acceptCall.setImageResource(R.drawable.ic_call_white_48dp);
241            this.binding.acceptCall.show();
242        } else if (state == RtpEndUserState.ENDING_CALL) {
243            this.binding.rejectCall.hide();
244            this.binding.endCall.hide();
245            this.binding.acceptCall.hide();
246        } else if (state == RtpEndUserState.DECLINED_OR_BUSY) {
247            this.binding.rejectCall.hide();
248            this.binding.endCall.setOnClickListener(this::exit);
249            this.binding.endCall.setImageResource(R.drawable.ic_clear_white_48dp);
250            this.binding.endCall.show();
251            this.binding.acceptCall.hide();
252        } else if (state == RtpEndUserState.CONNECTIVITY_ERROR || state == RtpEndUserState.APPLICATION_ERROR) {
253            this.binding.rejectCall.setOnClickListener(this::exit);
254            this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
255            this.binding.rejectCall.show();
256            this.binding.endCall.hide();
257            this.binding.acceptCall.setOnClickListener(this::retry);
258            this.binding.acceptCall.setImageResource(R.drawable.ic_replay_white_48dp);
259            this.binding.acceptCall.show();
260        } else {
261            this.binding.rejectCall.hide();
262            this.binding.endCall.setOnClickListener(this::endCall);
263            this.binding.endCall.setImageResource(R.drawable.ic_call_end_white_48dp);
264            this.binding.endCall.show();
265            this.binding.acceptCall.hide();
266        }
267    }
268
269    private void retry(View view) {
270        Log.d(Config.LOGTAG, "attempting retry");
271    }
272
273    private void exit(View view) {
274        finish();
275    }
276
277    private Contact getWith() {
278        final AbstractJingleConnection.Id id = requireRtpConnection().getId();
279        final Account account = id.account;
280        return account.getRoster().getContact(id.with);
281    }
282
283    private JingleRtpConnection requireRtpConnection() {
284        final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
285        if (connection == null) {
286            throw new IllegalStateException("No RTP connection found");
287        }
288        return connection;
289    }
290
291    @Override
292    public void onJingleRtpConnectionUpdate(Account account, Jid with, final String sessionId, RtpEndUserState state) {
293        Log.d(Config.LOGTAG, "onJingleRtpConnectionUpdate(" + state + ")");
294        if (with.isBareJid()) {
295            updateRtpSessionProposalState(account, with, state);
296            return;
297        }
298        if (this.rtpConnectionReference == null) {
299            //this happens when going from proposed session to actual session
300            reInitializeActivityWithRunningRapSession(account, with, sessionId);
301            return;
302        }
303        final AbstractJingleConnection.Id id = requireRtpConnection().getId();
304        if (account == id.account && id.with.equals(with) && id.sessionId.equals(sessionId)) {
305            if (state == RtpEndUserState.ENDED) {
306                finish();
307                return;
308            } else if (asList(RtpEndUserState.APPLICATION_ERROR, RtpEndUserState.DECLINED_OR_BUSY, RtpEndUserState.CONNECTIVITY_ERROR).contains(state)) {
309                resetIntent(account, with, state);
310            }
311            runOnUiThread(() -> {
312                updateStateDisplay(state);
313                updateButtonConfiguration(state);
314            });
315        } else {
316            Log.d(Config.LOGTAG, "received update for other rtp session");
317        }
318    }
319
320    private void updateRtpSessionProposalState(final Account account, final Jid with, final RtpEndUserState state) {
321        final Intent currentIntent = getIntent();
322        final String withExtra = currentIntent == null ? null : currentIntent.getStringExtra(EXTRA_WITH);
323        if (withExtra == null) {
324            return;
325        }
326        if (Jid.ofEscaped(withExtra).asBareJid().equals(with)) {
327            runOnUiThread(() -> {
328                updateStateDisplay(state);
329                updateButtonConfiguration(state);
330            });
331            resetIntent(account, with, state);
332        }
333    }
334
335    private void resetIntent(final Account account, Jid with, final RtpEndUserState state) {
336        Log.d(Config.LOGTAG, "resetting intent");
337        final Intent intent = new Intent(Intent.ACTION_VIEW);
338        intent.putExtra(EXTRA_WITH, with.asBareJid().toEscapedString());
339        intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString());
340        intent.putExtra(EXTRA_LAST_REPORTED_STATE, state.toString());
341        setIntent(intent);
342    }
343}