implement interfaces

This commit is contained in:
Ryan ZHAO
2021-01-13 17:11:30 +11:00
parent cb5ee74a43
commit 74a9754f48
174 changed files with 1022 additions and 895 deletions

View File

@@ -1,6 +1,7 @@
package org.session.libsession.messaging
import android.content.Context
import android.net.Uri
import org.session.libsession.messaging.jobs.AttachmentUploadJob
import org.session.libsession.messaging.jobs.Job
@@ -18,12 +19,14 @@ import org.session.libsession.messaging.threads.recipients.Recipient.RecipientSe
import org.session.libsignal.libsignal.ecc.ECKeyPair
import org.session.libsignal.libsignal.ecc.ECPrivateKey
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.internal.push.SignalServiceProtos
interface StorageProtocol {
// General
fun getUserPublicKey(): String?
fun getUserKeyPair(): ECKeyPair?
fun getUserKeyPair(): Pair<String, ByteArray>?
fun getUserDisplayName(): String?
fun getUserProfileKey(): ByteArray?
fun getUserProfilePictureURL(): String?
@@ -34,10 +37,6 @@ interface StorageProtocol {
fun getOrGenerateRegistrationID(): Int
// Shared Sender Keys
fun getClosedGroupPrivateKey(publicKey: String): ECPrivateKey?
fun isClosedGroup(publicKey: String): Boolean
// Jobs
fun persist(job: Job)
fun markJobAsSucceeded(job: Job)
@@ -97,6 +96,11 @@ interface StorageProtocol {
fun setActive(groupID: String, value: Boolean)
fun removeMember(groupID: String, member: Address)
fun updateMembers(groupID: String, members: List<Address>)
// Closed Group
fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type0: SignalServiceProtos.GroupContext.Type, type1: SignalServiceGroup.Type,
name: String, members: Collection<String>, admins: Collection<String>)
fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceProtos.GroupContext.Type, name: String,
members: Collection<String>, admins: Collection<String>, threadID: Long)
// Settings
fun setProfileSharing(address: Address, value: Boolean)

View File

@@ -1,150 +0,0 @@
package org.session.libsession.messaging.messages.signal;
import org.session.libsession.messaging.sending_receiving.attachments.Attachment;
import org.session.libsession.messaging.sending_receiving.attachments.PointerAttachment;
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview;
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel;
import org.session.libsession.messaging.threads.Address;
import org.session.libsession.utilities.GroupUtil;
import org.session.libsignal.libsignal.util.guava.Optional;
import org.session.libsignal.service.api.messages.SignalServiceAttachment;
import org.session.libsignal.service.api.messages.SignalServiceGroup;
import org.session.libsession.messaging.sending_receiving.contacts.Contact;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class IncomingMediaMessage {
private final Address from;
private final Address groupId;
private final String body;
private final boolean push;
private final long sentTimeMillis;
private final int subscriptionId;
private final long expiresIn;
private final boolean expirationUpdate;
private final QuoteModel quote;
private final boolean unidentified;
private final List<Attachment> attachments = new LinkedList<>();
private final List<Contact> sharedContacts = new LinkedList<>();
private final List<LinkPreview> linkPreviews = new LinkedList<>();
public IncomingMediaMessage(Address from,
Optional<Address> groupId,
String body,
long sentTimeMillis,
List<Attachment> attachments,
int subscriptionId,
long expiresIn,
boolean expirationUpdate,
boolean unidentified)
{
this.from = from;
this.groupId = groupId.orNull();
this.sentTimeMillis = sentTimeMillis;
this.body = body;
this.push = false;
this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn;
this.expirationUpdate = expirationUpdate;
this.quote = null;
this.unidentified = unidentified;
this.attachments.addAll(attachments);
}
public IncomingMediaMessage(Address from,
long sentTimeMillis,
int subscriptionId,
long expiresIn,
boolean expirationUpdate,
boolean unidentified,
Optional<String> body,
Optional<SignalServiceGroup> group,
Optional<List<SignalServiceAttachment>> attachments,
Optional<QuoteModel> quote,
Optional<List<Contact>> sharedContacts,
Optional<List<LinkPreview>> linkPreviews,
Optional<Attachment> sticker)
{
this.push = true;
this.from = from;
this.sentTimeMillis = sentTimeMillis;
this.body = body.orNull();
this.subscriptionId = subscriptionId;
this.expiresIn = expiresIn;
this.expirationUpdate = expirationUpdate;
this.quote = quote.orNull();
this.unidentified = unidentified;
if (group.isPresent()) this.groupId = Address.Companion.fromSerialized(GroupUtil.INSTANCE.getEncodedGroupID(group.get().getGroupId()));
else this.groupId = null;
this.attachments.addAll(PointerAttachment.forPointers(attachments));
this.sharedContacts.addAll(sharedContacts.or(Collections.emptyList()));
this.linkPreviews.addAll(linkPreviews.or(Collections.emptyList()));
if (sticker.isPresent()) {
this.attachments.add(sticker.get());
}
}
public int getSubscriptionId() {
return subscriptionId;
}
public String getBody() {
return body;
}
public List<Attachment> getAttachments() {
return attachments;
}
public Address getFrom() {
return from;
}
public Address getGroupId() {
return groupId;
}
public boolean isPushMessage() {
return push;
}
public boolean isExpirationUpdate() {
return expirationUpdate;
}
public long getSentTimeMillis() {
return sentTimeMillis;
}
public long getExpiresIn() {
return expiresIn;
}
public boolean isGroupMessage() {
return groupId != null;
}
public QuoteModel getQuote() {
return quote;
}
public List<Contact> getSharedContacts() {
return sharedContacts;
}
public List<LinkPreview> getLinkPreviews() {
return linkPreviews;
}
public boolean isUnidentified() {
return unidentified;
}
}

View File

@@ -196,7 +196,7 @@ object OpenGroupAPI: DotNetAPI() {
val userKeyPair = storage.getUserKeyPair() ?: throw Error.Generic
val userDisplayName = storage.getUserDisplayName() ?: throw Error.Generic
Thread {
val signedMessage = message.sign(userKeyPair.privateKey.serialize())
val signedMessage = message.sign(userKeyPair.second)
if (signedMessage == null) {
deferred.reject(Error.SigningFailed)
} else {
@@ -212,7 +212,7 @@ object OpenGroupAPI: DotNetAPI() {
format.timeZone = TimeZone.getTimeZone("GMT")
val dateAsString = data["created_at"] as String
val timestamp = format.parse(dateAsString).time
@Suppress("NAME_SHADOWING") val message = OpenGroupMessage(serverID, userKeyPair.hexEncodedPublicKey, userDisplayName, text, timestamp, openGroupMessageType, message.quote, message.attachments, null, signedMessage.signature, timestamp)
@Suppress("NAME_SHADOWING") val message = OpenGroupMessage(serverID, userKeyPair.first, userDisplayName, text, timestamp, openGroupMessageType, message.quote, message.attachments, null, signedMessage.signature, timestamp)
message
} catch (exception: Exception) {
Log.d("Loki", "Couldn't parse message for open group with ID: $channel on server: $server.")

View File

@@ -3,6 +3,7 @@ package org.session.libsession.messaging.sending_receiving
import org.session.libsession.messaging.MessagingConfiguration
import org.session.libsession.messaging.sending_receiving.MessageReceiver.Error
import org.session.libsession.utilities.AESGCM
import org.session.libsession.utilities.GroupUtil
import org.whispersystems.curve25519.Curve25519
@@ -38,16 +39,16 @@ object MessageReceiverDecryption {
internal fun decryptWithSharedSenderKeys(envelope: SignalServiceProtos.Envelope): Pair<ByteArray, String> {
// 1. ) Check preconditions
val groupPublicKey = envelope.source
if (!MessagingConfiguration.shared.storage.isClosedGroup(groupPublicKey)) { throw Error.InvalidGroupPublicKey }
if (!GroupUtil.isClosedGroup(groupPublicKey)) { throw Error.InvalidGroupPublicKey }
val data = envelope.content
if (data.count() == 0) { throw Error.NoData }
val groupPrivateKey = MessagingConfiguration.shared.storage.getClosedGroupPrivateKey(groupPublicKey) ?: throw Error.NoGroupPrivateKey
val groupPrivateKey = MessagingConfiguration.shared.sskDatabase.getClosedGroupPrivateKey(groupPublicKey) ?: throw Error.NoGroupPrivateKey
// 2. ) Parse the wrapper
val wrapper = SignalServiceProtos.ClosedGroupCiphertextMessageWrapper.parseFrom(data)
val ivAndCiphertext = wrapper.ciphertext.toByteArray()
val ephemeralPublicKey = wrapper.ephemeralPublicKey.toByteArray()
// 3. ) Decrypt the data inside
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(ephemeralPublicKey, groupPrivateKey.serialize())
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(ephemeralPublicKey, groupPrivateKey.toByteArray())
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256"))
val symmetricKey = mac.doFinal(ephemeralSharedSecret)

View File

@@ -27,6 +27,7 @@ import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.libsignal.util.Hex
import org.session.libsignal.libsignal.util.guava.Optional
import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupRatchet
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupRatchetCollectionType
@@ -38,7 +39,9 @@ import java.util.*
import kotlin.collections.ArrayList
internal fun MessageReceiver.isBlock(publicKey: String): Boolean {
return SSKEnvironment.shared.blockManager.isRecipientIdBlocked(publicKey)
val context = MessagingConfiguration.shared.context
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
return recipient.isBlocked
}
fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content, openGroupID: String?) {
@@ -46,13 +49,14 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content,
is ReadReceipt -> handleReadReceipt(message)
is TypingIndicator -> handleTypingIndicator(message)
is ClosedGroupUpdate -> handleClosedGroupUpdate(message)
is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message)
is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message, proto)
is VisibleMessage -> handleVisibleMessage(message, proto, openGroupID)
}
}
private fun MessageReceiver.handleReadReceipt(message: ReadReceipt) {
SSKEnvironment.shared.readReceiptManager.processReadReceipts(message.sender!!, message.timestamps!!.asList(), message.receivedTimestamp!!)
val context = MessagingConfiguration.shared.context
SSKEnvironment.shared.readReceiptManager.processReadReceipts(context, message.sender!!, message.timestamps!!.asList(), message.receivedTimestamp!!)
}
private fun MessageReceiver.handleTypingIndicator(message: TypingIndicator) {
@@ -83,20 +87,25 @@ fun MessageReceiver.cancelTypingIndicatorsIfNeeded(senderPublicKey: String) {
SSKEnvironment.shared.typingIndicators.didReceiveIncomingMessage(context, threadID.toLong(), address, 1)
}
private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimerUpdate) {
private fun MessageReceiver.handleExpirationTimerUpdate(message: ExpirationTimerUpdate, proto: SignalServiceProtos.Content) {
if (message.duration!! > 0) {
setExpirationTimer(message.duration!!, message.sender!!, message.groupPublicKey)
setExpirationTimer(message, proto)
} else {
disableExpirationTimer(message.sender!!, message.groupPublicKey)
disableExpirationTimer(message, proto)
}
}
fun MessageReceiver.setExpirationTimer(duration: Int, senderPublicKey: String, groupPublicKey: String?) {
fun MessageReceiver.setExpirationTimer(message: ExpirationTimerUpdate, proto: SignalServiceProtos.Content) {
val id = message.id?.toLong()
val duration = message.duration!!
val senderPublicKey = message.sender!!
SSKEnvironment.shared.messageExpirationManager.setExpirationTimer(id, duration, senderPublicKey, proto)
}
fun MessageReceiver.disableExpirationTimer(senderPublicKey: String, groupPublicKey: String?) {
fun MessageReceiver.disableExpirationTimer(message: ExpirationTimerUpdate, proto: SignalServiceProtos.Content) {
val id = message.id?.toLong()
val senderPublicKey = message.sender!!
SSKEnvironment.shared.messageExpirationManager.disableExpirationTimer(id, senderPublicKey, proto)
}
fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalServiceProtos.Content, openGroupID: String?) {
@@ -125,14 +134,14 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS
// Update the user's local name if the message came from their master device
TextSecurePreferences.setProfileName(context, displayName)
}
profileManager.setDisplayName(recipient, displayName)
profileManager.setDisplayName(context, recipient, displayName)
if (recipient.profileKey == null || !MessageDigest.isEqual(recipient.profileKey, newProfile.profileKey)) {
profileManager.setProfileKey(recipient, newProfile.profileKey!!)
profileManager.setUnidentifiedAccessMode(recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
profileManager.setProfileKey(context, recipient, newProfile.profileKey!!)
profileManager.setUnidentifiedAccessMode(context, recipient, Recipient.UnidentifiedAccessMode.UNKNOWN)
val url = newProfile.profilePictureURL.orEmpty()
profileManager.setProfilePictureURL(recipient, url)
profileManager.setProfilePictureURL(context, recipient, url)
if (userPublicKey == message.sender) {
profileManager.updateOpenGroupProfilePicturesIfNeeded()
profileManager.updateOpenGroupProfilePicturesIfNeeded(context)
}
}
}
@@ -192,6 +201,7 @@ private fun MessageReceiver.handleClosedGroupUpdate(message: ClosedGroupUpdate)
}
private fun MessageReceiver.handleNewGroup(message: ClosedGroupUpdate) {
val context = MessagingConfiguration.shared.context
val storage = MessagingConfiguration.shared.storage
val sskDatabase = MessagingConfiguration.shared.sskDatabase
if (message.kind !is ClosedGroupUpdate.Kind.New) { return }
@@ -241,12 +251,11 @@ private fun MessageReceiver.handleNewGroup(message: ClosedGroupUpdate) {
// Notify the PN server
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
// Notify the user
/* TODO
insertIncomingInfoMessage(context, senderPublicKey, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
*/
storage.insertIncomingInfoMessage(context, message.sender!!, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, SignalServiceGroup.Type.UPDATE, name, members, admins)
}
private fun MessageReceiver.handleGroupUpdate(message: ClosedGroupUpdate) {
val context = MessagingConfiguration.shared.context
val storage = MessagingConfiguration.shared.storage
val sskDatabase = MessagingConfiguration.shared.sskDatabase
if (message.kind !is ClosedGroupUpdate.Kind.Info) { return }
@@ -272,6 +281,7 @@ private fun MessageReceiver.handleGroupUpdate(message: ClosedGroupUpdate) {
val oldMembers = group.members.map { it.serialize() }.toSet()
val userPublicKey = storage.getUserPublicKey()!!
val wasUserRemoved = !members.contains(userPublicKey)
val wasSenderRemoved = !members.contains(message.sender!!)
if (members.toSet().intersect(oldMembers) != oldMembers.toSet()) {
val allOldRatchets = sskDatabase.getAllClosedGroupRatchets(groupPublicKey, ClosedGroupRatchetCollectionType.Current)
for (pair in allOldRatchets) {
@@ -304,9 +314,9 @@ private fun MessageReceiver.handleGroupUpdate(message: ClosedGroupUpdate) {
storage.updateTitle(groupID, name)
storage.updateMembers(groupID, members.map { Address.fromSerialized(it) })
// Notify the user if needed
val infoType = if (wasUserRemoved) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
// TODO insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID)
val type0 = if (wasSenderRemoved) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE
val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE
storage.insertIncomingInfoMessage(context, message.sender!!, groupID, type0, type1, name, members, admins)
}
private fun MessageReceiver.handleSenderKeyRequest(message: ClosedGroupUpdate) {

View File

@@ -12,6 +12,7 @@ import org.session.libsession.messaging.sending_receiving.notifications.PushNoti
import org.session.libsession.messaging.sending_receiving.MessageSender.Error
import org.session.libsession.messaging.threads.Address
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.SSKEnvironment
import org.session.libsignal.libsignal.ecc.Curve
@@ -27,6 +28,7 @@ import java.util.*
fun MessageSender.createClosedGroup(name: String, members: Collection<String>): Promise<String, Exception> {
val deferred = deferred<String, Exception>()
// Prepare
val context = MessagingConfiguration.shared.context
val storage = MessagingConfiguration.shared.storage
val members = members
val userPublicKey = storage.getUserPublicKey()!!
@@ -64,9 +66,7 @@ fun MessageSender.createClosedGroup(name: String, members: Collection<String>):
PushNotificationAPI.performOperation(PushNotificationAPI.ClosedGroupOperation.Subscribe, groupPublicKey, userPublicKey)
// Notify the user
val threadID =storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
/* TODO
insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID)
*/
storage.insertOutgoingInfoMessage(context, groupID, SignalServiceProtos.GroupContext.Type.UPDATE, name, members, admins, threadID)
// Fulfill the promise
deferred.resolve(groupPublicKey)
// Return
@@ -75,6 +75,7 @@ fun MessageSender.createClosedGroup(name: String, members: Collection<String>):
fun MessageSender.update(groupPublicKey: String, members: Collection<String>, name: String): Promise<Unit, Exception> {
val deferred = deferred<Unit, Exception>()
val context = MessagingConfiguration.shared.context
val storage = MessagingConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey()!!
val sskDatabase = MessagingConfiguration.shared.sskDatabase
@@ -197,9 +198,7 @@ fun MessageSender.update(groupPublicKey: String, members: Collection<String>, na
// Notify the user
val infoType = if (isUserLeaving) SignalServiceProtos.GroupContext.Type.QUIT else SignalServiceProtos.GroupContext.Type.UPDATE
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
/* TODO
insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID)
*/
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID)
deferred.resolve(Unit)
return deferred.promise
}

View File

@@ -27,7 +27,6 @@ fun MessageSender.send(message: Message, address: Address) {
fun MessageSender.sendNonDurably(message: VisibleMessage, attachments: List<SignalServiceAttachment>, address: Address): Promise<Unit, Exception> {
prep(attachments, message)
// TODO: Deal with attachments
return sendNonDurably(message, address)
}

View File

@@ -0,0 +1,23 @@
package org.session.libsession.messaging.sending_receiving.attachments
data class DatabaseAttachmentAudioExtras(
val attachmentId: AttachmentId,
/** Small amount of normalized audio byte samples to visualise the content (e.g. draw waveform). */
val visualSamples: ByteArray,
/** Duration of the audio track in milliseconds. May be [DURATION_UNDEFINED] when it is not known. */
val durationMs: Long = DURATION_UNDEFINED) {
companion object {
const val DURATION_UNDEFINED = -1L
}
override fun equals(other: Any?): Boolean {
return other != null &&
other is DatabaseAttachmentAudioExtras &&
other.attachmentId == attachmentId
}
override fun hashCode(): Int {
return attachmentId.hashCode()
}
}

View File

@@ -2,8 +2,10 @@ package org.session.libsession.messaging.sending_receiving.notifications
import android.content.Context
import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsignal.service.api.messages.SignalServiceGroup
import org.session.libsignal.service.internal.push.SignalServiceProtos
interface NotificationProtocol {
interface MessageNotifier {
fun setVisibleThread(threadId: Long)
fun setLastDesktopActivityTimestamp(timestamp: Long)
fun notifyMessageDeliveryFailed(context: Context?, recipient: Recipient?, threadId: Long)

View File

@@ -786,7 +786,7 @@ public class Recipient implements RecipientModifiedListener {
private final UnidentifiedAccessMode unidentifiedAccessMode;
private final boolean forceSmsSelection;
RecipientSettings(boolean blocked, long muteUntil,
public RecipientSettings(boolean blocked, long muteUntil,
@NonNull VibrateState messageVibrateState,
@NonNull VibrateState callVibrateState,
@Nullable Uri messageRingtone,

View File

@@ -4,7 +4,7 @@ import android.content.Intent;
import android.provider.ContactsContract;
import android.text.TextUtils;
import org.thoughtcrime.securesms.database.Address;
import org.session.libsession.messaging.threads.Address;
import static android.content.Intent.ACTION_INSERT_OR_EDIT;

View File

@@ -82,7 +82,7 @@ open class DotNetAPI {
private fun requestNewAuthToken(server: String): Promise<String, Exception> {
Log.d("Loki", "Requesting auth token for server: $server.")
val userKeyPair = MessagingConfiguration.shared.storage.getUserKeyPair() ?: throw Error.Generic
val parameters: Map<String, Any> = mapOf( "pubKey" to userKeyPair.hexEncodedPublicKey )
val parameters: Map<String, Any> = mapOf( "pubKey" to userKeyPair.first )
return execute(HTTPVerb.GET, server, "loki/v1/get_challenge", false, parameters).map(SnodeAPI.sharedContext) { json ->
try {
val base64EncodedChallenge = json["cipherText64"] as String
@@ -95,7 +95,7 @@ open class DotNetAPI {
serverPublicKey = Hex.fromStringCondensed(hexEncodedServerPublicKey.removing05PrefixIfNeeded())
}
// The challenge is prefixed by the 16 bit IV
val tokenAsData = DiffieHellman.decrypt(challenge, serverPublicKey, userKeyPair.privateKey.serialize())
val tokenAsData = DiffieHellman.decrypt(challenge, serverPublicKey, userKeyPair.second)
val token = tokenAsData.toString(Charsets.UTF_8)
token
} catch (exception: Exception) {

View File

@@ -3,14 +3,17 @@ package org.session.libsession.utilities
import java.util.regex.Pattern
object DelimiterUtil {
@JvmStatic
fun escape(value: String, delimiter: Char): String {
return value.replace("" + delimiter, "\\" + delimiter)
}
@JvmStatic
fun unescape(value: String, delimiter: Char): String {
return value.replace("\\" + delimiter, "" + delimiter)
}
@JvmStatic
fun split(value: String, delimiter: Char): Array<String> {
val regex = "(?<!\\\\)" + Pattern.quote(delimiter.toString() + "")
return value.split(regex).toTypedArray()

View File

@@ -1,16 +1,17 @@
package org.session.libsession.utilities
import android.content.Context
import org.session.libsession.messaging.sending_receiving.notifications.NotificationProtocol
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier
import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.threads.recipients.Recipient
import org.session.libsignal.service.internal.push.SignalServiceProtos
class SSKEnvironment(
val typingIndicators: TypingIndicatorsProtocol,
val blockManager: BlockingManagerProtocol,
val readReceiptManager: ReadReceiptManagerProtocol,
val profileManager: ProfileManagerProtocol,
val notificationManager: NotificationProtocol
val notificationManager: MessageNotifier,
val messageExpirationManager: MessageExpirationManagerProtocol
) {
interface TypingIndicatorsProtocol {
fun didReceiveTypingStartedMessage(context: Context, threadId: Long, author: Address, device: Int)
@@ -18,32 +19,33 @@ class SSKEnvironment(
fun didReceiveIncomingMessage(context: Context, threadId: Long, author: Address, device: Int)
}
interface BlockingManagerProtocol {
fun isRecipientIdBlocked(publicKey: String): Boolean
}
interface ReadReceiptManagerProtocol {
fun processReadReceipts(fromRecipientId: String, sentTimestamps: List<Long>, readTimestamp: Long)
fun processReadReceipts(context: Context, fromRecipientId: String, sentTimestamps: List<Long>, readTimestamp: Long)
}
interface ProfileManagerProtocol {
fun setDisplayName(recipient: Recipient, displayName: String)
fun setProfilePictureURL(recipient: Recipient, profilePictureURL: String)
fun setProfileKey(recipient: Recipient, profileKey: ByteArray)
fun setUnidentifiedAccessMode(recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode)
fun updateOpenGroupProfilePicturesIfNeeded()
fun setDisplayName(context: Context, recipient: Recipient, displayName: String)
fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String)
fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray)
fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode)
fun updateOpenGroupProfilePicturesIfNeeded(context: Context)
}
interface MessageExpirationManagerProtocol {
fun setExpirationTimer(messageID: Long?, duration: Int, senderPublicKey: String, content: SignalServiceProtos.Content)
fun disableExpirationTimer(messageID: Long?, senderPublicKey: String, content: SignalServiceProtos.Content)
}
companion object {
lateinit var shared: SSKEnvironment
fun configure(typingIndicators: TypingIndicatorsProtocol,
blockManager: BlockingManagerProtocol,
readReceiptManager: ReadReceiptManagerProtocol,
profileManager: ProfileManagerProtocol,
notificationManager: NotificationProtocol) {
notificationManager: MessageNotifier,
messageExpirationManager: MessageExpirationManagerProtocol) {
if (Companion::shared.isInitialized) { return }
shared = SSKEnvironment(typingIndicators, blockManager, readReceiptManager, profileManager, notificationManager)
shared = SSKEnvironment(typingIndicators, readReceiptManager, profileManager, notificationManager, messageExpirationManager)
}
}
}

View File

@@ -334,6 +334,7 @@ object TextSecurePreferences {
return getBooleanPreference(context, INCOGNITO_KEYBORAD_PREF, true)
}
@JvmStatic
fun isReadReceiptsEnabled(context: Context): Boolean {
return getBooleanPreference(context, READ_RECEIPTS_PREF, false)
}

View File

@@ -1,8 +1,15 @@
package org.session.libsession.utilities
import android.content.Context
import android.net.Uri
import android.os.Handler
import android.os.Looper
import org.session.libsession.messaging.threads.Address
import org.session.libsignal.libsignal.logging.Log
import java.io.Closeable
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.ThreadPoolExecutor
@@ -16,6 +23,21 @@ object Util {
return Looper.myLooper() == Looper.getMainLooper()
}
@JvmStatic
@Throws(IOException::class)
fun copy(`in`: InputStream, out: OutputStream): Long {
val buffer = ByteArray(8192)
var read: Int
var total: Long = 0
while (`in`.read(buffer).also { read = it } != -1) {
out.write(buffer, 0, read)
total += read.toLong()
}
`in`.close()
out.close()
return total
}
@JvmStatic
fun uri(uri: String?): Uri? {
return if (uri == null) null else Uri.parse(uri)
@@ -27,6 +49,16 @@ object Util {
else getHandler()?.post(runnable)
}
@JvmStatic
fun runOnMainDelayed(runnable: Runnable, delayMillis: Long) {
getHandler()?.postDelayed(runnable, delayMillis)
}
@JvmStatic
fun cancelRunnableOnMain(runnable: Runnable) {
getHandler()?.removeCallbacks(runnable)
}
private fun getHandler(): Handler? {
if (handler == null) {
synchronized(Util::class.java) {
@@ -77,4 +109,55 @@ object Util {
return Arrays.hashCode(objects)
}
@JvmStatic
fun <K, V> getOrDefault(map: Map<K, V>, key: K, defaultValue: V): V? {
return if (map.containsKey(key)) map[key] else defaultValue
}
@JvmStatic
@Throws(IOException::class)
fun getStreamLength(`in`: InputStream): Long {
val buffer = ByteArray(4096)
var totalSize = 0
var read: Int
while (`in`.read(buffer).also { read = it } != -1) {
totalSize += read
}
return totalSize.toLong()
}
@JvmStatic
fun toIntExact(value: Long): Int {
if (value.toInt().compareTo(value) != 0){
throw ArithmeticException("integer overflow")
}
return value.toInt()
}
@JvmStatic
fun close(closeable: Closeable) {
try {
closeable.close()
} catch (e: IOException) {
Log.w("Loki", e)
}
}
@JvmStatic
fun isOwnNumber(context: Context, address: Address): Boolean {
return if (address.isGroup) false else TextSecurePreferences.getLocalNumber(context) == address.serialize()
}
@JvmStatic
fun <T> partition(list: List<T>, partitionSize: Int): List<List<T>> {
val results: MutableList<List<T>> = LinkedList()
var index = 0
while (index < list.size) {
val subListSize = Math.min(partitionSize, list.size - index)
results.add(list.subList(index, index + subListSize))
index += partitionSize
}
return results
}
}