Allow Android / iOS devices (e.g. tablets, but also phones) to be linked to the primary device, i.e. used as secondary device like the Desktop app

It would be useful - especially for tablets - if they could be linked to the primary device (phone), like the Desktop app.
Linking secondary phones would also make sense, for people who have multiple phones, e.g. cheaper replacement phones that are only used occasionally.

There has been some discussion about this already in 2016:

201 Likes

As a new user I can’t believe this basic feature hasn’t been implemented! In fact I just wasted 20 minutes trying to find out how to link my tablet either to my phone or to the Desktop app. If this isn’t possible I’m not sure what the point is of installing the app on my tablet at all.

14 Likes

If you build Signal-Android yourself, you can test GitHub - Trolldemorted/Signal-Android at multidevice and annoy the devs to merge it :)

11 Likes

Cool. So do I just have to use your fork only on the secondary device or also on the primary one?

1 Like

Primary device can stay vanilla! I just took Turakar and AsamK’s libsignal-service-java and drilled Signal-Android to also operate as a slave.

2 Likes

I finally had some time to test your fork. The UI is still a bit rough around the edges, but the important stuff works in a few initial tests! Very nice! :grinning:

3 Likes

OK, unfortunately, your fork causes issues with Signal calls. Now calls only arrive on my tablet, not on my phone the device that has last gone online(?). I guess that is one of the reasons why it wasn’t merged in the official version yet.

2 Likes

Unfurtunately I only have one android device at hand, but i can see if I can get my hands on another one.

Is there anything unusual in the debug logs?

The most important reason is nobody really cares about Signal-Android slaves, I think. But I created it when moxie had no staff working on Signal-Android, maybe we are lucky and they will find the time to look at this soon now that they have a few employees.

3 Likes

From what I could figure out, the call never arrived the other device - I’m not sure however. Maybe the server doesn’t support multi-device calling yet?

1 Like

That would be great! Maybe you can simply use the emulator. However, to recreate this bug, you need three (emulated) devices or two and a friend who calls you.

I wonder why - I think that’s something the users want. At least it’s something people find great about Telegram.

I hope so, too. By the way, at the moment, Signal can’t be installed on an Android tablet from the Play Store. So maybe they’re planning Android slaves in the long run - or they just don’t want people to think it’s possible and leave bad reviews.

2 Likes

The server does not interfere with call establishing, all call messages are end-to-end encrypted.

I added webrtc support to Signal-Windows in a weekend hack so that I could test with my desktops, and found the culprit: Signal-Android is establishing calls before the you accept the call.

This happens when you receive a call offer message:

  private void handleIncomingCall(final Intent intent) {
    Log.w(TAG, "handleIncomingCall()");
    if (callState != CallState.STATE_IDLE) throw new IllegalStateException("Incoming on non-idle");

    final String offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION);

    this.callState                 = CallState.STATE_ANSWERING;
    this.callId                    = intent.getLongExtra(EXTRA_CALL_ID, -1);
    this.pendingIncomingIceUpdates = new LinkedList<>();
    this.recipient                 = getRemoteRecipient(intent);

    if (isIncomingMessageExpired(intent)) {
      insertMissedCall(this.recipient, true);
      terminate();
      return;
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      setCallInProgressNotification(TYPE_INCOMING_CONNECTING, this.recipient);
    }

    timeoutExecutor.schedule(new TimeoutRunnable(this.callId), 2, TimeUnit.MINUTES);

    initializeVideo();

    retrieveTurnServers().addListener(new SuccessOnlyListener<List<PeerConnection.IceServer>>(this.callState, this.callId) {
      @Override
      public void onSuccessContinue(List<PeerConnection.IceServer> result) {
        try {
          boolean isSystemContact = false;

          if (Permissions.hasAny(WebRtcCallService.this, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) {
            isSystemContact = ContactAccessor.getInstance().isSystemContact(WebRtcCallService.this, recipient.getAddress().serialize());
          }

          boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this);

          WebRtcCallService.this.peerConnection = new PeerConnectionWrapper(WebRtcCallService.this, peerConnectionFactory, WebRtcCallService.this, localRenderer, result, !isSystemContact || isAlwaysTurn);
          WebRtcCallService.this.peerConnection.setRemoteDescription(new SessionDescription(SessionDescription.Type.OFFER, offer));
          WebRtcCallService.this.lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING);

          SessionDescription sdp = WebRtcCallService.this.peerConnection.createAnswer(new MediaConstraints());
          Log.w(TAG, "Answer SDP: " + sdp.description);
          WebRtcCallService.this.peerConnection.setLocalDescription(sdp);

          ListenableFutureTask<Boolean> listenableFutureTask = sendMessage(recipient, SignalServiceCallMessage.forAnswer(new AnswerMessage(WebRtcCallService.this.callId, sdp.description)));

          for (IceCandidate candidate : pendingIncomingIceUpdates) WebRtcCallService.this.peerConnection.addIceCandidate(candidate);
          WebRtcCallService.this.pendingIncomingIceUpdates = null;

          listenableFutureTask.addListener(new FailureListener<Boolean>(WebRtcCallService.this.callState, WebRtcCallService.this.callId) {
            @Override
            public void onFailureContinue(Throwable error) {
              Log.w(TAG, error);
              insertMissedCall(recipient, true);
              terminate();
            }
          });
        } catch (PeerConnectionException e) {
          Log.w(TAG, e);
          terminate();
        }
      }
    });
  }

When you accept the call, Signal-Android just enables audio and video and notifies the other end of the connection:

  private void handleAnswerCall(Intent intent) {
    if (callState != CallState.STATE_LOCAL_RINGING) {
      Log.w(TAG, "Can only answer from ringing!");
      return;
    }

    if (peerConnection == null || dataChannel == null || recipient == null || callId == null) {
      throw new AssertionError("assert");
    }

    DatabaseFactory.getSmsDatabase(this).insertReceivedCall(recipient.getAddress());

    this.peerConnection.setAudioEnabled(true);
    this.peerConnection.setVideoEnabled(true);
    this.dataChannel.send(new DataChannel.Buffer(ByteBuffer.wrap(Data.newBuilder().setConnected(Connected.newBuilder().setId(this.callId)).build().toByteArray()), false));

    intent.putExtra(EXTRA_CALL_ID, callId);
    intent.putExtra(EXTRA_REMOTE_ADDRESS, recipient.getAddress());
    handleCallConnected(intent);
  }

If you convince OWS to make their vanilla clients only establish the webrtc channel if the callee accepts the call, you should be able to accept the call on any device.

5 Likes

I hope they will read this :slight_smile:.
So, if I would only use your ā€œmultideviceā€ branch on all devices, it would work?

3 Likes

Not sure they are actively reading the forum, you might try to tag them (@greyson-signal), poke them on twitter, or investigate if there are security reasons why Signal-Android should not engage webrtc connections without the user’s approval (maybe some private info like your IP (local and global) are leaked during the webrtc handshake?).

No I didn’t fix this yet, I just noticed it yesterday. If Signal-Android is leaking private data during the webrtc handshake we might get an upstream fix though! Unfortunately I don’t know a lot about webrtc’s internals.

1 Like

Does it make sense at all to post feature requests here then?
Of course in this case, it was worth it, because you could point to your fork :slight_smile:

1 Like

We do read the forums, but unfortunately we don’t have time to respond to every thread in detail.

8 Likes

This would be great! I want to get a new tablet and the only con with it right now is that I cannot use Signal on it unless I get a new phone number. I can sync my SMS messages to my new tablet since there are apps for that like PulseSMS.

3 Likes

Need this feature as well. 2 Android phones (share same phone number), and need them both to message with.

7 Likes

Just another note that this would be very useful. I’m currently trying to get away from Hangouts for talking with non-technical friends, but for some of them (and for me for that matter) not being able to carry on a conversation on a phone and and iPad, for example, is a deal-breaker.

7 Likes

I’m also in favor of this feature. From the beginning of next year I will use 2 phones (private and business) and I would like to get the message on bothi devices. Furthermore it would be useful to have two signal identities in signal. But there are other requests for this.

4 Likes

This missing feature is the sole reason why I can’t convince my friends and family to switch from iMessage.

11 Likes