use auto-generated RPC bindings

This commit is contained in:
adbenitez 2025-09-26 23:40:26 +02:00
parent 70a05221ab
commit b871e42b86
47 changed files with 936 additions and 655 deletions

View file

@ -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.

View file

@ -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<Integer, SettableFuture<JsonNode>> 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> T callForResult(TypeReference<T> 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<JsonNode> 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<JsonNode> 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;
}
}

View file

@ -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> T callForResult(TypeReference<T> 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<Integer>(){}, "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<Integer> 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<Integer>(){}, "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.
* <p>
* This function is useful because the key-contacts migration could fail due to bugs
* and then the account will not work properly.
* <p>
* 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<String>(){}, "get_migration_error", mapper.valueToTree(accountId));
}
public Integer draftSelfReport(Integer accountId) throws RpcException {
return transport.callForResult(new TypeReference<Integer>(){}, "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<String>(){}, "get_config", mapper.valueToTree(accountId), mapper.valueToTree(key));
}
/**
* Configures a new email account using the provided parameters
* and adds it as a transport.
* <p>
* 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.
* <p>
* This function stops and starts IO as needed.
* <p>
* Usually it will be enough to only set `addr` and `password`,
* and all the other settings will be autoconfigured.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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<Integer>(){}, "create_group_chat_unencrypted", mapper.valueToTree(accountId), mapper.valueToTree(name));
}
/**
* Create a new **broadcast channel**
* (called "Channel" in the UI).
* <p>
* 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.
* <p>
* 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.
* <p>
* After creation, the chat contains no recipients and is in _unpromoted_ state;
* see [`CommandApi::create_group_chat`] for more information on the unpromoted state.
* <p>
* Returns the created chat's id.
*/
public Integer createBroadcast(Integer accountId, String chatName) throws RpcException {
return transport.callForResult(new TypeReference<Integer>(){}, "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<Integer>(){}, "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<VcardContact> parseVcard(String path) throws RpcException {
return transport.callForResult(new TypeReference<java.util.List<VcardContact>>(){}, "parse_vcard", mapper.valueToTree(path));
}
/**
* Imports contacts from a vCard file located at the given path.
* <p>
* Returns the ids of created/modified contacts in the order they appear in the vCard.
*/
public java.util.List<Integer> importVcard(Integer accountId, String path) throws RpcException {
return transport.callForResult(new TypeReference<java.util.List<Integer>>(){}, "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<Integer> contacts) throws RpcException {
return transport.callForResult(new TypeReference<String>(){}, "make_vcard", mapper.valueToTree(accountId), mapper.valueToTree(contacts));
}
public void sendWebxdcRealtimeData(Integer accountId, Integer instanceMsgId, java.util.List<Integer> 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.
* <p>
* 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<Integer>(){}, "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<CallInfo>(){}, "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<String>(){}, "ice_servers", mapper.valueToTree(accountId));
}
/**
* Makes an HTTP GET request and returns a response.
* <p>
* `url` is the HTTP or HTTPS URL.
*/
public HttpResponse getHttpResponse(Integer accountId, String url) throws RpcException {
return transport.callForResult(new TypeReference<HttpResponse>(){}, "get_http_response", mapper.valueToTree(accountId), mapper.valueToTree(url));
}
/**
* Send a reaction to message.
* <p>
* 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<String> reaction) throws RpcException {
return transport.callForResult(new TypeReference<Integer>(){}, "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<Reactions>(){}, "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<Boolean>(){}, "can_send", mapper.valueToTree(accountId), mapper.valueToTree(chatId));
}
}

View file

@ -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); }
}

View file

@ -0,0 +1,17 @@
/* Autogenerated file, do not edit manually */
package chat.delta.rpc.types;
public class CallInfo {
/**
* SDP offer.
* <p>
* Can be used to manually answer the call even if incoming call event was missed.
*/
public String sdpOffer;
/**
* Call state.
* <p>
* For example, if the call is accepted, active, cancelled, declined etc.
*/
public CallState state;
}

View file

@ -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.
* <p>
* 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.
* <p>
* Incoming calls cannot be cancelled, on the receiver side cancelled calls usually result in missed calls.
*/
public static class Cancelled extends CallState {
}
}

View file

@ -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,
}

View file

@ -0,0 +1,51 @@
/* Autogenerated file, do not edit manually */
package chat.delta.rpc.types;
/**
* Login parameters entered by the user.
* <p>
* 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.
* <p>
* 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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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<Reaction> reactions;
/* Map from a contact to it's reaction to message. */
public java.util.Map<String, java.util.List<String>> reactionsByContact;
}

View file

@ -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,
}

View file

@ -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;
}

View file

@ -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<T> extends Future<T> {
void addListener(Listener<T> listener);
public interface Listener<T> {
public void onSuccess(T result);
public void onFailure(ExecutionException e);
}
}

View file

@ -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<T> implements ListenableFuture<T> {
private final List<Listener<T>> 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<T> other) {
other.addListener(new Listener<T>() {
@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<T> listener) {
synchronized (this) {
listeners.add(listener);
if (!completed) return;
}
notifyListener(listener);
}
private void notifyAllListeners() {
List<Listener<T>> localListeners;
synchronized (this) {
localListeners = new LinkedList<>(listeners);
}
for (Listener<T> listener : localListeners) {
notifyListener(listener);
}
}
private void notifyListener(Listener<T> listener) {
if (exception != null) listener.onFailure(new ExecutionException(exception));
else listener.onSuccess(result);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<Integer, String[]> reactionsByContact;
// Unique reactions, sorted in descending order.
private final ArrayList<Reaction> reactions;
public Reactions(HashMap<Integer, String[]> reactionsByContact, ArrayList<Reaction> reactions) {
this.reactionsByContact = reactionsByContact;
this.reactions = reactions;
}
public Map<Integer, String[]> getReactionsByContact() {
return reactionsByContact;
}
public List<Reaction> getReactions() {
return reactions;
}
}

View file

@ -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<Integer, SettableFuture<JsonElement>> 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<JsonElement> 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<JsonElement> 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<JsonElement> 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<VcardContact> parseVcard(String path) throws RpcException {
TypeToken<List<VcardContact>> listType = new TypeToken<List<VcardContact>>(){};
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<Integer> importVcard(int accountId, String path) throws RpcException {
TypeToken<List<Integer>> listType = new TypeToken<List<Integer>>(){};
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<Integer> 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<Integer> 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;
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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) {

View file

@ -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
{

View file

@ -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) {

View file

@ -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<Integer> contactIds = rpc.importVcard(dcContext.getAccountId(), path);

View file

@ -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()));

View file

@ -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
{

View file

@ -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();

View file

@ -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
{

View file

@ -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);

View file

@ -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";

View file

@ -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());

View file

@ -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();

View file

@ -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();

View file

@ -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) {

View file

@ -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();

View file

@ -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();

View file

@ -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());
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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<Integer, String[]> reactionsByContact = reactions.getReactionsByContact();
final String [] selfReactions = reactionsByContact.get(DcContact.DC_CONTACT_ID_SELF);
if (selfReactions != null && selfReactions.length > 0) {
result = selfReactions[0];
final Map<String, List<String>> reactionsByContact = reactions.reactionsByContact;
final List<String> 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();

View file

@ -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 {

View file

@ -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<Pair<Integer, String>> contactsReactions = new ArrayList<>();
if (reactions != null) {
Map<Integer, String[]> reactionsByContact = reactions.getReactionsByContact();
String[] selfReactions = reactionsByContact.remove(DcContact.DC_CONTACT_ID_SELF);
for (Integer contact: reactionsByContact.keySet()) {
Map<String, List<String>> reactionsByContact = reactions.reactionsByContact;
List<String> 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<Integer, String[]> reactionsByContact = reactions.getReactionsByContact();
final String [] selfReactions = reactionsByContact.get(DcContact.DC_CONTACT_ID_SELF);
if (selfReactions != null && selfReactions.length > 0) {
result = selfReactions[0];
final Map<String, List<String>> reactionsByContact = reactions.reactionsByContact;
final List<String> 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();

View file

@ -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<RecipientModifiedListener> listeners = Collections.newSetFromMap(new WeakHashMap<RecipientModifiedListener, Boolean>());
@ -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);
}

View file

@ -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> T fromJson(byte[] serialized, Class<T> clazz) throws IOException {
return fromJson(new String(serialized), clazz);
}