diff --git a/build.gradle b/build.gradle index dd4709a03..67ac948db 100644 --- a/build.gradle +++ b/build.gradle @@ -173,7 +173,6 @@ dependencies { implementation 'com.google.zxing:core:3.3.0' // fixed version to support SDK<24 implementation ('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } // QR Code scanner implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.1' // used as JSON library - implementation 'com.google.code.gson:gson:2.12.1' // used as JSON library. implementation 'com.github.Baseflow:PhotoView:2.3.0' // does the zooming on photos / media implementation 'com.github.penfeizhou.android.animation:awebp:3.0.5' // animated webp support. implementation 'com.caverock:androidsvg-aar:1.4' // SVG support. diff --git a/src/main/java/chat/delta/rpc/BaseTransport.java b/src/main/java/chat/delta/rpc/BaseTransport.java new file mode 100644 index 000000000..971bb130f --- /dev/null +++ b/src/main/java/chat/delta/rpc/BaseTransport.java @@ -0,0 +1,124 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc; + +import chat.delta.util.SettableFuture; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; + +/* Basic RPC Transport implementation */ +public abstract class BaseTransport implements Rpc.Transport { + private final Map> requestFutures = new ConcurrentHashMap<>(); + private int requestId = 0; + private final ObjectMapper mapper = new ObjectMapper(); + private Thread worker; + + /* Send a Request as raw JSON String to the RPC server */ + protected abstract void sendRequest(String jsonRequest); + + /* Get next Response as raw JSON String from the RPC server */ + protected abstract String getResponse(); + + public ObjectMapper getObjectMapper() { + return mapper; + } + + public void call(String method, JsonNode... params) throws RpcException { + innerCall(method, params); + } + + public T callForResult(TypeReference resultType, String method, JsonNode... params) throws RpcException { + try { + JsonNode node = innerCall(method, params); + if (node.isNull()) return null; + return mapper.readValue(node.traverse(), resultType); + } catch (IOException e) { + throw new RpcException(e.getMessage()); + } + } + + private JsonNode innerCall(String method, JsonNode... params) throws RpcException { + int id; + synchronized (this) { + id = ++requestId; + ensureWorkerThread(); + } + try { + String jsonRequest = mapper.writeValueAsString(new Request(method, params, id)); + SettableFuture future = new SettableFuture<>(); + requestFutures.put(id, future); + sendRequest(jsonRequest); + return future.get(); + } catch (ExecutionException e) { + throw (RpcException)e.getCause(); + } catch (InterruptedException e) { + throw new RpcException(e.getMessage()); + } catch (JsonProcessingException e) { + throw new RpcException(e.getMessage()); + } + } + + private void ensureWorkerThread() { + if (worker != null) return; + + worker = new Thread(() -> { + while (true) { + try { + processResponse(); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + }, "jsonrpcThread"); + worker.start(); + } + + private void processResponse() throws JsonProcessingException { + String jsonResponse = getResponse(); + Response response = mapper.readValue(jsonResponse, Response.class); + + if (response.id == 0) { // Got JSON-RPC notification/event, ignore + return; + } + + SettableFuture future = requestFutures.remove(response.id); + if (future == null) { // Got a response with unknown ID, ignore + return; + } + + if (response.error != null) { + future.setException(new RpcException(response.error.toString())); + } else if (response.result != null) { + future.set(response.result); + } else { + future.setException(new RpcException("Got JSON-RPC response without result or error: " + jsonResponse)); + } + } + + private static class Request { + private final String jsonrpc = "2.0"; + public final String method; + public final JsonNode[] params; + public final int id; + + public Request(String method, JsonNode[] params, int id) { + this.method = method; + this.params = params; + this.id = id; + } + } + + private static class Response { + public String jsonrpc; + public int id; + public JsonNode result; + public JsonNode error; + } +} \ No newline at end of file diff --git a/src/main/java/chat/delta/rpc/Rpc.java b/src/main/java/chat/delta/rpc/Rpc.java new file mode 100644 index 000000000..f157b8d22 --- /dev/null +++ b/src/main/java/chat/delta/rpc/Rpc.java @@ -0,0 +1,249 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import chat.delta.rpc.types.*; + +public class Rpc { + + public interface Transport { + void call(String method, JsonNode... params) throws RpcException; + T callForResult(TypeReference resultType, String method, JsonNode... params) throws RpcException; + ObjectMapper getObjectMapper(); + } + + public final Transport transport; + private final ObjectMapper mapper; + + public Rpc(Transport transport) { + this.transport = transport; + this.mapper = transport.getObjectMapper(); + } + + public Integer addAccount() throws RpcException { + return transport.callForResult(new TypeReference(){}, "add_account"); + } + + /** + * Set the order of accounts. + * The provided list should contain all account IDs in the desired order. + * If an account ID is missing from the list, it will be appended at the end. + * If the list contains non-existent account IDs, they will be ignored. + */ + public void setAccountsOrder(java.util.List order) throws RpcException { + transport.call("set_accounts_order", mapper.valueToTree(order)); + } + + /* Get the combined filesize of an account in bytes */ + public Integer getAccountFileSize(Integer accountId) throws RpcException { + return transport.callForResult(new TypeReference(){}, "get_account_file_size", mapper.valueToTree(accountId)); + } + + /** + * If there was an error while the account was opened + * and migrated to the current version, + * then this function returns it. + *

+ * This function is useful because the key-contacts migration could fail due to bugs + * and then the account will not work properly. + *

+ * After opening an account, the UI should call this function + * and show the error string if one is returned. + */ + public String getMigrationError(Integer accountId) throws RpcException { + return transport.callForResult(new TypeReference(){}, "get_migration_error", mapper.valueToTree(accountId)); + } + + public Integer draftSelfReport(Integer accountId) throws RpcException { + return transport.callForResult(new TypeReference(){}, "draft_self_report", mapper.valueToTree(accountId)); + } + + /* Returns configuration value for the given key. */ + public String getConfig(Integer accountId, String key) throws RpcException { + return transport.callForResult(new TypeReference(){}, "get_config", mapper.valueToTree(accountId), mapper.valueToTree(key)); + } + + /** + * Configures a new email account using the provided parameters + * and adds it as a transport. + *

+ * If the email address is the same as an existing transport, + * then this existing account will be reconfigured instead of a new one being added. + *

+ * This function stops and starts IO as needed. + *

+ * Usually it will be enough to only set `addr` and `password`, + * and all the other settings will be autoconfigured. + *

+ * During configuration, ConfigureProgress events are emitted; + * they indicate a successful configuration as well as errors + * and may be used to create a progress bar. + * This function will return after configuration is finished. + *

+ * If configuration is successful, + * the working server parameters will be saved + * and used for connecting to the server. + * The parameters entered by the user will be saved separately + * so that they can be prefilled when the user opens the server-configuration screen again. + *

+ * See also: + * - [Self::is_configured()] to check whether there is + * at least one working transport. + * - [Self::add_transport_from_qr()] to add a transport + * from a server encoded in a QR code. + * - [Self::list_transports()] to get a list of all configured transports. + * - [Self::delete_transport()] to remove a transport. + */ + public void addOrUpdateTransport(Integer accountId, EnteredLoginParam param) throws RpcException { + transport.call("add_or_update_transport", mapper.valueToTree(accountId), mapper.valueToTree(param)); + } + + /** + * Adds a new email account as a transport + * using the server encoded in the QR code. + * See [Self::add_or_update_transport]. + */ + public void addTransportFromQr(Integer accountId, String qr) throws RpcException { + transport.call("add_transport_from_qr", mapper.valueToTree(accountId), mapper.valueToTree(qr)); + } + + /** + * Create a new unencrypted group chat. + *

+ * Same as [`Self::create_group_chat`], but the chat is unencrypted and can only have + * address-contacts. + */ + public Integer createGroupChatUnencrypted(Integer accountId, String name) throws RpcException { + return transport.callForResult(new TypeReference(){}, "create_group_chat_unencrypted", mapper.valueToTree(accountId), mapper.valueToTree(name)); + } + + /** + * Create a new **broadcast channel** + * (called "Channel" in the UI). + *

+ * Broadcast channels are similar to groups on the sending device, + * however, recipients get the messages in a read-only chat + * and will not see who the other members are. + *

+ * Called `broadcast` here rather than `channel`, + * because the word "channel" already appears a lot in the code, + * which would make it hard to grep for it. + *

+ * After creation, the chat contains no recipients and is in _unpromoted_ state; + * see [`CommandApi::create_group_chat`] for more information on the unpromoted state. + *

+ * Returns the created chat's id. + */ + public Integer createBroadcast(Integer accountId, String chatName) throws RpcException { + return transport.callForResult(new TypeReference(){}, "create_broadcast", mapper.valueToTree(accountId), mapper.valueToTree(chatName)); + } + + /* Returns contact id of the created or existing DM chat with that contact */ + public Integer createChatByContactId(Integer accountId, Integer contactId) throws RpcException { + return transport.callForResult(new TypeReference(){}, "create_chat_by_contact_id", mapper.valueToTree(accountId), mapper.valueToTree(contactId)); + } + + /* Sets display name for existing contact. */ + public void changeContactName(Integer accountId, Integer contactId, String name) throws RpcException { + transport.call("change_contact_name", mapper.valueToTree(accountId), mapper.valueToTree(contactId), mapper.valueToTree(name)); + } + + + /* Parses a vCard file located at the given path. Returns contacts in their original order. */ + public java.util.List parseVcard(String path) throws RpcException { + return transport.callForResult(new TypeReference>(){}, "parse_vcard", mapper.valueToTree(path)); + } + + /** + * Imports contacts from a vCard file located at the given path. + *

+ * Returns the ids of created/modified contacts in the order they appear in the vCard. + */ + public java.util.List importVcard(Integer accountId, String path) throws RpcException { + return transport.callForResult(new TypeReference>(){}, "import_vcard", mapper.valueToTree(accountId), mapper.valueToTree(path)); + } + + /* Returns a vCard containing contacts with the given ids. */ + public String makeVcard(Integer accountId, java.util.List contacts) throws RpcException { + return transport.callForResult(new TypeReference(){}, "make_vcard", mapper.valueToTree(accountId), mapper.valueToTree(contacts)); + } + + public void sendWebxdcRealtimeData(Integer accountId, Integer instanceMsgId, java.util.List data) throws RpcException { + transport.call("send_webxdc_realtime_data", mapper.valueToTree(accountId), mapper.valueToTree(instanceMsgId), mapper.valueToTree(data)); + } + + public void sendWebxdcRealtimeAdvertisement(Integer accountId, Integer instanceMsgId) throws RpcException { + transport.call("send_webxdc_realtime_advertisement", mapper.valueToTree(accountId), mapper.valueToTree(instanceMsgId)); + } + + /** + * Leaves the gossip of the webxdc with the given message id. + *

+ * NB: When this is called before closing a webxdc app in UIs, it must be guaranteed that + * `send_webxdc_realtime_*()` functions aren't called for the given `instance_message_id` + * anymore until the app is open again. + */ + public void leaveWebxdcRealtime(Integer accountId, Integer instanceMessageId) throws RpcException { + transport.call("leave_webxdc_realtime", mapper.valueToTree(accountId), mapper.valueToTree(instanceMessageId)); + } + + /* Starts an outgoing call. */ + public Integer placeOutgoingCall(Integer accountId, Integer chatId, String placeCallInfo) throws RpcException { + return transport.callForResult(new TypeReference(){}, "place_outgoing_call", mapper.valueToTree(accountId), mapper.valueToTree(chatId), mapper.valueToTree(placeCallInfo)); + } + + /* Accepts an incoming call. */ + public void acceptIncomingCall(Integer accountId, Integer msgId, String acceptCallInfo) throws RpcException { + transport.call("accept_incoming_call", mapper.valueToTree(accountId), mapper.valueToTree(msgId), mapper.valueToTree(acceptCallInfo)); + } + + /* Ends incoming or outgoing call. */ + public void endCall(Integer accountId, Integer msgId) throws RpcException { + transport.call("end_call", mapper.valueToTree(accountId), mapper.valueToTree(msgId)); + } + + /* Returns information about the call. */ + public CallInfo callInfo(Integer accountId, Integer msgId) throws RpcException { + return transport.callForResult(new TypeReference(){}, "call_info", mapper.valueToTree(accountId), mapper.valueToTree(msgId)); + } + + /* Returns JSON with ICE servers, to be used for WebRTC video calls. */ + public String iceServers(Integer accountId) throws RpcException { + return transport.callForResult(new TypeReference(){}, "ice_servers", mapper.valueToTree(accountId)); + } + + /** + * Makes an HTTP GET request and returns a response. + *

+ * `url` is the HTTP or HTTPS URL. + */ + public HttpResponse getHttpResponse(Integer accountId, String url) throws RpcException { + return transport.callForResult(new TypeReference(){}, "get_http_response", mapper.valueToTree(accountId), mapper.valueToTree(url)); + } + + /** + * Send a reaction to message. + *

+ * Reaction is a string of emojis separated by spaces. Reaction to a + * single message can be sent multiple times. The last reaction + * received overrides all previously received reactions. It is + * possible to remove all reactions by sending an empty string. + */ + public Integer sendReaction(Integer accountId, Integer messageId, java.util.List reaction) throws RpcException { + return transport.callForResult(new TypeReference(){}, "send_reaction", mapper.valueToTree(accountId), mapper.valueToTree(messageId), mapper.valueToTree(reaction)); + } + + /* Returns reactions to the message. */ + public Reactions getMessageReactions(Integer accountId, Integer messageId) throws RpcException { + return transport.callForResult(new TypeReference(){}, "get_message_reactions", mapper.valueToTree(accountId), mapper.valueToTree(messageId)); + } + + /* Checks if messages can be sent to a given chat. */ + public Boolean canSend(Integer accountId, Integer chatId) throws RpcException { + return transport.callForResult(new TypeReference(){}, "can_send", mapper.valueToTree(accountId), mapper.valueToTree(chatId)); + } + +} diff --git a/src/main/java/chat/delta/rpc/RpcException.java b/src/main/java/chat/delta/rpc/RpcException.java new file mode 100644 index 000000000..6d165e387 --- /dev/null +++ b/src/main/java/chat/delta/rpc/RpcException.java @@ -0,0 +1,8 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc; + +public class RpcException extends Exception { + + public RpcException(String message) { super(message); } + +} \ No newline at end of file diff --git a/src/main/java/chat/delta/rpc/types/CallInfo.java b/src/main/java/chat/delta/rpc/types/CallInfo.java new file mode 100644 index 000000000..9d8c59f72 --- /dev/null +++ b/src/main/java/chat/delta/rpc/types/CallInfo.java @@ -0,0 +1,17 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc.types; + +public class CallInfo { + /** + * SDP offer. + *

+ * Can be used to manually answer the call even if incoming call event was missed. + */ + public String sdpOffer; + /** + * Call state. + *

+ * For example, if the call is accepted, active, cancelled, declined etc. + */ + public CallState state; +} \ No newline at end of file diff --git a/src/main/java/chat/delta/rpc/types/CallState.java b/src/main/java/chat/delta/rpc/types/CallState.java new file mode 100644 index 000000000..83333f11f --- /dev/null +++ b/src/main/java/chat/delta/rpc/types/CallState.java @@ -0,0 +1,48 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc.types; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; +import com.fasterxml.jackson.annotation.JsonTypeInfo.As; + +@JsonTypeInfo(use=Id.MINIMAL_CLASS, include=As.PROPERTY, property="kind") +@JsonSubTypes({@Type(CallState.Alerting.class), @Type(CallState.Active.class), @Type(CallState.Completed.class), @Type(CallState.Missed.class), @Type(CallState.Declined.class), @Type(CallState.Cancelled.class)}) +public abstract class CallState { + +/** + * Fresh incoming or outgoing call that is still ringing. + *

+ * There is no separate state for outgoing call that has been dialled but not ringing on the other side yet as we don't know whether the other side received our call. + */ + public static class Alerting extends CallState { + } + +/* Active call. */ + public static class Active extends CallState { + } + +/* Completed call that was once active and then was terminated for any reason. */ + public static class Completed extends CallState { + /* Call duration in seconds. */ + public Integer duration; + } + +/* Incoming call that was not picked up within a timeout or was explicitly ended by the caller before we picked up. */ + public static class Missed extends CallState { + } + +/* Incoming call that was explicitly ended on our side before picking up or outgoing call that was declined before the timeout. */ + public static class Declined extends CallState { + } + +/** + * Outgoing call that has been cancelled on our side before receiving a response. + *

+ * Incoming calls cannot be cancelled, on the receiver side cancelled calls usually result in missed calls. + */ + public static class Cancelled extends CallState { + } + +} \ No newline at end of file diff --git a/src/main/java/chat/delta/rpc/types/EnteredCertificateChecks.java b/src/main/java/chat/delta/rpc/types/EnteredCertificateChecks.java new file mode 100644 index 000000000..77c8f9f85 --- /dev/null +++ b/src/main/java/chat/delta/rpc/types/EnteredCertificateChecks.java @@ -0,0 +1,13 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc.types; + +public enum EnteredCertificateChecks { + /* `Automatic` means that provider database setting should be taken. If there is no provider database setting for certificate checks, check certificates strictly. */ + automatic, + + /* Ensure that TLS certificate is valid for the server hostname. */ + strict, + + /* Accept certificates that are expired, self-signed or otherwise not valid for the server hostname. */ + acceptInvalidCertificates, +} \ No newline at end of file diff --git a/src/main/java/chat/delta/rpc/types/EnteredLoginParam.java b/src/main/java/chat/delta/rpc/types/EnteredLoginParam.java new file mode 100644 index 000000000..87c1c9427 --- /dev/null +++ b/src/main/java/chat/delta/rpc/types/EnteredLoginParam.java @@ -0,0 +1,51 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc.types; + +/** + * Login parameters entered by the user. + *

+ * Usually it will be enough to only set `addr` and `password`, and all the other settings will be autoconfigured. + */ +public class EnteredLoginParam { + /* Email address. */ + public String addr; + /* TLS options: whether to allow invalid certificates and/or invalid hostnames. Default: Automatic */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public EnteredCertificateChecks certificateChecks; + /* Imap server port. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public Integer imapPort; + /* Imap socket security. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public Socket imapSecurity; + /* Imap server hostname or IP address. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public String imapServer; + /* Imap username. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public String imapUser; + /* If true, login via OAUTH2 (not recommended anymore). Default: false */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public Boolean oauth2; + /* Password. */ + public String password; + /** + * SMTP Password. + *

+ * Only needs to be specified if different than IMAP password. + */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public String smtpPassword; + /* SMTP server port. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public Integer smtpPort; + /* SMTP socket security. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public Socket smtpSecurity; + /* SMTP server hostname or IP address. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public String smtpServer; + /* SMTP username. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public String smtpUser; +} \ No newline at end of file diff --git a/src/main/java/chat/delta/rpc/types/HttpResponse.java b/src/main/java/chat/delta/rpc/types/HttpResponse.java new file mode 100644 index 000000000..8ad3c6438 --- /dev/null +++ b/src/main/java/chat/delta/rpc/types/HttpResponse.java @@ -0,0 +1,13 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc.types; + +public class HttpResponse { + /* base64-encoded response body. */ + public String blob; + /* Encoding, e.g. "utf-8". */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public String encoding; + /* MIME type, e.g. "text/plain" or "text/html". */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public String mimetype; +} \ No newline at end of file diff --git a/src/main/java/chat/delta/rpc/types/Reaction.java b/src/main/java/chat/delta/rpc/types/Reaction.java new file mode 100644 index 000000000..3ac86d5b7 --- /dev/null +++ b/src/main/java/chat/delta/rpc/types/Reaction.java @@ -0,0 +1,12 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc.types; + +/* A single reaction emoji. */ +public class Reaction { + /* Emoji frequency. */ + public Integer count; + /* Emoji. */ + public String emoji; + /* True if we reacted with this emoji. */ + public Boolean isFromSelf; +} \ No newline at end of file diff --git a/src/main/java/chat/delta/rpc/types/Reactions.java b/src/main/java/chat/delta/rpc/types/Reactions.java new file mode 100644 index 000000000..a7daf1155 --- /dev/null +++ b/src/main/java/chat/delta/rpc/types/Reactions.java @@ -0,0 +1,10 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc.types; + +/* Structure representing all reactions to a particular message. */ +public class Reactions { + /* Unique reactions and their count, sorted in descending order. */ + public java.util.List reactions; + /* Map from a contact to it's reaction to message. */ + public java.util.Map> reactionsByContact; +} \ No newline at end of file diff --git a/src/main/java/chat/delta/rpc/types/Socket.java b/src/main/java/chat/delta/rpc/types/Socket.java new file mode 100644 index 000000000..1dccf6935 --- /dev/null +++ b/src/main/java/chat/delta/rpc/types/Socket.java @@ -0,0 +1,16 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc.types; + +public enum Socket { + /* Unspecified socket security, select automatically. */ + automatic, + + /* TLS connection. */ + ssl, + + /* STARTTLS connection. */ + starttls, + + /* No TLS, plaintext connection. */ + plain, +} \ No newline at end of file diff --git a/src/main/java/chat/delta/rpc/types/VcardContact.java b/src/main/java/chat/delta/rpc/types/VcardContact.java new file mode 100644 index 000000000..f028f3357 --- /dev/null +++ b/src/main/java/chat/delta/rpc/types/VcardContact.java @@ -0,0 +1,20 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.rpc.types; + +public class VcardContact { + /* Email address. */ + public String addr; + /* Contact color as hex string. */ + public String color; + /* The contact's name, or the email address if no name was given. */ + public String displayName; + /* Public PGP key in Base64. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public String key; + /* Profile image in Base64. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public String profileImage; + /* Last update timestamp. */ + @com.fasterxml.jackson.annotation.JsonSetter(nulls = com.fasterxml.jackson.annotation.Nulls.SET) + public Integer timestamp; +} \ No newline at end of file diff --git a/src/main/java/chat/delta/util/ListenableFuture.java b/src/main/java/chat/delta/util/ListenableFuture.java new file mode 100644 index 000000000..d511ec729 --- /dev/null +++ b/src/main/java/chat/delta/util/ListenableFuture.java @@ -0,0 +1,14 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.util; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public interface ListenableFuture extends Future { + void addListener(Listener listener); + + public interface Listener { + public void onSuccess(T result); + public void onFailure(ExecutionException e); + } +} \ No newline at end of file diff --git a/src/main/java/chat/delta/util/SettableFuture.java b/src/main/java/chat/delta/util/SettableFuture.java new file mode 100644 index 000000000..0a14c4876 --- /dev/null +++ b/src/main/java/chat/delta/util/SettableFuture.java @@ -0,0 +1,137 @@ +/* Autogenerated file, do not edit manually */ +package chat.delta.util; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class SettableFuture implements ListenableFuture { + + private final List> listeners = new LinkedList<>(); + + private boolean completed; + private boolean canceled; + private volatile T result; + private volatile Throwable exception; + + public SettableFuture() { } + + public SettableFuture(T value) { + this.result = value; + this.completed = true; + } + + @Override + public synchronized boolean cancel(boolean mayInterruptIfRunning) { + if (!completed && !canceled) { + canceled = true; + return true; + } + + return false; + } + + @Override + public synchronized boolean isCancelled() { + return canceled; + } + + @Override + public synchronized boolean isDone() { + return completed; + } + + public boolean set(T result) { + synchronized (this) { + if (completed || canceled) return false; + + this.result = result; + this.completed = true; + + notifyAll(); + } + + notifyAllListeners(); + return true; + } + + public boolean setException(Throwable throwable) { + synchronized (this) { + if (completed || canceled) return false; + + this.exception = throwable; + this.completed = true; + + notifyAll(); + } + + notifyAllListeners(); + return true; + } + + public void deferTo(ListenableFuture other) { + other.addListener(new Listener() { + @Override + public void onSuccess(T result) { + SettableFuture.this.set(result); + } + + @Override + public void onFailure(ExecutionException e) { + SettableFuture.this.setException(e.getCause()); + } + }); + } + + @Override + public synchronized T get() throws InterruptedException, ExecutionException { + while (!completed) wait(); + + if (exception != null) throw new ExecutionException(exception); + else return result; + } + + @Override + public synchronized T get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException + { + long startTime = System.currentTimeMillis(); + + while (!completed && System.currentTimeMillis() - startTime > unit.toMillis(timeout)) { + wait(unit.toMillis(timeout)); + } + + if (!completed) throw new TimeoutException(); + else return get(); + } + + @Override + public void addListener(Listener listener) { + synchronized (this) { + listeners.add(listener); + + if (!completed) return; + } + + notifyListener(listener); + } + + private void notifyAllListeners() { + List> localListeners; + + synchronized (this) { + localListeners = new LinkedList<>(listeners); + } + + for (Listener listener : localListeners) { + notifyListener(listener); + } + } + + private void notifyListener(Listener listener) { + if (exception != null) listener.onFailure(new ExecutionException(exception)); + else listener.onSuccess(result); + } +} \ No newline at end of file diff --git a/src/main/java/com/b44t/messenger/FFITransport.java b/src/main/java/com/b44t/messenger/FFITransport.java new file mode 100644 index 000000000..401048c52 --- /dev/null +++ b/src/main/java/com/b44t/messenger/FFITransport.java @@ -0,0 +1,22 @@ +package com.b44t.messenger; + +import chat.delta.rpc.BaseTransport; + +/* RPC transport over C FFI */ +public class FFITransport extends BaseTransport { + private final DcJsonrpcInstance dcJsonrpcInstance; + + public FFITransport(DcJsonrpcInstance dcJsonrpcInstance) { + this.dcJsonrpcInstance = dcJsonrpcInstance; + } + + @Override + protected void sendRequest(String jsonRequest) { + dcJsonrpcInstance.request(jsonRequest); + } + + @Override + protected String getResponse() { + return dcJsonrpcInstance.getNextResponse(); + } +} \ No newline at end of file diff --git a/src/main/java/com/b44t/messenger/rpc/EnteredLoginParam.java b/src/main/java/com/b44t/messenger/rpc/EnteredLoginParam.java deleted file mode 100644 index 9c65486d5..000000000 --- a/src/main/java/com/b44t/messenger/rpc/EnteredLoginParam.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.b44t.messenger.rpc; - -public class EnteredLoginParam { - // Email address. - private final String addr; - - // Password. - private final String password; - - // ============ IMAP settings ============ - - // Server hostname or IP address. - private final String imapServer; - - // Server port. - private final int imapPort; - - // Socket security. - private final SocketSecurity imapSecurity; - - // Username. - private final String imapUser; - - // ============ SMTP settings ============ - - // Server hostname or IP address. - private final String smtpServer; - - // Server port. - private final int smtpPort; - - // Socket security. - private final SocketSecurity smtpSecurity; - - // Username. - private final String smtpUser; - - // SMTP Password. Only needs to be specified if different than IMAP password. - private final String smtpPassword; - - // TLS options: whether to allow invalid certificates and/or - // invalid hostnames - private final EnteredCertificateChecks certificateChecks; - - // If true, login via OAUTH2 (not recommended anymore) - private final boolean oauth2; - - public EnteredLoginParam(String addr, - String password, - String imapServer, - int imapPort, - SocketSecurity imapSecurity, - String imapUser, - String smtpServer, - int smtpPort, - SocketSecurity smtpSecurity, - String smtpUser, - String smtpPassword, - EnteredCertificateChecks certificateChecks, - boolean oauth2) { - this.addr = addr; - this.password = password; - this.imapServer = imapServer; - this.imapPort = imapPort; - this.imapSecurity = imapSecurity; - this.imapUser = imapUser; - this.smtpServer = smtpServer; - this.smtpPort = smtpPort; - this.smtpSecurity = smtpSecurity; - this.smtpUser = smtpUser; - this.smtpPassword = smtpPassword; - this.certificateChecks = certificateChecks; - this.oauth2 = oauth2; - } - - public String getAddr() { - return addr; - } - - public String getPassword() { - return password; - } - - public String getImapServer() { - return imapServer; - } - - public int getImapPort() { - return imapPort; - } - - public SocketSecurity getImapSecurity() { - return imapSecurity; - } - - public String getImapUser() { - return imapUser; - } - - public String getSmtpServer() { - return smtpServer; - } - - public int getSmtpPort() { - return smtpPort; - } - - public SocketSecurity getSmtpSecurity() { - return smtpSecurity; - } - - public String getSmtpUser() { - return smtpUser; - } - - public String getSmtpPassword() { - return smtpPassword; - } - - public EnteredCertificateChecks getCertificateChecks() { - return certificateChecks; - } - - public boolean isOauth2() { - return oauth2; - } - - public enum EnteredCertificateChecks { - automatic, strict, acceptInvalidCertificates, - } - - public static EnteredCertificateChecks certificateChecksFromInt(int position) { - switch (position) { - case 0: - return EnteredCertificateChecks.automatic; - case 1: - return EnteredCertificateChecks.strict; - case 2: - return EnteredCertificateChecks.acceptInvalidCertificates; - } - throw new IllegalArgumentException("Invalid certificate position: " + position); - } - - public enum SocketSecurity { - // Unspecified socket security, select automatically. - automatic, - - // TLS connection. - ssl, - - // STARTTLS connection. - starttls, - - // No TLS, plaintext connection. - plain, - } - - public static SocketSecurity socketSecurityFromInt(int position) { - switch (position) { - case 0: - return SocketSecurity.automatic; - case 1: - return SocketSecurity.ssl; - case 2: - return SocketSecurity.starttls; - case 3: - return SocketSecurity.plain; - } - throw new IllegalArgumentException("Invalid socketSecurity position: " + position); - } -} diff --git a/src/main/java/com/b44t/messenger/rpc/HttpResponse.java b/src/main/java/com/b44t/messenger/rpc/HttpResponse.java deleted file mode 100644 index ccc6b8c6b..000000000 --- a/src/main/java/com/b44t/messenger/rpc/HttpResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.b44t.messenger.rpc; - -import android.util.Base64; - -public class HttpResponse { - // base64-encoded response body. - private final String blob; - // MIME type, e.g. "text/plain" or "text/html". - private final String mimetype; - // Encoding, e.g. "utf-8". - private final String encoding; - - public HttpResponse(String blob, String mimetype, String encoding) { - this.blob = blob; - this.mimetype = mimetype; - this.encoding = encoding; - } - - public byte[] getBlob() { - if (blob == null) { - return null; - } - return Base64.decode(blob, Base64.NO_WRAP | Base64.NO_PADDING); - } - - public String getMimetype() { - return mimetype; - } - - public String getEncoding() { - return encoding; - } -} diff --git a/src/main/java/com/b44t/messenger/rpc/Reaction.java b/src/main/java/com/b44t/messenger/rpc/Reaction.java deleted file mode 100644 index 493cb4ec5..000000000 --- a/src/main/java/com/b44t/messenger/rpc/Reaction.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.b44t.messenger.rpc; - -import androidx.annotation.Nullable; - -public class Reaction { - // The reaction emoji string. - private final String emoji; - // The count of users that have reacted with this reaction. - private final int count; - // true if self-account reacted with this reaction, false otherwise. - private final boolean isFromSelf; - - public Reaction(String emoji, int count, boolean isFromSelf) { - this.emoji = emoji; - this.count = count; - this.isFromSelf = isFromSelf; - } - - public String getEmoji() { - return emoji; - } - - public int getCount() { - return count; - } - - public boolean isFromSelf() { - return isFromSelf; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof Reaction) { - Reaction reaction = (Reaction) obj; - return emoji.equals(reaction.getEmoji()) && count == reaction.getCount() && isFromSelf == reaction.isFromSelf(); - } - return false; - } -} diff --git a/src/main/java/com/b44t/messenger/rpc/Reactions.java b/src/main/java/com/b44t/messenger/rpc/Reactions.java deleted file mode 100644 index 7b8849868..000000000 --- a/src/main/java/com/b44t/messenger/rpc/Reactions.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.b44t.messenger.rpc; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class Reactions { - // Map from a contact to it's reaction to message. - private final HashMap reactionsByContact; - // Unique reactions, sorted in descending order. - private final ArrayList reactions; - - public Reactions(HashMap reactionsByContact, ArrayList reactions) { - this.reactionsByContact = reactionsByContact; - this.reactions = reactions; - } - - public Map getReactionsByContact() { - return reactionsByContact; - } - - public List getReactions() { - return reactions; - } -} diff --git a/src/main/java/com/b44t/messenger/rpc/Rpc.java b/src/main/java/com/b44t/messenger/rpc/Rpc.java deleted file mode 100644 index 12fa326b2..000000000 --- a/src/main/java/com/b44t/messenger/rpc/Rpc.java +++ /dev/null @@ -1,203 +0,0 @@ -package com.b44t.messenger.rpc; - -import android.util.Log; - -import com.b44t.messenger.DcJsonrpcInstance; -import com.b44t.messenger.util.concurrent.SettableFuture; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; - -public class Rpc { - private final static String TAG = Rpc.class.getSimpleName(); - - private final Map> requestFutures = new ConcurrentHashMap<>(); - private final DcJsonrpcInstance dcJsonrpcInstance; - private int requestId = 0; - private boolean started = false; - private final Gson gson = new GsonBuilder().serializeNulls().create(); - - public Rpc(DcJsonrpcInstance dcJsonrpcInstance) { - this.dcJsonrpcInstance = dcJsonrpcInstance; - } - - private void processResponse() throws JsonSyntaxException { - String jsonResponse = dcJsonrpcInstance.getNextResponse(); - - Response response = gson.fromJson(jsonResponse, Response.class); - if (response == null) { - Log.e(TAG, "Error parsing JSON: " + jsonResponse); - return; - } else if (response.id == 0) { - // Got JSON-RPC notification/event, ignore - return; - } - - SettableFuture future = requestFutures.remove(response.id); - if (future == null) { // Got a response with unknown ID, ignore - return; - } - - if (response.error != null) { - String message; - try { - message = response.error.getAsJsonObject().get("message").getAsString(); - } catch (Exception e) { - Log.e(TAG, "Can't get response error message: " + e); - message = response.error.toString(); - } - future.setException(new RpcException(message)); - } else if (response.result != null) { - future.set(response.result); - } else { - future.setException(new RpcException("Got JSON-RPC response without result or error: " + jsonResponse)); - } - } - - public void start() { - started = true; - new Thread(() -> { - while (true) { - try { - processResponse(); - } catch (Exception e) { - e.printStackTrace(); - } - } - }, "jsonrpcThread").start(); - } - - public SettableFuture call(String method, Object... params) throws RpcException { - if (!started) throw new RpcException("RPC not started yet."); - - int id; - synchronized (this) { - id = ++requestId; - } - String jsonRequest = gson.toJson(new Request(method, params, id)); - SettableFuture future = new SettableFuture<>(); - requestFutures.put(id, future); - dcJsonrpcInstance.request(jsonRequest); - return future; - } - - public JsonElement getResult(String method, Object... params) throws RpcException { - try { - return call(method, params).get(); - } catch (ExecutionException e) { - throw (RpcException)e.getCause(); - } catch (InterruptedException e) { - throw new RpcException(e.getMessage()); - } - } - - public List parseVcard(String path) throws RpcException { - TypeToken> listType = new TypeToken>(){}; - return gson.fromJson(getResult("parse_vcard", path), listType.getType()); - } - - public String makeVcard(int accountId, int... contacts) throws RpcException { - return gson.fromJson(getResult("make_vcard", accountId, contacts), String.class); - } - - public List importVcard(int accountId, String path) throws RpcException { - TypeToken> listType = new TypeToken>(){}; - return gson.fromJson(getResult("import_vcard", accountId, path), listType.getType()); - } - - public HttpResponse getHttpResponse(int accountId, String url) throws RpcException { - return gson.fromJson(getResult("get_http_response", accountId, url), HttpResponse.class); - } - - public Reactions getMsgReactions(int accountId, int msgId) throws RpcException { - return gson.fromJson(getResult("get_message_reactions", accountId, msgId), Reactions.class); - } - - public int sendReaction(int accountId, int msgId, String... reaction) throws RpcException { - return getResult("send_reaction", accountId, msgId, reaction).getAsInt(); - } - - public int draftSelfReport(int accountId) throws RpcException { - return getResult("draft_self_report", accountId).getAsInt(); - } - - public void sendWebxdcRealtimeData(Integer accountId, Integer instanceMsgId, List data) throws RpcException { - getResult("send_webxdc_realtime_data", accountId, instanceMsgId, data); - } - - public void sendWebxdcRealtimeAdvertisement(Integer accountId, Integer instanceMsgId) throws RpcException { - getResult("send_webxdc_realtime_advertisement", accountId, instanceMsgId); - } - - public void leaveWebxdcRealtime(Integer accountId, Integer instanceMessageId) throws RpcException { - getResult("leave_webxdc_realtime", accountId, instanceMessageId); - } - - public int getAccountFileSize(int accountId) throws RpcException { - return getResult("get_account_file_size", accountId).getAsInt(); - } - - public void changeContactName(int accountId, int contactId, String name) throws RpcException { - getResult("change_contact_name", accountId, contactId, name); - } - - public int addAccount() throws RpcException { - return getResult("add_account").getAsInt(); - } - - public void addTransportFromQr(int accountId, String qrCode) throws RpcException { - getResult("add_transport_from_qr", accountId, qrCode); - } - - public void addOrUpdateTransport(int accountId, EnteredLoginParam param) throws RpcException { - getResult("add_or_update_transport", accountId, param); - } - - public int createBroadcast(int accountId, String chatName) throws RpcException { - return gson.fromJson(getResult("create_broadcast", accountId, chatName), Integer.class); - } - - public int createGroupChatUnencrypted(int accountId, String chatName) throws RpcException { - return gson.fromJson(getResult("create_group_chat_unencrypted", accountId, chatName), Integer.class); - } - - public void setAccountsOrder(List order) throws RpcException { - getResult("set_accounts_order", order); - } - - private static class Request { - private final String jsonrpc = "2.0"; - public final String method; - public final Object[] params; - public final int id; - - public Request(String method, Object[] params, int id) { - this.method = method; - this.params = params; - this.id = id; - } - } - - public String getMigrationError(int accountId) throws RpcException { - return gson.fromJson(getResult("get_migration_error", accountId), String.class); - } - - private static class Response { - public final int id; - public final JsonElement result; - public final JsonElement error; - - public Response(int id, JsonElement result, JsonElement error) { - this.id = id; - this.result = result; - this.error = error; - } - } -} diff --git a/src/main/java/com/b44t/messenger/rpc/RpcException.java b/src/main/java/com/b44t/messenger/rpc/RpcException.java deleted file mode 100644 index ade1abed3..000000000 --- a/src/main/java/com/b44t/messenger/rpc/RpcException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.b44t.messenger.rpc; - -/** - * An exception occurred while processing a request in Delta Chat core. - **/ -public class RpcException extends Exception { - - public RpcException(String message) { - super(message); - } -} diff --git a/src/main/java/com/b44t/messenger/rpc/VcardContact.java b/src/main/java/com/b44t/messenger/rpc/VcardContact.java deleted file mode 100644 index 75c47ea0a..000000000 --- a/src/main/java/com/b44t/messenger/rpc/VcardContact.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.b44t.messenger.rpc; - -import android.util.Base64; - -public class VcardContact { - // Email address. - private final String addr; - - // The contact's name, or the email address if no name was given. - private final String displayName; - - // Public PGP key in Base64. - private final String key; - - // Profile image in Base64. - private final String profileImage; - - // Contact color in HTML color format. - private final String color; - - // Last update timestamp. - private final int timestamp; - - public VcardContact(String addr, String displayName, String key, String profileImage, String color, int timestamp) { - this.addr = addr; - this.displayName = displayName; - this.key = key; - this.profileImage = profileImage; - this.color = color; - this.timestamp = timestamp; - } - - public String getAddr() { - return addr; - } - - public String getDisplayName() { - return displayName; - } - - public byte[] getKey() { - return key == null? null : Base64.decode(key, Base64.NO_WRAP | Base64.NO_PADDING); - } - - public boolean hasProfileImage() { - return profileImage != null; - } - - public byte[] getProfileImage() { - return profileImage == null? null : Base64.decode(profileImage, Base64.NO_WRAP | Base64.NO_PADDING); - } - - public String getColor() { - return color; - } - - public int getTimestamp() { - return timestamp; - } -} diff --git a/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index ea465b771..0088e9ed1 100644 --- a/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -25,8 +25,7 @@ import com.b44t.messenger.DcAccounts; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; import com.b44t.messenger.DcEventEmitter; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; +import com.b44t.messenger.FFITransport; import org.thoughtcrime.securesms.connect.AccountManager; import org.thoughtcrime.securesms.connect.DcEventCenter; @@ -51,6 +50,9 @@ import org.thoughtcrime.securesms.util.Util; import java.io.File; import java.util.concurrent.TimeUnit; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; + public class ApplicationContext extends MultiDexApplication { private static final String TAG = ApplicationContext.class.getSimpleName(); @@ -87,8 +89,7 @@ public class ApplicationContext extends MultiDexApplication { System.loadLibrary("native-utils"); dcAccounts = new DcAccounts(new File(getFilesDir(), "accounts").getAbsolutePath()); - rpc = new Rpc(dcAccounts.getJsonrpcInstance()); - rpc.start(); + rpc = new Rpc(new FFITransport(dcAccounts.getJsonrpcInstance())); AccountManager.getInstance().migrateToDcAccounts(this); int[] allAccounts = dcAccounts.getAll(); for (int accountId : allAccounts) { diff --git a/src/main/java/org/thoughtcrime/securesms/BaseConversationItem.java b/src/main/java/org/thoughtcrime/securesms/BaseConversationItem.java index 8ead4bed5..c0bfcf296 100644 --- a/src/main/java/org/thoughtcrime/securesms/BaseConversationItem.java +++ b/src/main/java/org/thoughtcrime/securesms/BaseConversationItem.java @@ -13,7 +13,6 @@ import androidx.appcompat.app.AlertDialog; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcMsg; -import com.b44t.messenger.rpc.Rpc; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.recipients.Recipient; @@ -22,6 +21,8 @@ import org.thoughtcrime.securesms.util.Util; import java.util.HashSet; import java.util.Set; +import chat.delta.rpc.Rpc; + public abstract class BaseConversationItem extends LinearLayout implements BindableConversationItem { diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java index 08d0bd12e..855e55607 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java @@ -71,8 +71,6 @@ import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; import com.b44t.messenger.DcMsg; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import com.b44t.messenger.util.concurrent.ListenableFuture; import com.b44t.messenger.util.concurrent.SettableFuture; @@ -123,9 +121,13 @@ import org.thoughtcrime.securesms.videochat.VideochatUtil; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; + /** * Activity for displaying a message thread, as well as * composing/sending a new message into that thread. @@ -1005,7 +1007,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } try { - byte[] vcard = rpc.makeVcard(dcContext.getAccountId(), contactId).getBytes(); + byte[] vcard = rpc.makeVcard(dcContext.getAccountId(), Collections.singletonList(contactId)).getBytes(); String mimeType = "application/octet-stream"; setMedia(PersistentBlobProvider.getInstance().create(this, vcard, mimeType, "vcard.vcf"), MediaType.DOCUMENT); } catch (RpcException e) { diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationItem.java b/src/main/java/org/thoughtcrime/securesms/ConversationItem.java index ca91a36f4..9801b5d08 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationItem.java @@ -40,9 +40,6 @@ import androidx.appcompat.app.AlertDialog; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcMsg; -import com.b44t.messenger.rpc.Reactions; -import com.b44t.messenger.rpc.RpcException; -import com.b44t.messenger.rpc.VcardContact; import org.thoughtcrime.securesms.audio.AudioSlidePlayer; import org.thoughtcrime.securesms.components.AudioView; @@ -76,6 +73,10 @@ import org.thoughtcrime.securesms.util.views.Stub; import java.util.List; import java.util.Set; +import chat.delta.rpc.RpcException; +import chat.delta.rpc.types.Reactions; +import chat.delta.rpc.types.VcardContact; + /** * A view that displays an individual conversation item within a conversation * thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter. @@ -756,11 +757,11 @@ public class ConversationItem extends BaseConversationItem private void setReactions(@NonNull DcMsg current) { try { - Reactions reactions = rpc.getMsgReactions(dcContext.getAccountId(), current.getId()); + Reactions reactions = rpc.getMessageReactions(dcContext.getAccountId(), current.getId()); if (reactions == null) { reactionsView.clear(); } else { - reactionsView.setReactions(reactions.getReactions()); + reactionsView.setReactions(reactions.reactions); reactionsView.setOnClickListener(view -> { if (eventListener != null && batchSelected.isEmpty()) { eventListener.onReactionClicked(current); @@ -895,7 +896,7 @@ public class ConversationItem extends BaseConversationItem String path = slide.asAttachment().getRealPath(context); VcardContact vcardContact = rpc.parseVcard(path).get(0); new AlertDialog.Builder(context) - .setMessage(context.getString(R.string.ask_start_chat_with, vcardContact.getDisplayName())) + .setMessage(context.getString(R.string.ask_start_chat_with, vcardContact.displayName)) .setPositiveButton(android.R.string.ok, (dialog, which) -> { try { List contactIds = rpc.importVcard(dcContext.getAccountId(), path); diff --git a/src/main/java/org/thoughtcrime/securesms/FullMsgActivity.java b/src/main/java/org/thoughtcrime/securesms/FullMsgActivity.java index 1d9fff02c..98b3c6c22 100644 --- a/src/main/java/org/thoughtcrime/securesms/FullMsgActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/FullMsgActivity.java @@ -9,17 +9,19 @@ import android.webkit.WebSettings; import androidx.appcompat.app.AlertDialog; import com.b44t.messenger.DcContext; -import com.b44t.messenger.rpc.HttpResponse; -import com.b44t.messenger.rpc.Rpc; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.util.DynamicTheme; +import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.Util; import java.io.ByteArrayInputStream; import java.lang.ref.WeakReference; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.types.HttpResponse; + public class FullMsgActivity extends WebViewActivity { public static final String MSG_ID_EXTRA = "msg_id"; @@ -187,11 +189,12 @@ public class FullMsgActivity extends WebViewActivity throw new Exception("no url specified"); } HttpResponse httpResponse = rpc.getHttpResponse(dcContext.getAccountId(), url); - String mimeType = httpResponse.getMimetype(); + String mimeType = httpResponse.mimetype; if (mimeType == null) { mimeType = "application/octet-stream"; } - res = new WebResourceResponse(mimeType, httpResponse.getEncoding(), new ByteArrayInputStream(httpResponse.getBlob())); + byte[] blob = JsonUtils.decodeBase64(httpResponse.blob); + res = new WebResourceResponse(mimeType, httpResponse.encoding, new ByteArrayInputStream(blob)); } catch (Exception e) { e.printStackTrace(); res = new WebResourceResponse("text/plain", "UTF-8", new ByteArrayInputStream(("Error: " + e.getMessage()).getBytes())); diff --git a/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java index f85a525cd..41803e392 100644 --- a/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -23,7 +23,6 @@ import androidx.loader.app.LoaderManager; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; -import com.b44t.messenger.rpc.RpcException; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.transition.Transition; @@ -44,6 +43,8 @@ import java.io.File; import java.util.ArrayList; import java.util.Objects; +import chat.delta.rpc.RpcException; + public class GroupCreateActivity extends PassphraseRequiredActionBarActivity implements ItemClickListener { diff --git a/src/main/java/org/thoughtcrime/securesms/InstantOnboardingActivity.java b/src/main/java/org/thoughtcrime/securesms/InstantOnboardingActivity.java index 1e83a7f73..ee0d02478 100644 --- a/src/main/java/org/thoughtcrime/securesms/InstantOnboardingActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/InstantOnboardingActivity.java @@ -32,8 +32,6 @@ import androidx.loader.app.LoaderManager; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; import com.b44t.messenger.DcLot; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.transition.Transition; @@ -62,6 +60,9 @@ import java.io.IOException; import java.security.SecureRandom; import java.util.Objects; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; + public class InstantOnboardingActivity extends BaseActionBarActivity implements DcEventCenter.DcEventDelegate { private static final String TAG = InstantOnboardingActivity.class.getSimpleName(); diff --git a/src/main/java/org/thoughtcrime/securesms/ProfileActivity.java b/src/main/java/org/thoughtcrime/securesms/ProfileActivity.java index 8f36b35c0..bc85a3cdf 100644 --- a/src/main/java/org/thoughtcrime/securesms/ProfileActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ProfileActivity.java @@ -23,8 +23,6 @@ import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import org.thoughtcrime.securesms.connect.DcEventCenter; import org.thoughtcrime.securesms.connect.DcHelper; @@ -36,6 +34,9 @@ import org.thoughtcrime.securesms.util.ViewUtil; import java.io.File; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; + public class ProfileActivity extends PassphraseRequiredActionBarActivity implements DcEventCenter.DcEventDelegate { diff --git a/src/main/java/org/thoughtcrime/securesms/RegistrationActivity.java b/src/main/java/org/thoughtcrime/securesms/RegistrationActivity.java index 3278aa6a4..df6408066 100644 --- a/src/main/java/org/thoughtcrime/securesms/RegistrationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/RegistrationActivity.java @@ -46,9 +46,6 @@ import androidx.constraintlayout.widget.Group; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; import com.b44t.messenger.DcProvider; -import com.b44t.messenger.rpc.EnteredLoginParam; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import com.b44t.messenger.util.concurrent.ListenableFuture; import com.b44t.messenger.util.concurrent.SettableFuture; import com.google.android.material.textfield.TextInputEditText; @@ -66,6 +63,12 @@ import org.thoughtcrime.securesms.util.views.ProgressDialog; import java.lang.ref.WeakReference; import java.util.concurrent.ExecutionException; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; +import chat.delta.rpc.types.EnteredCertificateChecks; +import chat.delta.rpc.types.EnteredLoginParam; +import chat.delta.rpc.types.Socket; + public class RegistrationActivity extends BaseActionBarActivity implements DcEventCenter.DcEventDelegate { private enum VerificationType { @@ -589,24 +592,49 @@ public class RegistrationActivity extends BaseActionBarActivity implements DcEve && !passwordInput.getText().toString().isEmpty(); } + private EnteredCertificateChecks certificateChecksFromInt(int position) { + switch (position) { + case 0: + return EnteredCertificateChecks.automatic; + case 1: + return EnteredCertificateChecks.strict; + case 2: + return EnteredCertificateChecks.acceptInvalidCertificates; + } + throw new IllegalArgumentException("Invalid certificate position: " + position); + } + + public static Socket socketSecurityFromInt(int position) { + switch (position) { + case 0: + return Socket.automatic; + case 1: + return Socket.ssl; + case 2: + return Socket.starttls; + case 3: + return Socket.plain; + } + throw new IllegalArgumentException("Invalid socketSecurity position: " + position); + } + private void setupConfig() { DcHelper.getEventCenter(this).captureNextError(); - EnteredLoginParam param = new EnteredLoginParam( - getParam(R.id.email_text, true), - getParam(R.id.password_text, false), - getParam(R.id.imap_server_text, true), - Util.objectToInt(getParam(R.id.imap_port_text, true)), - EnteredLoginParam.socketSecurityFromInt(imapSecurity.getSelectedItemPosition()), - getParam(R.id.imap_login_text, false), - getParam(R.id.smtp_server_text, true), - Util.objectToInt(getParam(R.id.smtp_port_text, true)), - EnteredLoginParam.socketSecurityFromInt(smtpSecurity.getSelectedItemPosition()), - getParam(R.id.smtp_login_text, false), - getParam(R.id.smtp_password_text, false), - EnteredLoginParam.certificateChecksFromInt(certCheck.getSelectedItemPosition()), - authMethod.getSelectedItemPosition() == 1 - ); + EnteredLoginParam param = new EnteredLoginParam(); + param.addr = getParam(R.id.email_text, true); + param.password = getParam(R.id.password_text, false); + param.imapServer = getParam(R.id.imap_server_text, true); + param.imapPort = Util.objectToInt(getParam(R.id.imap_port_text, true)); + param.imapSecurity = socketSecurityFromInt(imapSecurity.getSelectedItemPosition()); + param.imapUser = getParam(R.id.imap_login_text, false); + param.smtpServer = getParam(R.id.smtp_server_text, true); + param.smtpPort = Util.objectToInt(getParam(R.id.smtp_port_text, true)); + param.smtpSecurity = socketSecurityFromInt(smtpSecurity.getSelectedItemPosition()); + param.smtpUser = getParam(R.id.smtp_login_text, false); + param.smtpPassword = getParam(R.id.smtp_password_text, false); + param.certificateChecks = certificateChecksFromInt(certCheck.getSelectedItemPosition()); + param.oauth2 = authMethod.getSelectedItemPosition() == 1; new Thread(() -> { Rpc rpc = DcHelper.getRpc(this); diff --git a/src/main/java/org/thoughtcrime/securesms/WebxdcActivity.java b/src/main/java/org/thoughtcrime/securesms/WebxdcActivity.java index 770ebbcbd..a376edea7 100644 --- a/src/main/java/org/thoughtcrime/securesms/WebxdcActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/WebxdcActivity.java @@ -37,8 +37,6 @@ import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; import com.b44t.messenger.DcMsg; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import com.google.common.base.Charsets; import org.json.JSONObject; @@ -62,6 +60,9 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; + public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate { private static final String TAG = WebxdcActivity.class.getSimpleName(); private static final String EXTRA_ACCOUNT_ID = "accountId"; diff --git a/src/main/java/org/thoughtcrime/securesms/WebxdcStoreActivity.java b/src/main/java/org/thoughtcrime/securesms/WebxdcStoreActivity.java index 2bb90a7e4..fd95b3401 100644 --- a/src/main/java/org/thoughtcrime/securesms/WebxdcStoreActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/WebxdcStoreActivity.java @@ -17,13 +17,11 @@ import android.widget.Toast; import androidx.appcompat.app.ActionBar; import com.b44t.messenger.DcContext; -import com.b44t.messenger.rpc.HttpResponse; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; import org.thoughtcrime.securesms.util.IntentUtils; +import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.Util; @@ -31,6 +29,10 @@ import org.thoughtcrime.securesms.util.Util; import java.io.ByteArrayInputStream; import java.util.HashMap; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; +import chat.delta.rpc.types.HttpResponse; + public class WebxdcStoreActivity extends PassphraseRequiredActionBarActivity { private static final String TAG = WebxdcStoreActivity.class.getSimpleName(); @@ -59,7 +61,8 @@ public class WebxdcStoreActivity extends PassphraseRequiredActionBarActivity { Util.runOnAnyBackgroundThread(() -> { try { HttpResponse httpResponse = rpc.getHttpResponse(dcContext.getAccountId(), url); - Uri uri = PersistentBlobProvider.getInstance().create(WebxdcStoreActivity.this, httpResponse.getBlob(), "application/octet-stream", "app.xdc"); + byte[] blob = JsonUtils.decodeBase64(httpResponse.blob); + Uri uri = PersistentBlobProvider.getInstance().create(WebxdcStoreActivity.this, blob, "application/octet-stream", "app.xdc"); Intent intent = new Intent(); intent.setData(uri); setResult(Activity.RESULT_OK, intent); @@ -108,13 +111,14 @@ public class WebxdcStoreActivity extends PassphraseRequiredActionBarActivity { throw new Exception("no url specified"); } HttpResponse httpResponse = rpc.getHttpResponse(dcContext.getAccountId(), url); - String mimeType = httpResponse.getMimetype(); + String mimeType = httpResponse.mimetype; if (mimeType == null) { mimeType = "application/octet-stream"; } - ByteArrayInputStream data = new ByteArrayInputStream(httpResponse.getBlob()); - res = new WebResourceResponse(mimeType, httpResponse.getEncoding(), data); + byte[] blob = JsonUtils.decodeBase64(httpResponse.blob); + ByteArrayInputStream data = new ByteArrayInputStream(blob); + res = new WebResourceResponse(mimeType, httpResponse.encoding, data); } catch (Exception e) { e.printStackTrace(); ByteArrayInputStream data = new ByteArrayInputStream(("Could not load apps. Are you online?\n\n" + e.getMessage()).getBytes()); diff --git a/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java b/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java index b9256b1b2..012807a31 100644 --- a/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/accounts/AccountSelectionListFragment.java @@ -26,8 +26,6 @@ import com.b44t.messenger.DcAccounts; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import org.thoughtcrime.securesms.ConnectivityActivity; import org.thoughtcrime.securesms.ConversationListActivity; @@ -43,6 +41,9 @@ import org.thoughtcrime.securesms.util.ViewUtil; import java.util.Arrays; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; + public class AccountSelectionListFragment extends DialogFragment implements DcEventCenter.DcEventDelegate { private static final String TAG = AccountSelectionListFragment.class.getSimpleName(); diff --git a/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java b/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java index b4ba1ad84..f6ff9ff7b 100644 --- a/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java +++ b/src/main/java/org/thoughtcrime/securesms/components/QuoteView.java @@ -18,8 +18,6 @@ import androidx.annotation.Nullable; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcMsg; -import com.b44t.messenger.rpc.RpcException; -import com.b44t.messenger.rpc.VcardContact; import com.bumptech.glide.load.engine.DiskCacheStrategy; import org.json.JSONObject; @@ -38,6 +36,9 @@ import org.thoughtcrime.securesms.util.Util; import java.util.List; +import chat.delta.rpc.RpcException; +import chat.delta.rpc.types.VcardContact; + public class QuoteView extends FrameLayout implements RecipientForeverObserver { private static final String TAG = QuoteView.class.getSimpleName(); diff --git a/src/main/java/org/thoughtcrime/securesms/components/VcardView.java b/src/main/java/org/thoughtcrime/securesms/components/VcardView.java index 36b8e84b5..8a9ce5f1c 100644 --- a/src/main/java/org/thoughtcrime/securesms/components/VcardView.java +++ b/src/main/java/org/thoughtcrime/securesms/components/VcardView.java @@ -9,16 +9,16 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; -import com.b44t.messenger.rpc.VcardContact; - import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.SlideClickListener; import org.thoughtcrime.securesms.mms.VcardSlide; import org.thoughtcrime.securesms.recipients.Recipient; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; +import chat.delta.rpc.types.VcardContact; + public class VcardView extends FrameLayout { private static final String TAG = VcardView.class.getSimpleName(); @@ -60,8 +60,8 @@ public class VcardView extends FrameLayout { public void setVcard(@NonNull GlideRequests glideRequests, final @NonNull VcardSlide slide, final @NonNull Rpc rpc) { try { VcardContact vcardContact = rpc.parseVcard(slide.asAttachment().getRealPath(getContext())).get(0); - name.setText(vcardContact.getDisplayName()); - address.setText(vcardContact.getAddr()); + name.setText(vcardContact.displayName); + address.setText(vcardContact.addr); avatar.setAvatar(glideRequests, new Recipient(getContext(), vcardContact), false); this.slide = slide; } catch (RpcException e) { diff --git a/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java b/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java index 070a8fc0a..2a0fbf5a1 100644 --- a/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java +++ b/src/main/java/org/thoughtcrime/securesms/connect/AccountManager.java @@ -13,8 +13,6 @@ import androidx.preference.PreferenceManager; import com.b44t.messenger.DcAccounts; import com.b44t.messenger.DcContext; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.ConversationListActivity; @@ -24,6 +22,9 @@ import org.thoughtcrime.securesms.accounts.AccountSelectionListFragment; import java.io.File; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; + public class AccountManager { private static final String TAG = AccountManager.class.getSimpleName(); diff --git a/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java b/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java index 57dbb94d7..65d14d9ab 100644 --- a/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java +++ b/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java @@ -22,8 +22,6 @@ import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcLot; import com.b44t.messenger.DcMsg; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.BuildConfig; @@ -43,6 +41,9 @@ import java.io.File; import java.util.Date; import java.util.HashMap; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; + public class DcHelper { private static final String TAG = DcHelper.class.getSimpleName(); diff --git a/src/main/java/org/thoughtcrime/securesms/contacts/avatars/VcardContactPhoto.java b/src/main/java/org/thoughtcrime/securesms/contacts/avatars/VcardContactPhoto.java index 36fac9f10..a15fbc697 100644 --- a/src/main/java/org/thoughtcrime/securesms/contacts/avatars/VcardContactPhoto.java +++ b/src/main/java/org/thoughtcrime/securesms/contacts/avatars/VcardContactPhoto.java @@ -6,7 +6,7 @@ import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.b44t.messenger.rpc.VcardContact; +import org.thoughtcrime.securesms.util.JsonUtils; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -14,6 +14,8 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.security.MessageDigest; +import chat.delta.rpc.types.VcardContact; + public class VcardContactPhoto implements ContactPhoto { private final VcardContact vContact; @@ -23,7 +25,7 @@ public class VcardContactPhoto implements ContactPhoto { @Override public InputStream openInputStream(Context context) throws IOException { - byte[] blob = vContact.getProfileImage(); + byte[] blob = JsonUtils.decodeBase64(vContact.profileImage); return (blob == null)? null : new ByteArrayInputStream(blob); } @@ -39,7 +41,7 @@ public class VcardContactPhoto implements ContactPhoto { @Override public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { - messageDigest.update(vContact.getAddr().getBytes()); - messageDigest.update(ByteBuffer.allocate(4).putFloat(vContact.getTimestamp()).array()); + messageDigest.update(vContact.addr.getBytes()); + messageDigest.update(ByteBuffer.allocate(4).putFloat(vContact.timestamp).array()); } } diff --git a/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index 8e7a592c8..9815c846f 100644 --- a/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -40,7 +40,6 @@ import androidx.appcompat.app.AlertDialog; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcMsg; -import com.b44t.messenger.rpc.RpcException; import com.b44t.messenger.util.concurrent.ListenableFuture; import com.b44t.messenger.util.concurrent.ListenableFuture.Listener; import com.b44t.messenger.util.concurrent.SettableFuture; @@ -78,6 +77,8 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; +import chat.delta.rpc.RpcException; + public class AttachmentManager { diff --git a/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java index 63d980aa1..9e8024ac4 100644 --- a/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -29,8 +29,6 @@ import androidx.preference.CheckBoxPreference; import androidx.preference.ListPreference; import androidx.preference.Preference; -import com.b44t.messenger.rpc.RpcException; - import org.thoughtcrime.securesms.ApplicationPreferencesActivity; import org.thoughtcrime.securesms.ConversationActivity; import org.thoughtcrime.securesms.LogViewActivity; @@ -51,6 +49,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Objects; +import chat.delta.rpc.RpcException; + public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment implements DcEventCenter.DcEventDelegate diff --git a/src/main/java/org/thoughtcrime/securesms/reactions/AddReactionView.java b/src/main/java/org/thoughtcrime/securesms/reactions/AddReactionView.java index 6754bba62..a88b3ae5b 100644 --- a/src/main/java/org/thoughtcrime/securesms/reactions/AddReactionView.java +++ b/src/main/java/org/thoughtcrime/securesms/reactions/AddReactionView.java @@ -13,16 +13,19 @@ import androidx.emoji2.emojipicker.EmojiPickerView; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcMsg; -import com.b44t.messenger.rpc.Reactions; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.util.ViewUtil; +import java.util.Collections; +import java.util.List; import java.util.Map; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; +import chat.delta.rpc.types.Reactions; + public class AddReactionView extends LinearLayout { private AppCompatTextView[] defaultReactionViews; private AppCompatTextView anyReactionView; @@ -123,12 +126,12 @@ public class AddReactionView extends LinearLayout { private String getSelfReaction() { String result = null; try { - final Reactions reactions = rpc.getMsgReactions(dcContext.getAccountId(), msgToReactTo.getId()); + final Reactions reactions = rpc.getMessageReactions(dcContext.getAccountId(), msgToReactTo.getId()); if (reactions != null) { - final Map reactionsByContact = reactions.getReactionsByContact(); - final String [] selfReactions = reactionsByContact.get(DcContact.DC_CONTACT_ID_SELF); - if (selfReactions != null && selfReactions.length > 0) { - result = selfReactions[0]; + final Map> reactionsByContact = reactions.reactionsByContact; + final List selfReactions = reactionsByContact.get(String.valueOf(DcContact.DC_CONTACT_ID_SELF)); + if (selfReactions != null && !selfReactions.isEmpty()) { + result = selfReactions.get(0); } } } catch(RpcException e) { @@ -175,9 +178,9 @@ public class AddReactionView extends LinearLayout { private void sendReaction(final String reaction) { try { if (reaction == null || reaction.equals(getSelfReaction())) { - rpc.sendReaction(dcContext.getAccountId(), msgToReactTo.getId(), ""); + rpc.sendReaction(dcContext.getAccountId(), msgToReactTo.getId(), Collections.singletonList("")); } else { - rpc.sendReaction(dcContext.getAccountId(), msgToReactTo.getId(), reaction); + rpc.sendReaction(dcContext.getAccountId(), msgToReactTo.getId(), Collections.singletonList(reaction)); } } catch(Exception e) { e.printStackTrace(); diff --git a/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsConversationView.java b/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsConversationView.java index 8000de338..4c23426ea 100644 --- a/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsConversationView.java +++ b/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsConversationView.java @@ -14,14 +14,14 @@ import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatTextView; import androidx.core.content.ContextCompat; -import com.b44t.messenger.rpc.Reaction; - import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.ViewUtil; import java.util.ArrayList; import java.util.List; +import chat.delta.rpc.types.Reaction; + public class ReactionsConversationView extends LinearLayout { // Normally 6dp, but we have 1dp left+right margin on the pills themselves @@ -80,10 +80,14 @@ public class ReactionsConversationView extends LinearLayout { int count = 0; boolean isFromSelf = false; for (int index = 2; index < reactions.size(); index++) { - count += reactions.get(index).getCount(); - isFromSelf = isFromSelf || reactions.get(index).isFromSelf(); + count += reactions.get(index).count; + isFromSelf = isFromSelf || reactions.get(index).isFromSelf; } - shortened.add(new Reaction(null, count, isFromSelf)); + Reaction reaction = new Reaction(); + reaction.emoji = null; + reaction.count = count; + reaction.isFromSelf = isFromSelf; + shortened.add(reaction); return shortened; } else { @@ -97,11 +101,11 @@ public class ReactionsConversationView extends LinearLayout { TextView countView = root.findViewById(R.id.reactions_pill_count); View spacer = root.findViewById(R.id.reactions_pill_spacer); - if (reaction.getEmoji() != null) { - emojiView.setText(reaction.getEmoji()); + if (reaction.emoji != null) { + emojiView.setText(reaction.emoji); - if (reaction.getCount() > 1) { - countView.setText(String.valueOf(reaction.getCount())); + if (reaction.count > 1) { + countView.setText(String.valueOf(reaction.count)); } else { countView.setVisibility(GONE); spacer.setVisibility(GONE); @@ -109,10 +113,10 @@ public class ReactionsConversationView extends LinearLayout { } else { emojiView.setVisibility(GONE); spacer.setVisibility(GONE); - countView.setText("+" + reaction.getCount()); + countView.setText("+" + reaction.count); } - if (reaction.isFromSelf()) { + if (reaction.isFromSelf) { root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background_selected)); countView.setTextColor(ContextCompat.getColor(context, R.color.reaction_pill_text_color_selected)); } else { diff --git a/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java b/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java index 5de76bbb2..91edc2e18 100644 --- a/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/reactions/ReactionsDetailsFragment.java @@ -16,9 +16,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.b44t.messenger.DcEvent; -import com.b44t.messenger.rpc.Reactions; -import com.b44t.messenger.rpc.Rpc; -import com.b44t.messenger.rpc.RpcException; import org.thoughtcrime.securesms.ProfileActivity; import org.thoughtcrime.securesms.R; @@ -29,8 +26,14 @@ import org.thoughtcrime.securesms.util.Pair; import org.thoughtcrime.securesms.util.ViewUtil; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; +import chat.delta.rpc.Rpc; +import chat.delta.rpc.RpcException; +import chat.delta.rpc.types.Reactions; + public class ReactionsDetailsFragment extends DialogFragment implements DcEventCenter.DcEventDelegate { private static final String TAG = ReactionsDetailsFragment.class.getSimpleName(); @@ -87,14 +90,14 @@ public class ReactionsDetailsFragment extends DialogFragment implements DcEventC int accId = DcHelper.getContext(requireActivity()).getAccountId(); try { - final Reactions reactions = DcHelper.getRpc(requireActivity()).getMsgReactions(accId, msgId); + final Reactions reactions = DcHelper.getRpc(requireActivity()).getMessageReactions(accId, msgId); ArrayList> contactsReactions = new ArrayList<>(); if (reactions != null) { - Map reactionsByContact = reactions.getReactionsByContact(); - String[] selfReactions = reactionsByContact.remove(DcContact.DC_CONTACT_ID_SELF); - for (Integer contact: reactionsByContact.keySet()) { + Map> reactionsByContact = reactions.reactionsByContact; + List selfReactions = reactionsByContact.remove(String.valueOf(DcContact.DC_CONTACT_ID_SELF)); + for (String contact: reactionsByContact.keySet()) { for (String reaction: reactionsByContact.get(contact)) { - contactsReactions.add(new Pair<>(contact, reaction)); + contactsReactions.add(new Pair<>(Integer.parseInt(contact), reaction)); } } if (selfReactions != null) { @@ -118,12 +121,12 @@ public class ReactionsDetailsFragment extends DialogFragment implements DcEventC private String getSelfReaction(Rpc rpc, int accId) { String result = null; try { - final Reactions reactions = rpc.getMsgReactions(accId, msgId); + final Reactions reactions = rpc.getMessageReactions(accId, msgId); if (reactions != null) { - final Map reactionsByContact = reactions.getReactionsByContact(); - final String [] selfReactions = reactionsByContact.get(DcContact.DC_CONTACT_ID_SELF); - if (selfReactions != null && selfReactions.length > 0) { - result = selfReactions[0]; + final Map> reactionsByContact = reactions.reactionsByContact; + final List selfReactions = reactionsByContact.get(String.valueOf(DcContact.DC_CONTACT_ID_SELF)); + if (selfReactions != null && !selfReactions.isEmpty()) { + result = selfReactions.get(0); } } } catch(RpcException e) { @@ -139,9 +142,9 @@ public class ReactionsDetailsFragment extends DialogFragment implements DcEventC try { if (reaction == null || reaction.equals(getSelfReaction(rpc, accId))) { - rpc.sendReaction(accId, msgId, ""); + rpc.sendReaction(accId, msgId, Collections.singletonList("")); } else { - rpc.sendReaction(accId, msgId, reaction); + rpc.sendReaction(accId, msgId, Collections.singletonList(reaction)); } } catch(Exception e) { e.printStackTrace(); diff --git a/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java index 3942c3bd6..82aa6b29b 100644 --- a/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -29,7 +29,6 @@ import androidx.annotation.Nullable; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; -import com.b44t.messenger.rpc.VcardContact; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto; @@ -50,6 +49,8 @@ import java.util.HashSet; import java.util.Set; import java.util.WeakHashMap; +import chat.delta.rpc.types.VcardContact; + public class Recipient { private final Set listeners = Collections.newSetFromMap(new WeakHashMap()); @@ -149,7 +150,7 @@ public class Recipient { return dcContact.getDisplayName(); } else if(vContact!=null) { - return vContact.getDisplayName(); + return vContact.displayName; } return ""; } @@ -199,7 +200,7 @@ public class Recipient { rgb = dcContact.getColor(); } else if(vContact!=null) { - rgb = Color.parseColor(vContact.getColor()); + rgb = Color.parseColor(vContact.color); } return Color.argb(0xFF, Color.red(rgb), Color.green(rgb), Color.blue(rgb)); } @@ -235,7 +236,7 @@ public class Recipient { } } - if (vContact!=null && vContact.hasProfileImage()) { + if (vContact!=null && vContact.profileImage != null) { return new VcardContactPhoto(vContact); } diff --git a/src/main/java/org/thoughtcrime/securesms/util/JsonUtils.java b/src/main/java/org/thoughtcrime/securesms/util/JsonUtils.java index e21b04ba3..c248527ad 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/JsonUtils.java +++ b/src/main/java/org/thoughtcrime/securesms/util/JsonUtils.java @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.util; +import android.util.Base64; + import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; @@ -21,6 +23,13 @@ public class JsonUtils { objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); } + public static byte[] decodeBase64(String base64) { + if (base64 == null) { + return null; + } + return Base64.decode(base64, Base64.NO_WRAP | Base64.NO_PADDING); + } + public static T fromJson(byte[] serialized, Class clazz) throws IOException { return fromJson(new String(serialized), clazz); }