1 package org
.asamk
.signal
.dbus
;
3 import org
.asamk
.Signal
;
4 import org
.asamk
.Signal
.Error
;
5 import org
.asamk
.signal
.BaseConfig
;
6 import org
.asamk
.signal
.commands
.exceptions
.IOErrorException
;
7 import org
.asamk
.signal
.manager
.AttachmentInvalidException
;
8 import org
.asamk
.signal
.manager
.Manager
;
9 import org
.asamk
.signal
.manager
.NotMasterDeviceException
;
10 import org
.asamk
.signal
.manager
.StickerPackInvalidException
;
11 import org
.asamk
.signal
.manager
.UntrustedIdentityException
;
12 import org
.asamk
.signal
.manager
.api
.Identity
;
13 import org
.asamk
.signal
.manager
.api
.Message
;
14 import org
.asamk
.signal
.manager
.api
.RecipientIdentifier
;
15 import org
.asamk
.signal
.manager
.api
.TypingAction
;
16 import org
.asamk
.signal
.manager
.groups
.GroupId
;
17 import org
.asamk
.signal
.manager
.groups
.GroupInviteLinkUrl
;
18 import org
.asamk
.signal
.manager
.groups
.GroupNotFoundException
;
19 import org
.asamk
.signal
.manager
.groups
.GroupSendingNotAllowedException
;
20 import org
.asamk
.signal
.manager
.groups
.LastGroupAdminException
;
21 import org
.asamk
.signal
.manager
.groups
.NotAGroupMemberException
;
22 import org
.asamk
.signal
.manager
.storage
.recipients
.Profile
;
23 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientAddress
;
24 import org
.asamk
.signal
.util
.ErrorUtils
;
25 import org
.freedesktop
.dbus
.DBusPath
;
26 import org
.freedesktop
.dbus
.connections
.impl
.DBusConnection
;
27 import org
.freedesktop
.dbus
.exceptions
.DBusException
;
28 import org
.freedesktop
.dbus
.exceptions
.DBusExecutionException
;
29 import org
.whispersystems
.libsignal
.InvalidKeyException
;
30 import org
.whispersystems
.libsignal
.util
.Pair
;
31 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
32 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
33 import org
.whispersystems
.signalservice
.api
.messages
.SendMessageResult
;
34 import org
.whispersystems
.signalservice
.api
.push
.exceptions
.UnregisteredUserException
;
35 import org
.whispersystems
.signalservice
.api
.util
.InvalidNumberException
;
36 import org
.whispersystems
.signalservice
.internal
.contacts
.crypto
.UnauthenticatedResponseException
;
39 import java
.io
.IOException
;
41 import java
.net
.URISyntaxException
;
42 import java
.util
.ArrayList
;
43 import java
.util
.Collection
;
44 import java
.util
.HashSet
;
45 import java
.util
.List
;
47 import java
.util
.Objects
;
49 import java
.util
.UUID
;
50 import java
.util
.stream
.Collectors
;
51 import java
.util
.stream
.Stream
;
53 public class DbusSignalImpl
implements Signal
{
55 private final Manager m
;
56 private final DBusConnection connection
;
57 private final String objectPath
;
59 private DBusPath thisDevice
;
60 private final List
<StructDevice
> devices
= new ArrayList
<>();
62 public DbusSignalImpl(final Manager m
, DBusConnection connection
, final String objectPath
) {
64 this.connection
= connection
;
65 this.objectPath
= objectPath
;
68 public void initObjects() {
77 public String
getObjectPath() {
82 public String
getSelfNumber() {
83 return m
.getSelfNumber();
87 public void submitRateLimitChallenge(String challenge
, String captchaString
) throws IOErrorException
{
88 final var captcha
= captchaString
== null ?
null : captchaString
.replace("signalcaptcha://", "");
91 m
.submitRateLimitRecaptchaChallenge(challenge
, captcha
);
92 } catch (IOException e
) {
93 throw new IOErrorException("Submit challenge error: " + e
.getMessage(), e
);
99 public void addDevice(String uri
) {
101 m
.addDeviceLink(new URI(uri
));
102 } catch (IOException
| InvalidKeyException e
) {
103 throw new Error
.Failure(e
.getClass().getSimpleName() + " Add device link failed. " + e
.getMessage());
104 } catch (URISyntaxException e
) {
105 throw new Error
.InvalidUri(e
.getClass().getSimpleName()
106 + " Device link uri has invalid format: "
112 public DBusPath
getDevice(long deviceId
) {
114 return new DBusPath(getDeviceObjectPath(objectPath
, deviceId
));
118 public List
<StructDevice
> listDevices() {
124 public DBusPath
getThisDevice() {
130 public long sendMessage(final String message
, final List
<String
> attachments
, final String recipient
) {
131 var recipients
= new ArrayList
<String
>(1);
132 recipients
.add(recipient
);
133 return sendMessage(message
, attachments
, recipients
);
137 public long sendMessage(final String message
, final List
<String
> attachments
, final List
<String
> recipients
) {
139 final var results
= m
.sendMessage(new Message(message
, attachments
),
140 getSingleRecipientIdentifiers(recipients
, m
.getSelfNumber()).stream()
141 .map(RecipientIdentifier
.class::cast
)
142 .collect(Collectors
.toSet()));
144 checkSendMessageResults(results
.getTimestamp(), results
.getResults());
145 return results
.getTimestamp();
146 } catch (AttachmentInvalidException e
) {
147 throw new Error
.AttachmentInvalid(e
.getMessage());
148 } catch (IOException e
) {
149 throw new Error
.Failure(e
.getMessage());
150 } catch (GroupNotFoundException
| NotAGroupMemberException
| GroupSendingNotAllowedException e
) {
151 throw new Error
.GroupNotFound(e
.getMessage());
156 public long sendRemoteDeleteMessage(
157 final long targetSentTimestamp
, final String recipient
159 var recipients
= new ArrayList
<String
>(1);
160 recipients
.add(recipient
);
161 return sendRemoteDeleteMessage(targetSentTimestamp
, recipients
);
165 public long sendRemoteDeleteMessage(
166 final long targetSentTimestamp
, final List
<String
> recipients
169 final var results
= m
.sendRemoteDeleteMessage(targetSentTimestamp
,
170 getSingleRecipientIdentifiers(recipients
, m
.getSelfNumber()).stream()
171 .map(RecipientIdentifier
.class::cast
)
172 .collect(Collectors
.toSet()));
173 checkSendMessageResults(results
.getTimestamp(), results
.getResults());
174 return results
.getTimestamp();
175 } catch (IOException e
) {
176 throw new Error
.Failure(e
.getMessage());
177 } catch (GroupNotFoundException
| NotAGroupMemberException
| GroupSendingNotAllowedException e
) {
178 throw new Error
.GroupNotFound(e
.getMessage());
183 public long sendGroupRemoteDeleteMessage(
184 final long targetSentTimestamp
, final byte[] groupId
187 final var results
= m
.sendRemoteDeleteMessage(targetSentTimestamp
,
188 Set
.of(new RecipientIdentifier
.Group(getGroupId(groupId
))));
189 checkSendMessageResults(results
.getTimestamp(), results
.getResults());
190 return results
.getTimestamp();
191 } catch (IOException e
) {
192 throw new Error
.Failure(e
.getMessage());
193 } catch (GroupNotFoundException
| NotAGroupMemberException
| GroupSendingNotAllowedException e
) {
194 throw new Error
.GroupNotFound(e
.getMessage());
199 public long sendMessageReaction(
201 final boolean remove
,
202 final String targetAuthor
,
203 final long targetSentTimestamp
,
204 final String recipient
206 var recipients
= new ArrayList
<String
>(1);
207 recipients
.add(recipient
);
208 return sendMessageReaction(emoji
, remove
, targetAuthor
, targetSentTimestamp
, recipients
);
212 public long sendMessageReaction(
214 final boolean remove
,
215 final String targetAuthor
,
216 final long targetSentTimestamp
,
217 final List
<String
> recipients
220 final var results
= m
.sendMessageReaction(emoji
,
222 getSingleRecipientIdentifier(targetAuthor
, m
.getSelfNumber()),
224 getSingleRecipientIdentifiers(recipients
, m
.getSelfNumber()).stream()
225 .map(RecipientIdentifier
.class::cast
)
226 .collect(Collectors
.toSet()));
227 checkSendMessageResults(results
.getTimestamp(), results
.getResults());
228 return results
.getTimestamp();
229 } catch (IOException e
) {
230 throw new Error
.Failure(e
.getMessage());
231 } catch (GroupNotFoundException
| NotAGroupMemberException
| GroupSendingNotAllowedException e
) {
232 throw new Error
.GroupNotFound(e
.getMessage());
237 public void sendTyping(
238 final String recipient
, final boolean stop
239 ) throws Error
.Failure
, Error
.GroupNotFound
, Error
.UntrustedIdentity
{
241 var recipients
= new ArrayList
<String
>(1);
242 recipients
.add(recipient
);
243 m
.sendTypingMessage(stop ? TypingAction
.STOP
: TypingAction
.START
,
244 getSingleRecipientIdentifiers(recipients
, m
.getSelfNumber()).stream()
245 .map(RecipientIdentifier
.class::cast
)
246 .collect(Collectors
.toSet()));
247 } catch (IOException e
) {
248 throw new Error
.Failure(e
.getMessage());
249 } catch (GroupNotFoundException
| NotAGroupMemberException
| GroupSendingNotAllowedException e
) {
250 throw new Error
.GroupNotFound(e
.getMessage());
251 } catch (UntrustedIdentityException e
) {
252 throw new Error
.UntrustedIdentity(e
.getMessage());
257 public void sendReadReceipt(
258 final String recipient
, final List
<Long
> messageIds
259 ) throws Error
.Failure
, Error
.UntrustedIdentity
{
261 m
.sendReadReceipt(getSingleRecipientIdentifier(recipient
, m
.getSelfNumber()), messageIds
);
262 } catch (IOException e
) {
263 throw new Error
.Failure(e
.getMessage());
264 } catch (UntrustedIdentityException e
) {
265 throw new Error
.UntrustedIdentity(e
.getMessage());
270 public void sendContacts() {
273 } catch (IOException e
) {
274 throw new Error
.Failure("SendContacts error: " + e
.getMessage());
279 public void sendSyncRequest() {
281 m
.requestAllSyncData();
282 } catch (IOException e
) {
283 throw new Error
.Failure("Request sync data error: " + e
.getMessage());
288 public long sendNoteToSelfMessage(
289 final String message
, final List
<String
> attachments
290 ) throws Error
.AttachmentInvalid
, Error
.Failure
, Error
.UntrustedIdentity
{
292 final var results
= m
.sendMessage(new Message(message
, attachments
),
293 Set
.of(RecipientIdentifier
.NoteToSelf
.INSTANCE
));
294 checkSendMessageResults(results
.getTimestamp(), results
.getResults());
295 return results
.getTimestamp();
296 } catch (AttachmentInvalidException e
) {
297 throw new Error
.AttachmentInvalid(e
.getMessage());
298 } catch (IOException e
) {
299 throw new Error
.Failure(e
.getMessage());
300 } catch (GroupNotFoundException
| NotAGroupMemberException
| GroupSendingNotAllowedException e
) {
301 throw new Error
.GroupNotFound(e
.getMessage());
306 public void sendEndSessionMessage(final List
<String
> recipients
) {
308 final var results
= m
.sendEndSessionMessage(getSingleRecipientIdentifiers(recipients
, m
.getSelfNumber()));
309 checkSendMessageResults(results
.getTimestamp(), results
.getResults());
310 } catch (IOException e
) {
311 throw new Error
.Failure(e
.getMessage());
316 public long sendGroupMessage(final String message
, final List
<String
> attachments
, final byte[] groupId
) {
318 var results
= m
.sendMessage(new Message(message
, attachments
),
319 Set
.of(new RecipientIdentifier
.Group(getGroupId(groupId
))));
320 checkSendMessageResults(results
.getTimestamp(), results
.getResults());
321 return results
.getTimestamp();
322 } catch (IOException e
) {
323 throw new Error
.Failure(e
.getMessage());
324 } catch (GroupNotFoundException
| NotAGroupMemberException
| GroupSendingNotAllowedException e
) {
325 throw new Error
.GroupNotFound(e
.getMessage());
326 } catch (AttachmentInvalidException e
) {
327 throw new Error
.AttachmentInvalid(e
.getMessage());
332 public long sendGroupMessageReaction(
334 final boolean remove
,
335 final String targetAuthor
,
336 final long targetSentTimestamp
,
340 final var results
= m
.sendMessageReaction(emoji
,
342 getSingleRecipientIdentifier(targetAuthor
, m
.getSelfNumber()),
344 Set
.of(new RecipientIdentifier
.Group(getGroupId(groupId
))));
345 checkSendMessageResults(results
.getTimestamp(), results
.getResults());
346 return results
.getTimestamp();
347 } catch (IOException e
) {
348 throw new Error
.Failure(e
.getMessage());
349 } catch (GroupNotFoundException
| NotAGroupMemberException
| GroupSendingNotAllowedException e
) {
350 throw new Error
.GroupNotFound(e
.getMessage());
354 // Since contact names might be empty if not defined, also potentially return
357 public String
getContactName(final String number
) {
358 final var name
= m
.getContactOrProfileName(getSingleRecipientIdentifier(number
, m
.getSelfNumber()));
359 return name
== null ?
"" : name
;
363 public void setContactName(final String number
, final String name
) {
365 m
.setContactName(getSingleRecipientIdentifier(number
, m
.getSelfNumber()), name
);
366 } catch (NotMasterDeviceException e
) {
367 throw new Error
.Failure("This command doesn't work on linked devices.");
368 } catch (UnregisteredUserException e
) {
369 throw new Error
.Failure("Contact is not registered.");
374 public void setExpirationTimer(final String number
, final int expiration
) {
376 m
.setExpirationTimer(getSingleRecipientIdentifier(number
, m
.getSelfNumber()), expiration
);
377 } catch (IOException e
) {
378 throw new Error
.Failure(e
.getMessage());
383 public void setContactBlocked(final String number
, final boolean blocked
) {
385 m
.setContactBlocked(getSingleRecipientIdentifier(number
, m
.getSelfNumber()), blocked
);
386 } catch (NotMasterDeviceException e
) {
387 throw new Error
.Failure("This command doesn't work on linked devices.");
388 } catch (IOException e
) {
389 throw new Error
.Failure(e
.getMessage());
394 public void setGroupBlocked(final byte[] groupId
, final boolean blocked
) {
396 m
.setGroupBlocked(getGroupId(groupId
), blocked
);
397 } catch (GroupNotFoundException e
) {
398 throw new Error
.GroupNotFound(e
.getMessage());
399 } catch (IOException e
) {
400 throw new Error
.Failure(e
.getMessage());
405 public List
<byte[]> getGroupIds() {
406 var groups
= m
.getGroups();
407 var ids
= new ArrayList
<byte[]>(groups
.size());
408 for (var group
: groups
) {
409 ids
.add(group
.getGroupId().serialize());
415 public String
getGroupName(final byte[] groupId
) {
416 var group
= m
.getGroup(getGroupId(groupId
));
417 if (group
== null || group
.getTitle() == null) {
420 return group
.getTitle();
425 public List
<String
> getGroupMembers(final byte[] groupId
) {
426 var group
= m
.getGroup(getGroupId(groupId
));
430 return group
.getMembers().stream().map(RecipientAddress
::getLegacyIdentifier
).collect(Collectors
.toList());
435 public byte[] updateGroup(byte[] groupId
, String name
, List
<String
> members
, String avatar
) {
437 groupId
= nullIfEmpty(groupId
);
438 name
= nullIfEmpty(name
);
439 avatar
= nullIfEmpty(avatar
);
440 final var memberIdentifiers
= getSingleRecipientIdentifiers(members
, m
.getSelfNumber());
441 if (groupId
== null) {
442 final var results
= m
.createGroup(name
, memberIdentifiers
, avatar
== null ?
null : new File(avatar
));
443 checkSendMessageResults(results
.second().getTimestamp(), results
.second().getResults());
444 return results
.first().serialize();
446 final var results
= m
.updateGroup(getGroupId(groupId
),
457 avatar
== null ?
null : new File(avatar
),
460 if (results
!= null) {
461 checkSendMessageResults(results
.getTimestamp(), results
.getResults());
465 } catch (IOException e
) {
466 throw new Error
.Failure(e
.getMessage());
467 } catch (GroupNotFoundException
| NotAGroupMemberException
| GroupSendingNotAllowedException e
) {
468 throw new Error
.GroupNotFound(e
.getMessage());
469 } catch (AttachmentInvalidException e
) {
470 throw new Error
.AttachmentInvalid(e
.getMessage());
475 public boolean isRegistered() {
480 public boolean isRegistered(String number
) {
481 var result
= isRegistered(List
.of(number
));
482 return result
.get(0);
486 public List
<Boolean
> isRegistered(List
<String
> numbers
) {
487 var results
= new ArrayList
<Boolean
>();
488 if (numbers
.isEmpty()) {
492 Map
<String
, Pair
<String
, UUID
>> registered
;
494 registered
= m
.areUsersRegistered(new HashSet
<>(numbers
));
495 } catch (IOException e
) {
496 throw new Error
.Failure(e
.getMessage());
499 return numbers
.stream().map(number
-> {
500 var uuid
= registered
.get(number
).second();
502 }).collect(Collectors
.toList());
506 public void updateProfile(
512 final boolean removeAvatar
515 givenName
= nullIfEmpty(givenName
);
516 familyName
= nullIfEmpty(familyName
);
517 about
= nullIfEmpty(about
);
518 aboutEmoji
= nullIfEmpty(aboutEmoji
);
519 avatarPath
= nullIfEmpty(avatarPath
);
520 Optional
<File
> avatarFile
= removeAvatar
522 : avatarPath
== null ?
null : Optional
.of(new File(avatarPath
));
523 m
.setProfile(givenName
, familyName
, about
, aboutEmoji
, avatarFile
);
524 } catch (IOException e
) {
525 throw new Error
.Failure(e
.getMessage());
530 public void updateProfile(
533 final String aboutEmoji
,
535 final boolean removeAvatar
537 updateProfile(name
, "", about
, aboutEmoji
, avatarPath
, removeAvatar
);
541 public void removePin() {
543 m
.setRegistrationLockPin(Optional
.absent());
544 } catch (UnauthenticatedResponseException e
) {
545 throw new Error
.Failure("Remove pin failed with unauthenticated response: " + e
.getMessage());
546 } catch (IOException e
) {
547 throw new Error
.Failure("Remove pin error: " + e
.getMessage());
552 public void setPin(String registrationLockPin
) {
554 m
.setRegistrationLockPin(Optional
.of(registrationLockPin
));
555 } catch (UnauthenticatedResponseException e
) {
556 throw new Error
.Failure("Set pin error failed with unauthenticated response: " + e
.getMessage());
557 } catch (IOException e
) {
558 throw new Error
.Failure("Set pin error: " + e
.getMessage());
562 // Provide option to query a version string in order to react on potential
563 // future interface changes
565 public String
version() {
566 return BaseConfig
.PROJECT_VERSION
;
569 // Create a unique list of Numbers from Identities and Contacts to really get
570 // all numbers the system knows
572 public List
<String
> listNumbers() {
573 return Stream
.concat(m
.getIdentities().stream().map(Identity
::getRecipient
),
574 m
.getContacts().stream().map(Pair
::first
))
575 .map(a
-> a
.getNumber().orElse(null))
576 .filter(Objects
::nonNull
)
578 .collect(Collectors
.toList());
582 public List
<String
> getContactNumber(final String name
) {
583 // Contact names have precedence.
584 var numbers
= new ArrayList
<String
>();
585 var contacts
= m
.getContacts();
586 for (var c
: contacts
) {
587 if (name
.equals(c
.second().getName())) {
588 numbers
.add(c
.first().getLegacyIdentifier());
591 // Try profiles if no contact name was found
592 for (var identity
: m
.getIdentities()) {
593 final var address
= identity
.getRecipient();
594 var number
= address
.getNumber().orElse(null);
595 if (number
!= null) {
596 Profile profile
= null;
598 profile
= m
.getRecipientProfile(RecipientIdentifier
.Single
.fromAddress(address
));
599 } catch (UnregisteredUserException ignored
) {
601 if (profile
!= null && profile
.getDisplayName().equals(name
)) {
610 public void quitGroup(final byte[] groupId
) {
611 var group
= getGroupId(groupId
);
613 m
.quitGroup(group
, Set
.of());
614 } catch (GroupNotFoundException
| NotAGroupMemberException e
) {
615 throw new Error
.GroupNotFound(e
.getMessage());
616 } catch (IOException
| LastGroupAdminException e
) {
617 throw new Error
.Failure(e
.getMessage());
622 public byte[] joinGroup(final String groupLink
) {
624 final var linkUrl
= GroupInviteLinkUrl
.fromUri(groupLink
);
625 if (linkUrl
== null) {
626 throw new Error
.Failure("Group link is invalid:");
628 final var result
= m
.joinGroup(linkUrl
);
629 return result
.first().serialize();
630 } catch (GroupInviteLinkUrl
.InvalidGroupLinkException
| GroupLinkNotActiveException e
) {
631 throw new Error
.Failure("Group link is invalid: " + e
.getMessage());
632 } catch (GroupInviteLinkUrl
.UnknownGroupLinkVersionException e
) {
633 throw new Error
.Failure("Group link was created with an incompatible version: " + e
.getMessage());
634 } catch (IOException e
) {
635 throw new Error
.Failure(e
.getMessage());
640 public boolean isContactBlocked(final String number
) {
641 return m
.isContactBlocked(getSingleRecipientIdentifier(number
, m
.getSelfNumber()));
645 public boolean isGroupBlocked(final byte[] groupId
) {
646 var group
= m
.getGroup(getGroupId(groupId
));
650 return group
.isBlocked();
655 public boolean isMember(final byte[] groupId
) {
656 var group
= m
.getGroup(getGroupId(groupId
));
660 return group
.isMember();
665 public String
uploadStickerPack(String stickerPackPath
) {
666 File path
= new File(stickerPackPath
);
668 return m
.uploadStickerPack(path
).toString();
669 } catch (IOException e
) {
670 throw new Error
.Failure("Upload error (maybe image size is too large):" + e
.getMessage());
671 } catch (StickerPackInvalidException e
) {
672 throw new Error
.Failure("Invalid sticker pack: " + e
.getMessage());
676 private static void checkSendMessageResult(long timestamp
, SendMessageResult result
) throws DBusExecutionException
{
677 var error
= ErrorUtils
.getErrorMessageFromSendMessageResult(result
);
683 final var message
= timestamp
+ "\nFailed to send message:\n" + error
+ '\n';
685 if (result
.getIdentityFailure() != null) {
686 throw new Error
.UntrustedIdentity(message
);
688 throw new Error
.Failure(message
);
692 private static void checkSendMessageResults(
693 long timestamp
, Map
<RecipientIdentifier
, List
<SendMessageResult
>> results
694 ) throws DBusExecutionException
{
695 final var sendMessageResults
= results
.values().stream().findFirst();
696 if (results
.size() == 1 && sendMessageResults
.get().size() == 1) {
697 checkSendMessageResult(timestamp
, sendMessageResults
.get().stream().findFirst().get());
701 var errors
= ErrorUtils
.getErrorMessagesFromSendMessageResults(results
);
702 if (errors
.size() == 0) {
706 var message
= new StringBuilder();
707 message
.append(timestamp
).append('\n');
708 message
.append("Failed to send (some) messages:\n");
709 for (var error
: errors
) {
710 message
.append(error
).append('\n');
713 throw new Error
.Failure(message
.toString());
716 private static void checkSendMessageResults(
717 long timestamp
, Collection
<SendMessageResult
> results
718 ) throws DBusExecutionException
{
719 if (results
.size() == 1) {
720 checkSendMessageResult(timestamp
, results
.stream().findFirst().get());
724 var errors
= ErrorUtils
.getErrorMessagesFromSendMessageResults(results
);
725 if (errors
.size() == 0) {
729 var message
= new StringBuilder();
730 message
.append(timestamp
).append('\n');
731 message
.append("Failed to send (some) messages:\n");
732 for (var error
: errors
) {
733 message
.append(error
).append('\n');
736 throw new Error
.Failure(message
.toString());
739 private static Set
<RecipientIdentifier
.Single
> getSingleRecipientIdentifiers(
740 final Collection
<String
> recipientStrings
, final String localNumber
741 ) throws DBusExecutionException
{
742 final var identifiers
= new HashSet
<RecipientIdentifier
.Single
>();
743 for (var recipientString
: recipientStrings
) {
744 identifiers
.add(getSingleRecipientIdentifier(recipientString
, localNumber
));
749 private static RecipientIdentifier
.Single
getSingleRecipientIdentifier(
750 final String recipientString
, final String localNumber
751 ) throws DBusExecutionException
{
753 return RecipientIdentifier
.Single
.fromString(recipientString
, localNumber
);
754 } catch (InvalidNumberException e
) {
755 throw new Error
.InvalidNumber(e
.getMessage());
759 private static GroupId
getGroupId(byte[] groupId
) throws DBusExecutionException
{
761 return GroupId
.unknownVersion(groupId
);
762 } catch (Throwable e
) {
763 throw new Error
.InvalidGroupId("Invalid group id: " + e
.getMessage());
767 private byte[] nullIfEmpty(final byte[] array
) {
768 return array
.length
== 0 ?
null : array
;
771 private String
nullIfEmpty(final String name
) {
772 return name
.isEmpty() ?
null : name
;
775 private String
emptyIfNull(final String string
) {
776 return string
== null ?
"" : string
;
779 private static String
getDeviceObjectPath(String basePath
, long deviceId
) {
780 return basePath
+ "/Devices/" + deviceId
;
783 private void updateDevices() {
784 List
<org
.asamk
.signal
.manager
.api
.Device
> linkedDevices
;
786 linkedDevices
= m
.getLinkedDevices();
787 } catch (IOException e
) {
788 throw new Error
.Failure("Failed to get linked devices: " + e
.getMessage());
793 linkedDevices
.forEach(d
-> {
794 final var object
= new DbusSignalDeviceImpl(d
);
795 final var deviceObjectPath
= object
.getObjectPath();
797 connection
.exportObject(object
);
798 } catch (DBusException e
) {
801 if (d
.isThisDevice()) {
802 thisDevice
= new DBusPath(deviceObjectPath
);
804 this.devices
.add(new StructDevice(new DBusPath(deviceObjectPath
), d
.getId(), emptyIfNull(d
.getName())));
808 private void unExportDevices() {
809 this.devices
.stream()
810 .map(StructDevice
::getObjectPath
)
811 .map(DBusPath
::getPath
)
812 .forEach(connection
::unExportObject
);
813 this.devices
.clear();
816 public class DbusSignalDeviceImpl
extends DbusProperties
implements Signal
.Device
{
818 private final org
.asamk
.signal
.manager
.api
.Device device
;
820 public DbusSignalDeviceImpl(final org
.asamk
.signal
.manager
.api
.Device device
) {
821 super.addPropertiesHandler(new DbusInterfacePropertiesHandler("org.asamk.Signal.Device",
822 List
.of(new DbusProperty
<>("Id", device
::getId
),
823 new DbusProperty
<>("Name", () -> emptyIfNull(device
.getName()), this::setDeviceName
),
824 new DbusProperty
<>("Created", device
::getCreated
),
825 new DbusProperty
<>("LastSeen", device
::getLastSeen
))));
826 this.device
= device
;
830 public String
getObjectPath() {
831 return getDeviceObjectPath(objectPath
, device
.getId());
835 public void removeDevice() throws Error
.Failure
{
837 m
.removeLinkedDevices(device
.getId());
839 } catch (IOException e
) {
840 throw new Error
.Failure(e
.getMessage());
844 private void setDeviceName(String name
) {
845 if (!device
.isThisDevice()) {
846 throw new Error
.Failure("Only the name of this device can be changed");
849 m
.updateAccountAttributes(name
);
850 // update device list
852 } catch (IOException e
) {
853 throw new Error
.Failure(e
.getMessage());