théoriquement on peut envoyer des MMS (chiffrés ou non)

This commit is contained in:
odrling 2018-03-30 18:40:37 +02:00
parent c2a888a792
commit cd4e674759
6 changed files with 183 additions and 83 deletions

View file

@ -1,6 +1,5 @@
package xyz.johnny.norntalk.database
import android.annotation.SuppressLint
import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
@ -17,7 +16,7 @@ import java.lang.ref.WeakReference
* Base de données de Norn Talk
*/
@Database(entities = arrayOf(Message::class, Member::class, Media::class,
Conversation::class, Contact::class), version=4, exportSchema=false)
Conversation::class, Contact::class), version=5, exportSchema=false)
abstract class NornDatabase : RoomDatabase() {
companion object {
@ -34,21 +33,35 @@ abstract class NornDatabase : RoomDatabase() {
* Migration de la base de données de la version 2 à la version 3.
* Rajoute le champs read de l'entité Message.
*/
private val migration2_3 = object : Migration(2, 3) {
private val migrations = arrayOf(
object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table Message add column read integer not null default 0")
}
},
object: Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table Media add column name text not null default ''")
database.execSQL("alter table Media add column position integer not null default 0")
}
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table Message add column read integer not null default 0")
}
}
)
class BuildDBTask : AsyncTask<Context, Void, Unit>() {
override fun doInBackground(vararg params: Context) {
val dbBuilder = Room.databaseBuilder(params[0].applicationContext,
NornDatabase::class.java,
DB_NAME).fallbackToDestructiveMigration()
dbBuilder.addMigrations(migration2_3)
val dbBuilder = Room.databaseBuilder(
params[0].applicationContext, NornDatabase::class.java, DB_NAME
).fallbackToDestructiveMigration()
for (migration in migrations) {
dbBuilder.addMigrations(migration)
}
instance = dbBuilder.build()
}

View file

@ -36,9 +36,17 @@ interface MediaDao {
/**
* Supprimer un media donné
*
* @param media Média à supprimer
* @param media [Media] à supprimer
*/
@Delete
fun deleteMedia(media: Media)
/**
* Récupére les documents associés à un message
*
* @param messageId Identifiant du message
*/
@Query("select * from Media where messageId = :messageId order by position")
fun getMediasFromMessage(messageId: Long) : Array<Media>
}

View file

@ -11,6 +11,7 @@ import android.arch.persistence.room.PrimaryKey
* @property type MIME type du média
* @property data Contenu du média
* @property messageId Identifiant du message auquel le média est associé
* @property position position du document dans le message
*/
@Entity(foreignKeys = arrayOf(ForeignKey(entity= Message::class,
parentColumns = arrayOf("id"),
@ -20,7 +21,9 @@ data class Media(var type: String,
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
var data: ByteArray,
@ColumnInfo(index=true)
var messageId: Long) {
var messageId: Long,
var name: String,
val position: Int) {
/**
* Identifiant du média

View file

@ -0,0 +1,79 @@
package xyz.johnny.norntalk.messages
import android.content.Context
import xyz.johnny.norntalk.database.NornDatabase
import xyz.johnny.norntalk.database.entities.Media
import xyz.johnny.norntalk.security.Curve
/**
* Classe permettant de gérer un document contenu dans un MMS
*/
class NornMedia(data: ByteArray?, val mimeType: String, name: String?) {
var _data = data
val data get() = _data!!
companion object {
fun from(media: Media): NornMedia {
return NornMedia(media.data, media.type, media.name)
}
}
val name = name ?: "media_%d".format(System.currentTimeMillis())
/**
* Insérer le document dans la base de données
*
* @param context Contexte courant
* @param message Message auquel le [Media] est associé
* @param position position de ce [Media] pour le message
*/
fun insert(context: Context, message: NornMessage, position: Int) {
NornDatabase.Helper(context).run { db ->
val media = Media(this.mimeType, this.data, message.id, this.name, position)
db.mediaDao().insertMedia(media)
}
}
/**
* Chiffre le contenu du message
*
* @param context Contexte courant
* @param contact Destinataire du message
* @return document chiffré
*/
fun encrypt(context: Context, contact: NornContact): ByteArray {
val curve = Curve.getCurve(context)
val cipher = curve.getCipher(contact.pubKey!!)
return cipher.encrypt(this.data)
}
/**
* Chiffre le contenu du message en fonction de la conversation
*
* @param context Contexte courant
* @param message Message auquel le document est associé
*/
fun getContent(context: Context, message: NornMessage): ByteArray? {
return if (message.conversation.secured && message.ciphertext != null)
this.encrypt(context, message.contact)
else
this.data
}
/**
* Déchiffre le document
*
* @param context Contexte courant
* @param pubKey clé publique utilisée lors du chiffrement du message
* @return document déchiffré
*/
fun decrypt(context: Context, pubKey: ByteArray): ByteArray {
val curve = Curve.getCurve(context)
val cipher = curve.getCipher(pubKey)
return cipher.decrypt(this.data)
}
}

View file

@ -11,6 +11,7 @@ import xyz.johnny.norntalk.database.NornDatabase
import xyz.johnny.norntalk.database.entities.Message
import xyz.johnny.norntalk.security.Curve
import java.util.*
import kotlin.collections.ArrayList
/**
@ -127,6 +128,22 @@ class NornMessage constructor(text: String?, var ciphertext: String?, sender: St
*/
val addresses get() = conversation.members.map { it.number }.toTypedArray()
private var _medias : ArrayList<NornMedia>? = null
private val mediaLock = Object()
/**
* Medias attachés au message
*/
val medias get(): ArrayList<NornMedia> {
synchronized(mediaLock) {
return _medias!!
}
}
/**
* Sujet du MMS
*/
var subject: String = ""
init {
if (sender == null) {
this.sender = null
@ -135,6 +152,16 @@ class NornMessage constructor(text: String?, var ciphertext: String?, sender: St
this.sender = NornContact.getContact(sender, context)
this.contact = this.sender
}
if (this._dbMessage == null) {
this._medias = ArrayList()
} else {
NornDatabase.Helper(context).run { db ->
synchronized(mediaLock) {
val medias = db.mediaDao().getMediasFromMessage(this.id)
this._medias = ArrayList(medias.map { NornMedia.from(it) })
}
}
}
}
/**
@ -210,6 +237,12 @@ class NornMessage constructor(text: String?, var ciphertext: String?, sender: St
// Ajouter le message à la conversation
this.conversation.messages[this.id] = this
if (this.medias.isNotEmpty()) {
for (i in this.medias.indices) {
this.medias[i].insert(context, this, i)
}
}
// retourner le message
this.dbMessage
}
@ -250,7 +283,6 @@ class NornMessage constructor(text: String?, var ciphertext: String?, sender: St
*/
fun send(context: Context, insert: Boolean, raw: Boolean = false) {
Log.d(this::class.java.simpleName, "sending: " + this)
// envoyer le message
val transaction = NornTransaction(context)
transaction.sendNewMessage(this, insert, raw)
}
@ -286,6 +318,15 @@ class NornMessage constructor(text: String?, var ciphertext: String?, sender: St
this.sender!!.setPublicKey(Hex.decode(pubkey), context)
}
/**
* Envoyer le message chiffré en fonction de la conversation
*
* @param raw forcer l'envoi du message en clair
*/
fun messageBody(raw: Boolean) =
if (this.conversation.secured && this.ciphertext != null && !raw) this.ciphertext!!
else this.text
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

View file

@ -19,7 +19,6 @@ package xyz.johnny.norntalk.messages
import android.app.Activity
import android.app.PendingIntent
import android.content.*
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.os.Looper
@ -72,43 +71,27 @@ class NornTransaction constructor(private val context: Context) {
}
}
fun sendNewMessage(message: NornMessage, insert: Boolean, raw: Boolean) {
this.sendSmsMessage(message, insert, raw)
}
/**
* Called to send a new message depending on settings and provided Message object
* If you want to send message as mms, call this from the UI thread
* Evoyer un message à partir d'une instance de [NornMessage]
*
* @param message is the message that you want to send
* @param message Message à envoyer
* @param insert Si Vrai le message doit être inséré dans la base de données, Faux sinon
* cela permet de cacher les messages d'échange de clés à l'utilisateur
* @param raw Si Vrai le message sera envoyé en clair, sinon le message sera chiffré en fonction
* des propriétés de la conversation
*/
fun sendNewMessage(message: Message) {
this.saveMessage = message.save
// if message:
// 1) Has images attached
// or
// 1) is enabled to send long messages as mms
// 2) number of pages for that sms exceeds value stored in settings for when to send the mms by
// 3) prefer voice is disabled
// or
// 1) more than one address is attached
// 2) group messaging is enabled
//
// then, send as MMS, else send as Voice or SMS
if (checkMMS(message)) {
fun sendNewMessage(message: NornMessage, insert: Boolean, raw: Boolean) {
if (message.medias.isEmpty()) {
this.sendSmsMessage(message, insert, raw)
} else {
try {
Looper.prepare()
} catch (e: Exception) {
}
} catch (e: Exception) {}
RateController.init(context)
DownloadManager.init(context)
sendMmsMessage(message.text, message.addresses, message.images, message.imageNames, message.parts, message.subject)
}// else {
// sendSmsMessage(message.text, message.addresses, message.delay)
//}
this.sendMmsMessage(message)
}
}
/**
@ -153,8 +136,7 @@ class NornTransaction constructor(private val context: Context) {
private fun sendSmsMessage(message: NornMessage, insert: Boolean, raw: Boolean) {
// envoyer le message chiffré si la conversation est sécurisée
val body = if (message.conversation.secured && message.ciphertext != null && !raw) message.ciphertext!!
else message.text
val body = message.messageBody(raw)
// save the message for each of the addresses
for (i in message.addresses.indices) {
@ -258,47 +240,22 @@ class NornTransaction constructor(private val context: Context) {
}
}
private fun sendMmsMessage(text: String?, addresses: Array<String>, image: Array<Bitmap>, imageNames: Array<String>?, parts: List<Message.Part>?, subject: String) {
// merge the string[] of addresses into a single string so they can be inserted into the database easier
var address = ""
for (i in addresses.indices) {
address += addresses[i] + " "
}
address = address.trim { it <= ' ' }
// create the parts to send
private fun sendMmsMessage(message: NornMessage) {
// create the medias to send
val data = ArrayList<MMSPart>()
for (i in image.indices) {
// turn bitmap into byte array to be stored
val imageBytes = Message.bitmapToByteArray(image[i])
val part = MMSPart()
part.MimeType = "image/jpeg"
part.Name = if (imageNames != null) imageNames[i] else "image_" + System.currentTimeMillis()
part.Data = imageBytes
data.add(part)
}
// add any extra media according to their mimeType set in the message
// eg. videos, audio, contact cards, location maybe?
if (parts != null) {
for (p in parts) {
val part = MMSPart()
if (p.name != null) {
part.Name = p.name
} else {
part.Name = p.contentType.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
}
part.MimeType = p.contentType
part.Data = p.media
data.add(part)
}
for (m in message.medias) {
val part = MMSPart()
part.Name = m.name
part.MimeType = m.mimeType
part.Data = m.getContent(context, message)
data.add(part)
}
if (text != null && text != "") {
val text = message.messageBody(false)
if (message.text != "") {
// add text to the end of the part and send
val part = MMSPart()
part.Name = "text"
@ -308,8 +265,7 @@ class NornTransaction constructor(private val context: Context) {
}
Log.v(TAG, "using system method for sending")
sendMmsThroughSystem(context, subject, data, addresses, explicitSentMmsReceiver)
sendMmsThroughSystem(context, message.subject, data, message.addresses, explicitSentMmsReceiver)
}
class MessageInfo {