Skip to content

Commit

Permalink
Merge pull request #403 from jorgeblacio/recover_models
Browse files Browse the repository at this point in the history
Now models that need init values are properly recovered after app suspension.
  • Loading branch information
danieltigse authored Feb 20, 2019
2 parents 9481cad + 787758f commit c47a679
Show file tree
Hide file tree
Showing 17 changed files with 278 additions and 19 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ android {
defaultConfig {
minSdkVersion 21
targetSdkVersion 28
versionCode 49
versionName "0.16.7"
versionCode 50
versionName "0.16.8"
applicationId "com.criptext.mail"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
Expand Down
109 changes: 108 additions & 1 deletion src/main/kotlin/com/criptext/mail/BaseActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.PersistableBundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
Expand All @@ -17,13 +18,17 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import com.criptext.mail.db.KeyValueStorage
import com.criptext.mail.db.models.Label
import com.criptext.mail.email_preview.EmailPreview
import com.criptext.mail.push.data.IntentExtrasData
import com.criptext.mail.push.services.LinkDeviceActionService
import com.criptext.mail.push.services.NewMailActionService
import com.criptext.mail.push.services.SyncDeviceActionService
import com.criptext.mail.scenes.ActivityMessage
import com.criptext.mail.scenes.SceneController
import com.criptext.mail.scenes.SceneModel
import com.criptext.mail.scenes.composer.ComposerModel
import com.criptext.mail.scenes.composer.data.ComposerType
import com.criptext.mail.scenes.emaildetail.EmailDetailSceneModel
import com.criptext.mail.scenes.linking.LinkingModel
import com.criptext.mail.scenes.mailbox.MailboxActivity
Expand Down Expand Up @@ -64,6 +69,7 @@ import com.github.omadahealth.lollipin.lib.managers.AppLock
import com.google.firebase.analytics.FirebaseAnalytics
import droidninja.filepicker.FilePickerBuilder
import droidninja.filepicker.FilePickerConst
import kotlinx.android.synthetic.main.contact_item.*
import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper
import java.io.File
import java.lang.Exception
Expand Down Expand Up @@ -116,6 +122,56 @@ abstract class BaseActivity: PinCompatActivity(), IHostActivity {
return cachedModels[javaClass]
}

private fun getSavedInstanceModel(savedInstanceState: Bundle): SceneModel? {
return if(savedInstanceState.getString("type") != null){
when(savedInstanceState.getString("type")){
EMAIL_DETAIL_MODEL -> {
EmailDetailSceneModel(
threadId = savedInstanceState.getString("threadId")!!,
currentLabel = Label.fromJSON(savedInstanceState.getString("currentLabel")!!),
threadPreview = EmailPreview.emailPreviewFromJSON(savedInstanceState.getString("threadPreview")!!),
doReply = savedInstanceState.getBoolean("doReply")
)
}
COMPOSER_MODEL -> {
ComposerModel(
type = ComposerType.fromJSON(savedInstanceState.getString("composerType")!!, this)
)
}
PRIVACY_AND_SECURITY_MODEL -> {
PrivacyAndSecurityModel(
hasReadReceipts = savedInstanceState.getBoolean("hasReadReceipts")
)
}
PROFILE_MODEL -> {
ProfileModel(
name = savedInstanceState.getString("name")!!,
email = savedInstanceState.getString("email")!!,
exitToMailbox = savedInstanceState.getBoolean("exitToMailbox")
)
}
RECOVERY_EMAIL_MODEL -> {
RecoveryEmailModel(
isEmailConfirmed = savedInstanceState.getBoolean("isEmailConfirmed"),
recoveryEmail = savedInstanceState.getString("recoveryEmail")!!
)
}
REPLY_TO_MODEL -> {
ReplyToModel(
replyToEmail = savedInstanceState.getString("replyToEmail")!!
)
}
SIGNATURE_MODEL -> {
SignatureModel(
recipientId = savedInstanceState.getString("recipientId")!!
)
}
else -> null
}
}else
null
}

private fun dismissAllNotifications() {
val notificationManager = this.applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancelAll()
Expand All @@ -139,7 +195,9 @@ abstract class BaseActivity: PinCompatActivity(), IHostActivity {
setSupportActionBar(toolbar)
}

val cacheModel = getCachedModel()
val cacheModel = if(savedInstanceState == null) getCachedModel()
else getSavedInstanceModel(savedInstanceState)

if(cacheModel == null){
restartApplication()
return
Expand Down Expand Up @@ -178,6 +236,47 @@ abstract class BaseActivity: PinCompatActivity(), IHostActivity {
return true
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val currentModel = model
when(currentModel) {
is EmailDetailSceneModel -> {
outState.putString("type", EMAIL_DETAIL_MODEL)
outState.putString("threadId", currentModel.threadId)
outState.putString("currentLabel", Label.toJSON(currentModel.currentLabel).toString())
outState.putString("threadPreview", EmailPreview.emailPreviewToJSON(currentModel.threadPreview))
outState.putBoolean("doReply", currentModel.doReply)
}
is ComposerModel -> {
outState.putString("type", COMPOSER_MODEL)
outState.putString("composerType", ComposerType.toJSON(currentModel.type))
}
is PrivacyAndSecurityModel -> {
outState.putString("type", PRIVACY_AND_SECURITY_MODEL)
outState.putBoolean("hasReadReceipts", currentModel.hasReadReceipts)
}
is ProfileModel -> {
outState.putString("type", PROFILE_MODEL)
outState.putString("name", currentModel.name)
outState.putString("email", currentModel.email)
outState.putBoolean("exitToMailbox", currentModel.exitToMailbox)
}
is RecoveryEmailModel -> {
outState.putString("type", RECOVERY_EMAIL_MODEL)
outState.putBoolean("isEmailConfirmed", currentModel.isEmailConfirmed)
outState.putString("recoveryEmail", currentModel.recoveryEmail)
}
is ReplyToModel -> {
outState.putString("type", REPLY_TO_MODEL)
outState.putString("replyToEmail", currentModel.replyToEmail)
}
is SignatureModel -> {
outState.putString("type", SIGNATURE_MODEL)
outState.putString("recipientId", currentModel.recipientId)
}
}
}

override fun onBackPressed() {
val shouldCallSuper = controller.onBackPressed()
if (shouldCallSuper) super.onBackPressed()
Expand Down Expand Up @@ -530,6 +629,14 @@ abstract class BaseActivity: PinCompatActivity(), IHostActivity {
fun setCachedModel(clazz: Class<*>, model: Any) {
cachedModels[clazz] = model
}

private const val EMAIL_DETAIL_MODEL = "EmailDetailModel"
private const val COMPOSER_MODEL = "ComposerModel"
private const val PRIVACY_AND_SECURITY_MODEL = "PrivacyAndSecurityModel"
private const val PROFILE_MODEL = "ProfileModel"
private const val RECOVERY_EMAIL_MODEL = "RecoveryEmailModel"
private const val REPLY_TO_MODEL = "ReplyToModel"
private const val SIGNATURE_MODEL = "SignatureModel"
}

enum class RequestCode {
Expand Down
9 changes: 4 additions & 5 deletions src/main/kotlin/com/criptext/mail/aes/AESUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,11 @@ class AESUtil(keyAndIV: String) {

fun decryptWithPassword(password: String, salt: String, iv: String, dataToDecrypt: ByteArray): String{

val generator = PKCS5S2ParametersGenerator(SHA256Digest())
generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(password.toCharArray()), Encoding.stringToByteArray(salt), 10000)
val key = generator.generateDerivedMacParameters(128) as KeyParameter

val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
val spec = PBEKeySpec(password.toCharArray(), Encoding.stringToByteArray(salt),
10000, 128)
val tmp = factory.generateSecret(spec)
val skey = SecretKeySpec(tmp.encoded, "AES")
val skey = SecretKeySpec(key.key, "AES")

val ivspec = IvParameterSpec(Encoding.stringToByteArray(iv))

Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/com/criptext/mail/db/models/Contact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ open class Contact(
score = 0
)
}

fun toJSON(contact: Contact): JSONObject{
val json = JSONObject()
json.put("id", contact.id)
json.put("email", contact.email)
json.put("name", contact.name)
json.put("isTrusted", contact.isTrusted)
json.put("score", contact.score)
return json
}
}

class Invalid(email: String, name: String): Contact(0, email, name, false, 0)
Expand Down
11 changes: 11 additions & 0 deletions src/main/kotlin/com/criptext/mail/db/models/Label.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ data class Label (
uuid = uuid
)
}

fun toJSON(label: Label): JSONObject {
val json = JSONObject()
json.put("id", label.id)
json.put("color", label.color)
json.put("text", label.text)
json.put("type", label.type)
json.put("visible", label.visible)
json.put("uuid", label.uuid)
return json
}
}

class DefaultItems {
Expand Down
39 changes: 39 additions & 0 deletions src/main/kotlin/com/criptext/mail/email_preview/EmailPreview.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.criptext.mail.email_preview
import com.criptext.mail.db.DeliveryTypes
import com.criptext.mail.db.models.Contact
import com.criptext.mail.scenes.mailbox.data.EmailThread
import com.criptext.mail.utils.DateAndTimeUtils
import org.json.JSONObject
import java.util.*

/**
Expand Down Expand Up @@ -30,5 +32,42 @@ data class EmailPreview(val subject: String, val topText: String, val bodyPrevie
isSelected = false, isStarred = e.isStarred, hasFiles = e.hasFiles,
latestEmailUnsentDate = e.latestEmail.email.unsentDate, metadataKey = e.metadataKey)
}

fun emailPreviewToJSON(e: EmailPreview): String {
val json = JSONObject()
json.put("subject", e.subject)
json.put("topText", e.topText)
json.put("bodyPreview", e.bodyPreview)
json.put("sender", Contact.toJSON(e.sender))
json.put("emailId", e.emailId)
json.put("deliveryStatus", DeliveryTypes.getTrueOrdinal(e.deliveryStatus))
json.put("unread", e.unread)
json.put("count", e.count)
json.put("timestamp", DateAndTimeUtils.printDateWithServerFormat(e.timestamp))
json.put("threadId", e.threadId)
json.put("isSelected", e.isSelected)
json.put("isStarred", e.isStarred)
json.put("hasFiles", e.hasFiles)
if(e.latestEmailUnsentDate != null)
json.put("latestEmailUnsentDate", DateAndTimeUtils.printDateWithServerFormat(e.latestEmailUnsentDate))
json.put("metadataKey", e.metadataKey)

return json.toString()
}

fun emailPreviewFromJSON(e: String): EmailPreview {
val json = JSONObject(e)
return EmailPreview(subject = json.getString("subject"), bodyPreview = json.getString("topText"),
topText = json.getString("bodyPreview"), sender = Contact.fromJSON(json.getString("sender").toString()),
emailId = json.getLong("emailId"), deliveryStatus = DeliveryTypes.fromInt(json.getInt("deliveryStatus")),
unread = json.getBoolean("unread"), count = json.getInt("count"),
timestamp = DateAndTimeUtils.getDateFromString(
json.getString("timestamp"), null), threadId = json.getString("threadId"),
isSelected = json.getBoolean("isSelected"), isStarred = json.getBoolean("isStarred"),
hasFiles = json.getBoolean("hasFiles"),
latestEmailUnsentDate = if(json.has("latestEmailUnsentDate"))
DateAndTimeUtils.getDateFromString(
json.getString("latestEmailUnsentDate"), null) else null, metadataKey = json.getLong("metadataKey"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.criptext.mail.scenes.composer
import com.criptext.mail.db.models.Contact
import com.criptext.mail.db.models.Label
import com.criptext.mail.email_preview.EmailPreview
import com.criptext.mail.scenes.SceneModel
import com.criptext.mail.scenes.composer.data.ComposerAttachment
import com.criptext.mail.scenes.composer.data.ComposerType
import com.criptext.mail.validation.FormInputState
Expand All @@ -12,7 +13,7 @@ import java.util.*
* Created by gabriel on 2/26/18.
*/

class ComposerModel(val type: ComposerType) {
class ComposerModel(val type: ComposerType): SceneModel {

val isReplyOrDraft: Boolean = type is ComposerType.Reply
|| type is ComposerType.ReplyAll || type is ComposerType.Draft
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.criptext.mail.scenes.composer.data

import android.content.Context
import com.criptext.mail.api.toList
import com.criptext.mail.db.models.Label
import com.criptext.mail.email_preview.EmailPreview
import com.criptext.mail.utils.mailtemplates.CriptextMailTemplate
import com.criptext.mail.utils.mailtemplates.FWMailTemplate
import com.criptext.mail.utils.mailtemplates.REMailTemplate
import com.criptext.mail.utils.mailtemplates.SupportMailTemplate
import org.json.JSONArray
import org.json.JSONObject

/**
* Created by sebas on 3/27/18.
Expand All @@ -26,4 +30,83 @@ sealed class ComposerType {
data class Support(val template: SupportMailTemplate): ComposerType()
data class MailTo(val to: String): ComposerType()
data class Send(val files: List<Pair<String, Long>>): ComposerType()

companion object {
fun fromJSON(jsonString: String, context: Context) : ComposerType {
val json = JSONObject(jsonString)
return when(json.getInt("type")){
Type.DRAFT.ordinal -> {
Draft(draftId = json.getLong("draftId"),
threadPreview = EmailPreview.emailPreviewFromJSON(json.getString("threadPreview")!!),
currentLabel = Label.fromJSON(json.getString("currentLabel")!!))
}
Type.REPLY.ordinal -> {
Reply(originalId = json.getLong("originalId"),
threadPreview = EmailPreview.emailPreviewFromJSON(json.getString("threadPreview")!!),
currentLabel = Label.fromJSON(json.getString("currentLabel")!!),
template = REMailTemplate(context))
}
Type.REPLYALL.ordinal -> {
ReplyAll(originalId = json.getLong("originalId"),
threadPreview = EmailPreview.emailPreviewFromJSON(json.getString("threadPreview")!!),
currentLabel = Label.fromJSON(json.getString("currentLabel")!!),
template = REMailTemplate(context))
}
Type.FORWARD.ordinal -> {
Forward(originalId = json.getLong("draftId"),
threadPreview = EmailPreview.emailPreviewFromJSON(json.getString("threadPreview")!!),
currentLabel = Label.fromJSON(json.getString("currentLabel")!!),
template = FWMailTemplate(context))
}
Type.SUPPORT.ordinal -> {
Support(SupportMailTemplate(context))
}
Type.MAILTO.ordinal -> {
MailTo(to = json.getString("to"))
}
else -> Empty()
}
}

fun toJSON(c: ComposerType) : String{
val json = JSONObject()
when(c){
is Draft -> {
json.put("type", Type.DRAFT.ordinal)
json.put("draftId", c.draftId)
json.put("currentLabel", Label.toJSON(c.currentLabel))
json.put("threadPreview", EmailPreview.emailPreviewToJSON(c.threadPreview))
}
is Empty -> json.put("type", Type.EMPTY.ordinal)
is Reply -> {
json.put("type", Type.REPLY.ordinal)
json.put("originalId", c.originalId)
json.put("currentLabel", Label.toJSON(c.currentLabel))
json.put("threadPreview", EmailPreview.emailPreviewToJSON(c.threadPreview))
}
is ReplyAll -> {
json.put("type", Type.REPLYALL.ordinal)
json.put("originalId", c.originalId)
json.put("currentLabel", Label.toJSON(c.currentLabel))
json.put("threadPreview", EmailPreview.emailPreviewToJSON(c.threadPreview))
}
is Forward -> {
json.put("type", Type.FORWARD.ordinal)
json.put("originalId", c.originalId)
json.put("currentLabel", Label.toJSON(c.currentLabel))
json.put("threadPreview", EmailPreview.emailPreviewToJSON(c.threadPreview))
}
is Support -> json.put("type", Type.SUPPORT.ordinal)
is MailTo -> {
json.put("type", Type.MAILTO.ordinal)
json.put("to", c.to)
}
}
return json.toString()
}

private enum class Type {
EMPTY, DRAFT, REPLY, REPLYALL, FORWARD, SUPPORT, MAILTO, SEND;
}
}
}
Loading

0 comments on commit c47a679

Please sign in to comment.