10 Commits

21 changed files with 340 additions and 262 deletions

View File

@ -20,8 +20,8 @@ android {
applicationId "ch.dissem.apps.${appName.toLowerCase()}" applicationId "ch.dissem.apps.${appName.toLowerCase()}"
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 27 targetSdkVersion 27
versionCode 18 versionCode 20
versionName "1.0-beta18" versionName "1.0-beta20"
multiDexEnabled true multiDexEnabled true
} }
compileOptions { compileOptions {

View File

@ -220,6 +220,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
val drawerItems = ArrayList<IDrawerItem<*, *>>() val drawerItems = ArrayList<IDrawerItem<*, *>>()
drawerItems.add(PrimaryDrawerItem() drawerItems.add(PrimaryDrawerItem()
.withIdentifier(LABEL_ARCHIVE.id as Long)
.withName(R.string.archive) .withName(R.string.archive)
.withTag(LABEL_ARCHIVE) .withTag(LABEL_ARCHIVE)
.withIcon(CommunityMaterial.Icon.cmd_archive) .withIcon(CommunityMaterial.Icon.cmd_archive)
@ -343,7 +344,7 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
} }
Singleton.getMessageListener(this).resetNotification() Singleton.getMessageListener(this).resetNotification()
currentLabel.addObserver(this) { label -> currentLabel.addObserver(this) { label ->
if (label != null) { if (label != null && label.id is Long) {
drawer.setSelection(label.id as Long) drawer.setSelection(label.id as Long)
} }
} }

View File

@ -19,8 +19,8 @@ package ch.dissem.apps.abit.listener
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import ch.dissem.apps.abit.service.BitmessageService
import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.Preferences
import org.jetbrains.anko.connectivityManager import org.jetbrains.anko.connectivityManager
@ -29,7 +29,7 @@ class WifiReceiver : BroadcastReceiver() {
if ("android.net.conn.CONNECTIVITY_CHANGE" == intent.action) { if ("android.net.conn.CONNECTIVITY_CHANGE" == intent.action) {
val bmc = Singleton.getBitmessageContext(ctx) val bmc = Singleton.getBitmessageContext(ctx)
if (Preferences.isFullNodeActive(ctx) && !bmc.isRunning() && !(Preferences.isWifiOnly(ctx) && ctx.connectivityManager.isActiveNetworkMetered)) { if (Preferences.isFullNodeActive(ctx) && !bmc.isRunning() && !(Preferences.isWifiOnly(ctx) && ctx.connectivityManager.isActiveNetworkMetered)) {
ctx.startService(Intent(ctx, BitmessageService::class.java)) NetworkUtils.doStartBitmessageService(ctx)
} }
} }
} }

View File

@ -17,8 +17,13 @@
package ch.dissem.apps.abit.notification package ch.dissem.apps.abit.notification
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.os.Build
import android.support.annotation.ColorRes
import android.support.v4.content.ContextCompat
import ch.dissem.apps.abit.R
import org.jetbrains.anko.notificationManager import org.jetbrains.anko.notificationManager
/** /**
@ -46,4 +51,30 @@ abstract class AbstractNotification(ctx: Context) {
showing = false showing = false
manager.cancel(notificationId) manager.cancel(notificationId)
} }
protected fun initChannel(channelId: String, @ColorRes color: Int = R.color.colorPrimary) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ctx.notificationManager.createNotificationChannel(
NotificationChannel(
channelId,
ctx.getText(R.string.app_name),
NotificationManager.IMPORTANCE_LOW
).apply {
lightColor = ContextCompat.getColor(ctx, color)
lockscreenVisibility = Notification.VISIBILITY_PRIVATE
}
)
}
}
companion object {
internal const val ONGOING_CHANNEL_ID = "abit.ongoing"
internal const val MESSAGE_CHANNEL_ID = "abit.message"
internal const val ERROR_CHANNEL_ID = "abit.error"
init {
}
}
} }

View File

@ -30,10 +30,14 @@ import ch.dissem.apps.abit.R
*/ */
class ErrorNotification(ctx: Context) : AbstractNotification(ctx) { class ErrorNotification(ctx: Context) : AbstractNotification(ctx) {
private val builder = NotificationCompat.Builder(ctx, "abit.error") private val builder = NotificationCompat.Builder(ctx, ERROR_CHANNEL_ID)
.setContentTitle(ctx.getString(R.string.app_name)) .setContentTitle(ctx.getString(R.string.app_name))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
init {
initChannel(ERROR_CHANNEL_ID, R.color.colorPrimaryDark)
}
fun setWarning(@StringRes resId: Int, vararg args: Any): ErrorNotification { fun setWarning(@StringRes resId: Int, vararg args: Any): ErrorNotification {
builder.setSmallIcon(R.drawable.ic_notification_warning) builder.setSmallIcon(R.drawable.ic_notification_warning)
.setContentText(ctx.getString(resId, *args)) .setContentText(ctx.getString(resId, *args))

View File

@ -34,18 +34,19 @@ import kotlin.concurrent.fixedRateTimer
*/ */
class NetworkNotification(ctx: Context) : AbstractNotification(ctx) { class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
private val builder = NotificationCompat.Builder(ctx, "abit.network") private val builder = NotificationCompat.Builder(ctx, ONGOING_CHANNEL_ID)
private var timer: Timer? = null private var timer: Timer? = null
init { init {
initChannel(ONGOING_CHANNEL_ID, R.color.colorAccent)
val showAppIntent = Intent(ctx, MainActivity::class.java) val showAppIntent = Intent(ctx, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(ctx, 1, showAppIntent, 0) val pendingIntent = PendingIntent.getActivity(ctx, 1, showAppIntent, 0)
builder builder
.setSmallIcon(R.drawable.ic_notification_full_node) .setSmallIcon(R.drawable.ic_notification_full_node)
.setContentTitle(ctx.getString(R.string.bitmessage_full_node)) .setContentTitle(ctx.getString(R.string.bitmessage_full_node))
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setShowWhen(false) .setShowWhen(false)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
} }
@SuppressLint("StringFormatMatches") @SuppressLint("StringFormatMatches")
@ -63,11 +64,19 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
val streamNumber = Integer.parseInt(stream.name.substring("stream ".length)) val streamNumber = Integer.parseInt(stream.name.substring("stream ".length))
val nodeCount = stream.getProperty("nodes")!!.value as Int? val nodeCount = stream.getProperty("nodes")!!.value as Int?
if (nodeCount == 1) { if (nodeCount == 1) {
info.append(ctx.getString(R.string.connection_info_1, info.append(
streamNumber)) ctx.getString(
R.string.connection_info_1,
streamNumber
)
)
} else { } else {
info.append(ctx.getString(R.string.connection_info_n, info.append(
streamNumber, nodeCount)) ctx.getString(
R.string.connection_info_n,
streamNumber, nodeCount
)
)
} }
info.append('\n') info.append('\n')
} }
@ -77,14 +86,18 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
val intent = Intent(ctx, BitmessageIntentService::class.java) val intent = Intent(ctx, BitmessageIntentService::class.java)
if (running) { if (running) {
intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true) intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true)
builder.addAction(R.drawable.ic_notification_node_stop, builder.addAction(
ctx.getString(R.string.full_node_stop), R.drawable.ic_notification_node_stop,
PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)) ctx.getString(R.string.full_node_stop),
PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)
)
} else { } else {
intent.putExtra(BitmessageIntentService.EXTRA_STARTUP_NODE, true) intent.putExtra(BitmessageIntentService.EXTRA_STARTUP_NODE, true)
builder.addAction(R.drawable.ic_notification_node_start, builder.addAction(
ctx.getString(R.string.full_node_restart), R.drawable.ic_notification_node_start,
PendingIntent.getService(ctx, 1, intent, FLAG_UPDATE_CURRENT)) ctx.getString(R.string.full_node_restart),
PendingIntent.getService(ctx, 1, intent, FLAG_UPDATE_CURRENT)
)
} }
notification = builder.build() notification = builder.build()
return running return running
@ -116,13 +129,15 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
val intent = Intent(ctx, BitmessageIntentService::class.java) val intent = Intent(ctx, BitmessageIntentService::class.java)
intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true) intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true)
builder.mActions.clear() builder.mActions.clear()
builder.addAction(R.drawable.ic_notification_node_stop, builder.addAction(
ctx.getString(R.string.full_node_stop), R.drawable.ic_notification_node_stop,
PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)) ctx.getString(R.string.full_node_stop),
PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)
)
notification = builder.build() notification = builder.build()
} }
companion object { companion object {
val NETWORK_NOTIFICATION_ID = 2 const val NETWORK_NOTIFICATION_ID = 2
} }
} }

View File

@ -42,36 +42,48 @@ import ch.dissem.apps.abit.util.Drawables.toBitmap
class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) { class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
init {
initChannel(MESSAGE_CHANNEL_ID, R.color.colorPrimary)
}
fun singleNotification(plaintext: Plaintext): NewMessageNotification { fun singleNotification(plaintext: Plaintext): NewMessageNotification {
val builder = NotificationCompat.Builder(ctx, CHANNEL_ID) val builder = NotificationCompat.Builder(ctx, MESSAGE_CHANNEL_ID)
val bigText = SpannableString(plaintext.subject + "\n" + plaintext.text) val bigText = SpannableString(plaintext.subject + "\n" + plaintext.text)
plaintext.subject?.let { subject -> plaintext.subject?.let { subject ->
bigText.setSpan(SPAN_EMPHASIS, 0, subject.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE) bigText.setSpan(SPAN_EMPHASIS, 0, subject.length, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
} }
builder.setSmallIcon(R.drawable.ic_notification_new_message) builder.setSmallIcon(R.drawable.ic_notification_new_message)
.setLargeIcon(toBitmap(Identicon(plaintext.from), 192)) .setLargeIcon(toBitmap(Identicon(plaintext.from), 192))
.setContentTitle(plaintext.from.toString()) .setContentTitle(plaintext.from.toString())
.setContentText(plaintext.subject) .setContentText(plaintext.subject)
.setStyle(BigTextStyle().bigText(bigText)) .setStyle(BigTextStyle().bigText(bigText))
.setContentInfo("Info") .setContentInfo("Info")
builder.setContentIntent( builder.setContentIntent(
createActivityIntent(EXTRA_SHOW_MESSAGE, plaintext)) createActivityIntent(EXTRA_SHOW_MESSAGE, plaintext)
builder.addAction(R.drawable.ic_action_reply, ctx.getString(R.string.reply), )
createActivityIntent(EXTRA_REPLY_TO_MESSAGE, plaintext)) builder.addAction(
builder.addAction(R.drawable.ic_action_delete, ctx.getString(R.string.delete), R.drawable.ic_action_reply, ctx.getString(R.string.reply),
createServiceIntent(ctx, EXTRA_DELETE_MESSAGE, plaintext)) createActivityIntent(EXTRA_REPLY_TO_MESSAGE, plaintext)
)
builder.addAction(
R.drawable.ic_action_delete, ctx.getString(R.string.delete),
createServiceIntent(ctx, EXTRA_DELETE_MESSAGE, plaintext)
)
notification = builder.build() notification = builder.build()
return this return this
} }
private fun createActivityIntent(action: String, message: Plaintext): PendingIntent { private fun createActivityIntent(action: String, message: Plaintext): PendingIntent {
val intent = Intent(ctx, MainActivity::class.java) val intent = Intent(ctx, MainActivity::class.java).putExtra(action, message)
intent.putExtra(action, message)
return PendingIntent.getActivity(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT) return PendingIntent.getActivity(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT)
} }
private fun createServiceIntent(ctx: Context, action: String, message: Plaintext): PendingIntent { private fun createServiceIntent(
ctx: Context,
action: String,
message: Plaintext
): PendingIntent {
val intent = Intent(ctx, BitmessageIntentService::class.java) val intent = Intent(ctx, BitmessageIntentService::class.java)
intent.putExtra(action, message) intent.putExtra(action, message)
return PendingIntent.getService(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT) return PendingIntent.getService(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT)
@ -82,19 +94,24 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
* * accessed it will be in a `synchronized(unacknowledged) * * accessed it will be in a `synchronized(unacknowledged)
* * {}` block * * {}` block
*/ */
fun multiNotification(unacknowledged: Collection<Plaintext>, numberOfUnacknowledgedMessages: Int): NewMessageNotification { fun multiNotification(
val builder = NotificationCompat.Builder(ctx, CHANNEL_ID) unacknowledged: Collection<Plaintext>,
numberOfUnacknowledgedMessages: Int
): NewMessageNotification {
val builder = NotificationCompat.Builder(ctx, MESSAGE_CHANNEL_ID)
builder.setSmallIcon(R.drawable.ic_notification_new_message) builder.setSmallIcon(R.drawable.ic_notification_new_message)
.setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages)) .setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages))
.setContentText(ctx.getString(R.string.app_name)) .setContentText(ctx.getString(R.string.app_name))
val inboxStyle = InboxStyle() val inboxStyle = InboxStyle()
synchronized(unacknowledged) { synchronized(unacknowledged) {
for (msg in unacknowledged) { for (msg in unacknowledged) {
val sb = SpannableString(msg.from.toString() + " " + msg.subject) val sb = SpannableString(msg.from.toString() + " " + msg.subject)
sb.setSpan(SPAN_EMPHASIS, 0, msg.from.toString().length, Spannable sb.setSpan(
.SPAN_INCLUSIVE_EXCLUSIVE) SPAN_EMPHASIS, 0, msg.from.toString().length, Spannable
.SPAN_INCLUSIVE_EXCLUSIVE
)
inboxStyle.addLine(sb) inboxStyle.addLine(sb)
} }
} }
@ -113,6 +130,5 @@ class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) {
companion object { companion object {
private val NEW_MESSAGE_NOTIFICATION_ID = 1 private val NEW_MESSAGE_NOTIFICATION_ID = 1
private val SPAN_EMPHASIS = StyleSpan(Typeface.BOLD) private val SPAN_EMPHASIS = StyleSpan(Typeface.BOLD)
private val CHANNEL_ID = "abit.message"
} }
} }

View File

@ -33,7 +33,7 @@ import kotlin.concurrent.fixedRateTimer
*/ */
class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) { class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
private val builder = NotificationCompat.Builder(ctx, "abit.pow") private val builder = NotificationCompat.Builder(ctx, ONGOING_CHANNEL_ID)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setUsesChronometer(true) .setUsesChronometer(true)
.setOngoing(true) .setOngoing(true)
@ -46,6 +46,7 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
private var timer: Timer? = null private var timer: Timer? = null
init { init {
initChannel(ONGOING_CHANNEL_ID, R.color.colorAccent)
update(0) update(0)
} }
@ -67,10 +68,6 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
return this return this
} }
companion object {
const val ONGOING_NOTIFICATION_ID = 3
}
fun start(item: ProofOfWorkService.PowItem) { fun start(item: ProofOfWorkService.PowItem) {
val expectedPowTimeInMilliseconds = PowStats.getExpectedPowTimeInMilliseconds(ctx, item.targetValue) val expectedPowTimeInMilliseconds = PowStats.getExpectedPowTimeInMilliseconds(ctx, item.targetValue)
val delta = (expectedPowTimeInMilliseconds / 3).toInt() val delta = (expectedPowTimeInMilliseconds / 3).toInt()
@ -101,4 +98,8 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) {
show() show()
} }
} }
companion object {
const val ONGOING_NOTIFICATION_ID = 3
}
} }

View File

@ -86,8 +86,7 @@ class AndroidAddressRepository(private val sql: SqlHelper) : AddressRepository {
// you will actually use after this query. // you will actually use after this query.
val projection = arrayOf(COLUMN_ADDRESS) val projection = arrayOf(COLUMN_ADDRESS)
val db = sql.readableDatabase sql.readableDatabase.query(
db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
where, null, null, null, where, null, null, null,
orderBy orderBy
@ -106,8 +105,7 @@ class AndroidAddressRepository(private val sql: SqlHelper) : AddressRepository {
// you will actually use after this query. // you will actually use after this query.
val projection = arrayOf(COLUMN_ADDRESS, COLUMN_ALIAS, COLUMN_PUBLIC_KEY, COLUMN_PRIVATE_KEY, COLUMN_SUBSCRIBED, COLUMN_CHAN) val projection = arrayOf(COLUMN_ADDRESS, COLUMN_ALIAS, COLUMN_PUBLIC_KEY, COLUMN_PRIVATE_KEY, COLUMN_SUBSCRIBED, COLUMN_CHAN)
val db = sql.readableDatabase sql.readableDatabase.query(
db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
where, null, null, null, null where, null, null, null, null
).use { c -> ).use { c ->
@ -154,8 +152,7 @@ class AndroidAddressRepository(private val sql: SqlHelper) : AddressRepository {
} }
private fun exists(address: BitmessageAddress): Boolean { private fun exists(address: BitmessageAddress): Boolean {
val db = sql.readableDatabase sql.readableDatabase.rawQuery(
db.rawQuery(
"SELECT COUNT(*) FROM Address WHERE address=?", "SELECT COUNT(*) FROM Address WHERE address=?",
arrayOf(address.address) arrayOf(address.address)
).use { cursor -> ).use { cursor ->
@ -165,49 +162,45 @@ class AndroidAddressRepository(private val sql: SqlHelper) : AddressRepository {
} }
private fun update(address: BitmessageAddress) { private fun update(address: BitmessageAddress) {
val db = sql.writableDatabase
// Create a new map of values, where column names are the keys // Create a new map of values, where column names are the keys
val values = getContentValues(address) val values = getContentValues(address)
val update = db.update(TABLE_NAME, values, "address=?", arrayOf(address.address)) val update = sql.writableDatabase.update(TABLE_NAME, values, "address=?", arrayOf(address.address))
if (update < 0) { if (update < 0) {
LOG.error("Could not update address {}", address) LOG.error("Could not update address {}", address)
} }
} }
private fun insert(address: BitmessageAddress) { private fun insert(address: BitmessageAddress) {
val db = sql.writableDatabase
// Create a new map of values, where column names are the keys // Create a new map of values, where column names are the keys
val values = getContentValues(address) val values = getContentValues(address).apply {
values.put(COLUMN_ADDRESS, address.address) put(COLUMN_ADDRESS, address.address)
values.put(COLUMN_VERSION, address.version) put(COLUMN_VERSION, address.version)
values.put(COLUMN_CHAN, address.isChan) put(COLUMN_CHAN, address.isChan)
}
val insert = db.insert(TABLE_NAME, null, values) val insert = sql.writableDatabase.insert(TABLE_NAME, null, values)
if (insert < 0) { if (insert < 0) {
LOG.error("Could not insert address {}", address) LOG.error("Could not insert address {}", address)
} }
} }
private fun getContentValues(address: BitmessageAddress): ContentValues { private fun getContentValues(address: BitmessageAddress) = ContentValues().apply {
val values = ContentValues() address.alias?.let { put(COLUMN_ALIAS, it) }
address.alias?.let { values.put(COLUMN_ALIAS, it) }
address.pubkey?.let { pubkey -> address.pubkey?.let { pubkey ->
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
pubkey.writer().writeUnencrypted(out) pubkey.writer().writeUnencrypted(out)
values.put(COLUMN_PUBLIC_KEY, out.toByteArray()) put(COLUMN_PUBLIC_KEY, out.toByteArray())
} }
address.privateKey?.let { values.put(COLUMN_PRIVATE_KEY, Encode.bytes(it)) } address.privateKey?.let { put(COLUMN_PRIVATE_KEY, Encode.bytes(it)) }
if (address.isChan) { if (address.isChan) {
values.put(COLUMN_CHAN, true) put(COLUMN_CHAN, true)
} }
values.put(COLUMN_SUBSCRIBED, address.isSubscribed) put(COLUMN_SUBSCRIBED, address.isSubscribed)
return values
} }
override fun remove(address: BitmessageAddress) { override fun remove(address: BitmessageAddress) {
val db = sql.writableDatabase sql.writableDatabase.delete(TABLE_NAME, "address = ?", arrayOf(address.address))
db.delete(TABLE_NAME, "address = ?", arrayOf(address.address))
} }
override fun getAddress(address: String) = find("address = '$address'").firstOrNull() override fun getAddress(address: String) = find("address = '$address'").firstOrNull()

View File

@ -57,10 +57,10 @@ class AndroidInventory(private val sql: SqlHelper) : Inventory {
cache.put(stream, result) cache.put(stream, result)
val projection = arrayOf(COLUMN_HASH, COLUMN_EXPIRES) val projection = arrayOf(COLUMN_HASH, COLUMN_EXPIRES)
val db = sql.readableDatabase
db.query( sql.readableDatabase.query(
TABLE_NAME, projection, TABLE_NAME, projection,
"stream = $stream", null, null, null, null "stream = $stream", null, null, null, null
).use { c -> ).use { c ->
while (c.moveToNext()) { while (c.moveToNext()) {
val blob = c.getBlob(c.getColumnIndex(COLUMN_HASH)) val blob = c.getBlob(c.getColumnIndex(COLUMN_HASH))
@ -84,10 +84,9 @@ class AndroidInventory(private val sql: SqlHelper) : Inventory {
// you will actually use after this query. // you will actually use after this query.
val projection = arrayOf(COLUMN_VERSION, COLUMN_DATA) val projection = arrayOf(COLUMN_VERSION, COLUMN_DATA)
val db = sql.readableDatabase sql.readableDatabase.query(
db.query( TABLE_NAME, projection,
TABLE_NAME, projection, "hash = X'$vector'", null, null, null, null
"hash = X'$vector'", null, null, null, null
).use { c -> ).use { c ->
if (!c.moveToFirst()) { if (!c.moveToFirst()) {
LOG.info("Object requested that we don't have. IV: {}", vector) LOG.info("Object requested that we don't have. IV: {}", vector)
@ -115,11 +114,10 @@ class AndroidInventory(private val sql: SqlHelper) : Inventory {
where.append(" AND type IN (").append(types.joinToString(separator = "', '", prefix = "'", postfix = "'", transform = { it.number.toString() })).append(")") where.append(" AND type IN (").append(types.joinToString(separator = "', '", prefix = "'", postfix = "'", transform = { it.number.toString() })).append(")")
} }
val db = sql.readableDatabase
val result = LinkedList<ObjectMessage>() val result = LinkedList<ObjectMessage>()
db.query( sql.readableDatabase.query(
TABLE_NAME, projection, TABLE_NAME, projection,
where.toString(), null, null, null, null where.toString(), null, null, null, null
).use { c -> ).use { c ->
while (c.moveToNext()) { while (c.moveToNext()) {
val objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION)) val objectVersion = c.getInt(c.getColumnIndex(COLUMN_VERSION))
@ -139,31 +137,29 @@ class AndroidInventory(private val sql: SqlHelper) : Inventory {
LOG.trace("Storing object {}", iv) LOG.trace("Storing object {}", iv)
try { try {
val db = sql.writableDatabase
// Create a new map of values, where column names are the keys // Create a new map of values, where column names are the keys
val values = ContentValues() val values = ContentValues().apply {
values.put(COLUMN_HASH, objectMessage.inventoryVector.hash) put(COLUMN_HASH, objectMessage.inventoryVector.hash)
values.put(COLUMN_STREAM, objectMessage.stream) put(COLUMN_STREAM, objectMessage.stream)
values.put(COLUMN_EXPIRES, objectMessage.expiresTime) put(COLUMN_EXPIRES, objectMessage.expiresTime)
values.put(COLUMN_DATA, Encode.bytes(objectMessage)) put(COLUMN_DATA, Encode.bytes(objectMessage))
values.put(COLUMN_TYPE, objectMessage.type) put(COLUMN_TYPE, objectMessage.type)
values.put(COLUMN_VERSION, objectMessage.version) put(COLUMN_VERSION, objectMessage.version)
}
db.insertOrThrow(TABLE_NAME, null, values) sql.writableDatabase.insertOrThrow(TABLE_NAME, null, values)
getCache(objectMessage.stream).put(iv, objectMessage.expiresTime) getCache(objectMessage.stream).put(iv, objectMessage.expiresTime)
} catch (e: SQLiteConstraintException) { } catch (e: SQLiteConstraintException) {
LOG.trace(e.message, e) LOG.trace(e.message, e)
} }
} }
override fun contains(objectMessage: ObjectMessage) = getCache(objectMessage.stream).keys.contains(objectMessage.inventoryVector) override fun contains(objectMessage: ObjectMessage) = getCache(objectMessage.stream).keys.contains(objectMessage.inventoryVector)
override fun cleanup() { override fun cleanup() {
val fiveMinutesAgo = now - 5 * MINUTE val fiveMinutesAgo = now - 5 * MINUTE
val db = sql.writableDatabase sql.writableDatabase.delete(TABLE_NAME, "expires < ?", arrayOf(fiveMinutesAgo.toString()))
db.delete(TABLE_NAME, "expires < ?", arrayOf(fiveMinutesAgo.toString()))
cache.values.map { it.entries }.forEach { entries -> entries.removeAll { it.value < fiveMinutesAgo } } cache.values.map { it.entries }.forEach { entries -> entries.removeAll { it.value < fiveMinutesAgo } }
} }

View File

@ -85,7 +85,7 @@ class AndroidLabelRepository(private val sql: SqlHelper, private val context: Co
internal fun findLabels(msgId: Any) = find("id IN (SELECT label_id FROM Message_Label WHERE message_id=$msgId)") internal fun findLabels(msgId: Any) = find("id IN (SELECT label_id FROM Message_Label WHERE message_id=$msgId)")
companion object { companion object {
val LABEL_ARCHIVE = Label("archive", null, 0) val LABEL_ARCHIVE = Label("archive", null, 0).apply { id = Long.MAX_VALUE }
private const val TABLE_NAME = "Label" private const val TABLE_NAME = "Label"
private const val COLUMN_ID = "id" private const val COLUMN_ID = "id"

View File

@ -19,7 +19,6 @@ package ch.dissem.apps.abit.repository
import android.content.ContentValues import android.content.ContentValues
import android.database.Cursor import android.database.Cursor
import android.database.DatabaseUtils import android.database.DatabaseUtils
import android.database.sqlite.SQLiteConstraintException
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARCHIVE import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARCHIVE
import ch.dissem.apps.abit.util.UuidUtils import ch.dissem.apps.abit.util.UuidUtils
@ -29,7 +28,6 @@ import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.entity.valueobject.Label import ch.dissem.bitmessage.entity.valueobject.Label
import ch.dissem.bitmessage.ports.AbstractMessageRepository import ch.dissem.bitmessage.ports.AbstractMessageRepository
import ch.dissem.bitmessage.ports.AlreadyStoredException
import ch.dissem.bitmessage.ports.MessageRepository import ch.dissem.bitmessage.ports.MessageRepository
import ch.dissem.bitmessage.utils.Encode import ch.dissem.bitmessage.utils.Encode
import ch.dissem.bitmessage.utils.Strings.hex import ch.dissem.bitmessage.utils.Strings.hex
@ -137,8 +135,7 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo
// you will actually use after this query. // you will actually use after this query.
val projection = arrayOf(COLUMN_ID, COLUMN_IV, COLUMN_TYPE, COLUMN_SENDER, COLUMN_RECIPIENT, COLUMN_DATA, COLUMN_ACK_DATA, COLUMN_SENT, COLUMN_RECEIVED, COLUMN_STATUS, COLUMN_TTL, COLUMN_RETRIES, COLUMN_NEXT_TRY, COLUMN_CONVERSATION) val projection = arrayOf(COLUMN_ID, COLUMN_IV, COLUMN_TYPE, COLUMN_SENDER, COLUMN_RECIPIENT, COLUMN_DATA, COLUMN_ACK_DATA, COLUMN_SENT, COLUMN_RECEIVED, COLUMN_STATUS, COLUMN_TTL, COLUMN_RETRIES, COLUMN_NEXT_TRY, COLUMN_CONVERSATION)
val db = sql.readableDatabase sql.readableDatabase.query(
db.query(
TABLE_NAME, projection, TABLE_NAME, projection,
where, null, null, null, where, null, null, null,
"$COLUMN_RECEIVED DESC, $COLUMN_SENT DESC", "$COLUMN_RECEIVED DESC, $COLUMN_SENT DESC",
@ -206,23 +203,21 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo
} }
} }
private fun getValues(message: Plaintext): ContentValues { private fun getValues(message: Plaintext) = ContentValues(14).apply {
val values = ContentValues() put(COLUMN_IV, message.inventoryVector?.hash)
values.put(COLUMN_IV, message.inventoryVector?.hash) put(COLUMN_TYPE, message.type.name)
values.put(COLUMN_TYPE, message.type.name) put(COLUMN_SENDER, message.from.address)
values.put(COLUMN_SENDER, message.from.address) put(COLUMN_RECIPIENT, message.to?.address)
values.put(COLUMN_RECIPIENT, message.to?.address) put(COLUMN_DATA, Encode.bytes(message))
values.put(COLUMN_DATA, Encode.bytes(message)) put(COLUMN_ACK_DATA, message.ackData)
values.put(COLUMN_ACK_DATA, message.ackData) put(COLUMN_SENT, message.sent)
values.put(COLUMN_SENT, message.sent) put(COLUMN_RECEIVED, message.received)
values.put(COLUMN_RECEIVED, message.received) put(COLUMN_STATUS, message.status.name)
values.put(COLUMN_STATUS, message.status.name) put(COLUMN_INITIAL_HASH, message.initialHash)
values.put(COLUMN_INITIAL_HASH, message.initialHash) put(COLUMN_TTL, message.ttl)
values.put(COLUMN_TTL, message.ttl) put(COLUMN_RETRIES, message.retries)
values.put(COLUMN_RETRIES, message.retries) put(COLUMN_NEXT_TRY, message.nextTry)
values.put(COLUMN_NEXT_TRY, message.nextTry) put(COLUMN_CONVERSATION, UuidUtils.asBytes(message.conversationId))
values.put(COLUMN_CONVERSATION, UuidUtils.asBytes(message.conversationId))
return values
} }
private fun insert(db: SQLiteDatabase, message: Plaintext) { private fun insert(db: SQLiteDatabase, message: Plaintext) {
@ -235,8 +230,7 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo
} }
override fun remove(message: Plaintext) { override fun remove(message: Plaintext) {
val db = sql.writableDatabase sql.writableDatabase.delete(TABLE_NAME, "id = ?", arrayOf(message.id.toString()))
db.delete(TABLE_NAME, "id = ?", arrayOf(message.id.toString()))
} }
companion object { companion object {

View File

@ -5,7 +5,6 @@ import android.database.sqlite.SQLiteConstraintException
import android.database.sqlite.SQLiteDoneException import android.database.sqlite.SQLiteDoneException
import android.database.sqlite.SQLiteStatement import android.database.sqlite.SQLiteStatement
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.ports.NodeRegistry import ch.dissem.bitmessage.ports.NodeRegistry
import ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes import ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes
import ch.dissem.bitmessage.utils.Collections import ch.dissem.bitmessage.utils.Collections
@ -120,19 +119,20 @@ class AndroidNodeRegistry(private val sql: SqlHelper) : NodeRegistry {
private fun insert(node: NetworkAddress) { private fun insert(node: NetworkAddress) {
try { try {
// Create a new map of values, where column names are the keys // Create a new map of values, where column names are the keys
val values = ContentValues() val values = ContentValues().apply {
values.put(COLUMN_STREAM, node.stream) put(COLUMN_STREAM, node.stream)
values.put(COLUMN_ADDRESS, node.IPv6) put(COLUMN_ADDRESS, node.IPv6)
values.put(COLUMN_PORT, node.port) put(COLUMN_PORT, node.port)
values.put(COLUMN_SERVICES, node.services) put(COLUMN_SERVICES, node.services)
values.put(COLUMN_TIME, put(COLUMN_TIME,
if (node.time > UnixTime.now) { if (node.time > UnixTime.now) {
// This might be an attack, let's not use those nodes with priority // This might be an attack, let's not use those nodes with priority
UnixTime.now - 7 * UnixTime.DAY UnixTime.now - 7 * UnixTime.DAY
} else { } else {
node.time node.time
} }
) )
}
sql.writableDatabase.insertOrThrow(TABLE_NAME, null, values) sql.writableDatabase.insertOrThrow(TABLE_NAME, null, values)
} catch (e: SQLiteConstraintException) { } catch (e: SQLiteConstraintException) {
@ -150,9 +150,10 @@ class AndroidNodeRegistry(private val sql: SqlHelper) : NodeRegistry {
} }
// Create a new map of values, where column names are the keys // Create a new map of values, where column names are the keys
val values = ContentValues() val values = ContentValues().apply {
values.put(COLUMN_SERVICES, node.services) put(COLUMN_SERVICES, node.services)
values.put(COLUMN_TIME, max(node.time, time)) put(COLUMN_TIME, max(node.time, time))
}
sql.writableDatabase.update( sql.writableDatabase.update(
TABLE_NAME, TABLE_NAME,

View File

@ -48,28 +48,27 @@ class AndroidProofOfWorkRepository(private val sql: SqlHelper) : ProofOfWorkRepo
// you will actually use after this query. // you will actually use after this query.
val projection = arrayOf(COLUMN_DATA, COLUMN_VERSION, COLUMN_NONCE_TRIALS_PER_BYTE, COLUMN_EXTRA_BYTES, COLUMN_EXPIRATION_TIME, COLUMN_MESSAGE_ID) val projection = arrayOf(COLUMN_DATA, COLUMN_VERSION, COLUMN_NONCE_TRIALS_PER_BYTE, COLUMN_EXTRA_BYTES, COLUMN_EXPIRATION_TIME, COLUMN_MESSAGE_ID)
val db = sql.readableDatabase sql.readableDatabase.query(
db.query( TABLE_NAME, projection,
TABLE_NAME, projection, "initial_hash=X'${hex(initialHash)}'",
"initial_hash=X'${hex(initialHash)}'", null, null, null, null
null, null, null, null
).use { c -> ).use { c ->
if (c.moveToFirst()) { if (c.moveToFirst()) {
val version = c.getInt(c.getColumnIndex(COLUMN_VERSION)) val version = c.getInt(c.getColumnIndex(COLUMN_VERSION))
val blob = c.getBlob(c.getColumnIndex(COLUMN_DATA)) val blob = c.getBlob(c.getColumnIndex(COLUMN_DATA))
return if (c.isNull(c.getColumnIndex(COLUMN_MESSAGE_ID))) { return if (c.isNull(c.getColumnIndex(COLUMN_MESSAGE_ID))) {
ProofOfWorkRepository.Item( ProofOfWorkRepository.Item(
Factory.getObjectMessage(version, ByteArrayInputStream(blob), blob.size) ?: throw RuntimeException("Invalid object in repository"), Factory.getObjectMessage(version, ByteArrayInputStream(blob), blob.size) ?: throw RuntimeException("Invalid object in repository"),
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)), c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)) c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES))
) )
} else { } else {
ProofOfWorkRepository.Item( ProofOfWorkRepository.Item(
Factory.getObjectMessage(version, ByteArrayInputStream(blob), blob.size) ?: throw RuntimeException("Invalid object in repository"), Factory.getObjectMessage(version, ByteArrayInputStream(blob), blob.size) ?: throw RuntimeException("Invalid object in repository"),
c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)), c.getLong(c.getColumnIndex(COLUMN_NONCE_TRIALS_PER_BYTE)),
c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)), c.getLong(c.getColumnIndex(COLUMN_EXTRA_BYTES)),
c.getLong(c.getColumnIndex(COLUMN_EXPIRATION_TIME)), c.getLong(c.getColumnIndex(COLUMN_EXPIRATION_TIME)),
bmc.messageRepository.getMessage(c.getLong(c.getColumnIndex(COLUMN_MESSAGE_ID))) bmc.messageRepository.getMessage(c.getLong(c.getColumnIndex(COLUMN_MESSAGE_ID)))
) )
} }
} }
@ -82,10 +81,9 @@ class AndroidProofOfWorkRepository(private val sql: SqlHelper) : ProofOfWorkRepo
// you will actually use after this query. // you will actually use after this query.
val projection = arrayOf(COLUMN_INITIAL_HASH) val projection = arrayOf(COLUMN_INITIAL_HASH)
val db = sql.readableDatabase
val result = LinkedList<ByteArray>() val result = LinkedList<ByteArray>()
db.query( sql.readableDatabase.query(
TABLE_NAME, projection, null, null, null, null, null TABLE_NAME, projection, null, null, null, null, null
).use { c -> ).use { c ->
while (c.moveToNext()) { while (c.moveToNext()) {
val initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH)) val initialHash = c.getBlob(c.getColumnIndex(COLUMN_INITIAL_HASH))
@ -97,20 +95,20 @@ class AndroidProofOfWorkRepository(private val sql: SqlHelper) : ProofOfWorkRepo
override fun putObject(item: ProofOfWorkRepository.Item) { override fun putObject(item: ProofOfWorkRepository.Item) {
try { try {
val db = sql.writableDatabase
// Create a new map of values, where column names are the keys // Create a new map of values, where column names are the keys
val values = ContentValues() val values = ContentValues().apply {
values.put(COLUMN_INITIAL_HASH, cryptography().getInitialHash(item.objectMessage)) put(COLUMN_INITIAL_HASH, cryptography().getInitialHash(item.objectMessage))
values.put(COLUMN_DATA, Encode.bytes(item.objectMessage)) put(COLUMN_DATA, Encode.bytes(item.objectMessage))
values.put(COLUMN_VERSION, item.objectMessage.version) put(COLUMN_VERSION, item.objectMessage.version)
values.put(COLUMN_NONCE_TRIALS_PER_BYTE, item.nonceTrialsPerByte) put(COLUMN_NONCE_TRIALS_PER_BYTE, item.nonceTrialsPerByte)
values.put(COLUMN_EXTRA_BYTES, item.extraBytes) put(COLUMN_EXTRA_BYTES, item.extraBytes)
item.message?.let { message -> item.message?.let { message ->
values.put(COLUMN_EXPIRATION_TIME, item.expirationTime) put(COLUMN_EXPIRATION_TIME, item.expirationTime)
values.put(COLUMN_MESSAGE_ID, message.id as Long?) put(COLUMN_MESSAGE_ID, message.id as Long?)
}
} }
db.insertOrThrow(TABLE_NAME, null, values) sql.writableDatabase.insertOrThrow(TABLE_NAME, null, values)
} catch (e: SQLiteConstraintException) { } catch (e: SQLiteConstraintException) {
LOG.trace(e.message, e) LOG.trace(e.message, e)
} }
@ -121,8 +119,7 @@ class AndroidProofOfWorkRepository(private val sql: SqlHelper) : ProofOfWorkRepo
putObject(ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes)) putObject(ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes))
override fun removeObject(initialHash: ByteArray) { override fun removeObject(initialHash: ByteArray) {
val db = sql.writableDatabase sql.writableDatabase.delete(TABLE_NAME, "initial_hash=X'${hex(initialHash)}'", null)
db.delete(TABLE_NAME, "initial_hash=X'${hex(initialHash)}'", null)
} }
companion object { companion object {

View File

@ -32,42 +32,42 @@ class SqlHelper(private val ctx: Context) : SQLiteOpenHelper(ctx, DATABASE_NAME,
override fun onCreate(db: SQLiteDatabase) = onUpgrade(db, 0, DATABASE_VERSION) override fun onCreate(db: SQLiteDatabase) = onUpgrade(db, 0, DATABASE_VERSION)
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = mapOf( override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) = mapOf(
0 to { 0 to {
executeMigration(db, "V1.0__Create_table_inventory") executeMigration(db, "V1.0__Create_table_inventory")
executeMigration(db, "V1.1__Create_table_address") executeMigration(db, "V1.1__Create_table_address")
executeMigration(db, "V1.2__Create_table_message") executeMigration(db, "V1.2__Create_table_message")
}, },
1 to { 1 to {
// executeMigration(db, "V2.0__Update_table_message"); // executeMigration(db, "V2.0__Update_table_message");
executeMigration(db, "V2.1__Create_table_POW") executeMigration(db, "V2.1__Create_table_POW")
}, },
2 to { 2 to {
executeMigration(db, "V3.0__Update_table_address") executeMigration(db, "V3.0__Update_table_address")
}, },
3 to { 3 to {
executeMigration(db, "V3.1__Update_table_POW") executeMigration(db, "V3.1__Update_table_POW")
executeMigration(db, "V3.2__Update_table_message") executeMigration(db, "V3.2__Update_table_message")
}, },
4 to { 4 to {
executeMigration(db, "V3.3__Create_table_node") executeMigration(db, "V3.3__Create_table_node")
}, },
5 to { 5 to {
executeMigration(db, "V3.4__Add_label_outbox") executeMigration(db, "V3.4__Add_label_outbox")
}, },
6 to { 6 to {
executeMigration(db, "V4.0__Create_table_message_parent") executeMigration(db, "V4.0__Create_table_message_parent")
}, },
7 to { 7 to {
setMissingConversationIds(db) setMissingConversationIds(db)
} }
).filterKeys { it in oldVersion until newVersion }.forEach { (_, v) -> v.invoke() } ).filterKeys { it in oldVersion until newVersion }.forEach { (_, v) -> v.invoke() }
/** /**
* Set UUIDs for all messages that have no conversation ID * Set UUIDs for all messages that have no conversation ID
*/ */
private fun setMissingConversationIds(db: SQLiteDatabase) = db.query( private fun setMissingConversationIds(db: SQLiteDatabase) = db.query(
"Message", arrayOf("id"), "Message", arrayOf("id"),
"conversation IS NULL", null, null, null, null "conversation IS NULL", null, null, null, null
).use { c -> ).use { c ->
while (c.moveToNext()) { while (c.moveToNext()) {
val id = c.getLong(0) val id = c.getLong(0)
@ -76,8 +76,9 @@ class SqlHelper(private val ctx: Context) : SQLiteOpenHelper(ctx, DATABASE_NAME,
} }
private fun setMissingConversationId(id: Long, db: SQLiteDatabase) { private fun setMissingConversationId(id: Long, db: SQLiteDatabase) {
val values = ContentValues(1) val values = ContentValues(1).apply {
values.put("conversation", UuidUtils.asBytes(UUID.randomUUID())) put("conversation", UuidUtils.asBytes(UUID.randomUUID()))
}
db.update("Message", values, "id=?", arrayOf(id.toString())) db.update("Message", values, "id=?", arrayOf(id.toString()))
} }

View File

@ -25,6 +25,8 @@ import android.net.ConnectivityManager
import android.os.Handler import android.os.Handler
import ch.dissem.apps.abit.notification.NetworkNotification import ch.dissem.apps.abit.notification.NetworkNotification
import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences
import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.BitmessageContext
import ch.dissem.bitmessage.utils.Property import ch.dissem.bitmessage.utils.Property
import org.jetbrains.anko.connectivityManager import org.jetbrains.anko.connectivityManager
@ -39,9 +41,9 @@ class BitmessageService : Service() {
private val bmc: BitmessageContext by lazy { Singleton.getBitmessageContext(this) } private val bmc: BitmessageContext by lazy { Singleton.getBitmessageContext(this) }
private lateinit var notification: NetworkNotification private lateinit var notification: NetworkNotification
private val connectivityReceiver: BroadcastReceiver = object: BroadcastReceiver() { private val connectivityReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
if (bmc.isRunning() && connectivityManager.isActiveNetworkMetered){ if (bmc.isRunning() && !Preferences.isConnectionAllowed(this@BitmessageService)) {
bmc.shutdown() bmc.shutdown()
} }
} }
@ -58,7 +60,10 @@ class BitmessageService : Service() {
} }
override fun onCreate() { override fun onCreate() {
registerReceiver(connectivityReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) registerReceiver(
connectivityReceiver,
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
)
notification = NetworkNotification(this) notification = NetworkNotification(this)
running = false running = false
} }
@ -85,13 +90,15 @@ class BitmessageService : Service() {
notification.showShutdown() notification.showShutdown()
cleanupHandler.removeCallbacks(cleanupTask) cleanupHandler.removeCallbacks(cleanupTask)
bmc.cleanup() bmc.cleanup()
unregisterReceiver(connectivityReceiver)
stopSelf() stopSelf()
} }
override fun onBind(intent: Intent) = null override fun onBind(intent: Intent) = null
companion object { companion object {
@Volatile private var running = false @Volatile
private var running = false
val isRunning: Boolean val isRunning: Boolean
get() = running && Singleton.bitmessageContext?.isRunning() == true get() = running && Singleton.bitmessageContext?.isRunning() == true

View File

@ -20,6 +20,7 @@ import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.Binder import android.os.Binder
import android.os.IBinder import android.os.IBinder
import android.support.v4.content.ContextCompat
import ch.dissem.apps.abit.notification.ProofOfWorkNotification import ch.dissem.apps.abit.notification.ProofOfWorkNotification
import ch.dissem.apps.abit.notification.ProofOfWorkNotification.Companion.ONGOING_NOTIFICATION_ID import ch.dissem.apps.abit.notification.ProofOfWorkNotification.Companion.ONGOING_NOTIFICATION_ID
import ch.dissem.apps.abit.util.PowStats import ch.dissem.apps.abit.util.PowStats
@ -44,9 +45,14 @@ class ProofOfWorkService : Service() {
private val notification = service.notification private val notification = service.notification
fun process(item: PowItem) = synchronized(queue) { fun process(item: PowItem) = synchronized(queue) {
service.startService(Intent(service, ProofOfWorkService::class.java)) ContextCompat.startForegroundService(
service.startForeground(ONGOING_NOTIFICATION_ID, service,
notification.notification) Intent(service, ProofOfWorkService::class.java)
)
service.startForeground(
ONGOING_NOTIFICATION_ID,
notification.notification
)
if (!calculating) { if (!calculating) {
calculating = true calculating = true
service.calculateNonce(item) service.calculateNonce(item)
@ -58,7 +64,11 @@ class ProofOfWorkService : Service() {
} }
data class PowItem(val initialHash: ByteArray, val targetValue: ByteArray, val callback: ProofOfWorkEngine.Callback) { data class PowItem(
val initialHash: ByteArray,
val targetValue: ByteArray,
val callback: ProofOfWorkEngine.Callback
) {
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
@ -81,29 +91,32 @@ class ProofOfWorkService : Service() {
private fun calculateNonce(item: PowItem) { private fun calculateNonce(item: PowItem) {
notification.start(item) notification.start(item)
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
engine.calculateNonce(item.initialHash, item.targetValue, object : ProofOfWorkEngine.Callback { engine.calculateNonce(
override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { item.initialHash,
notification.finished() item.targetValue,
val time = System.currentTimeMillis() - startTime object : ProofOfWorkEngine.Callback {
PowStats.addPow(this@ProofOfWorkService, time, item.targetValue) override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
try { notification.finished()
item.callback.onNonceCalculated(initialHash, nonce) val time = System.currentTimeMillis() - startTime
} finally { PowStats.addPow(this@ProofOfWorkService, time, item.targetValue)
var next: PowItem? = null try {
synchronized(queue) { item.callback.onNonceCalculated(initialHash, nonce)
next = queue.poll() } finally {
if (next == null) { var next: PowItem? = null
calculating = false synchronized(queue) {
stopForeground(true) next = queue.poll()
stopSelf() if (next == null) {
} else { calculating = false
notification.update(queue.size).show() stopForeground(true)
stopSelf()
} else {
notification.update(queue.size).show()
}
} }
next?.let { calculateNonce(it) }
} }
next?.let { calculateNonce(it) }
} }
} })
})
} }
companion object { companion object {

View File

@ -50,36 +50,38 @@ object Singleton {
private var swipeableMessageAdapter: WeakReference<SwipeableMessageAdapter>? = null private var swipeableMessageAdapter: WeakReference<SwipeableMessageAdapter>? = null
val labeler = DefaultLabeler().apply { val labeler = DefaultLabeler().apply {
listener = { message, added, removed -> listener = { message, added, removed ->
swipeableMessageAdapter?.get()?.let { swipeableMessageAdapter -> MainActivity.apply {
currentLabel.value?.let { label -> runOnUiThread {
when { swipeableMessageAdapter?.get()?.let { swipeableMessageAdapter ->
label.type == Label.Type.TRASH currentLabel.value?.let { label ->
&& added.all { it.type == Label.Type.TRASH } when {
&& removed.any { it.type == Label.Type.TRASH } -> { label.type == Label.Type.TRASH
// work-around for messages that are deleted from trash && added.all { it.type == Label.Type.TRASH }
swipeableMessageAdapter.remove(message) && removed.any { it.type == Label.Type.TRASH } -> {
} // work-around for messages that are deleted from trash
label.type == Label.Type.UNREAD swipeableMessageAdapter.remove(message)
&& added.all { it.type == Label.Type.TRASH } -> { }
// work-around for messages that are deleted from unread, which already have the unread label removed label.type == Label.Type.UNREAD
swipeableMessageAdapter.remove(message) && added.all { it.type == Label.Type.TRASH } -> {
} // work-around for messages that are deleted from unread, which already have the unread label removed
added.contains(label) -> { swipeableMessageAdapter.remove(message)
// in most cases, top should be the correct position, but time will show if }
// the message should be properly sorted in added.contains(label) -> {
swipeableMessageAdapter.addFirst(message) // in most cases, top should be the correct position, but time will show if
} // the message should be properly sorted in
removed.contains(label) -> { swipeableMessageAdapter.addFirst(message)
swipeableMessageAdapter.remove(message) }
} removed.contains(label) -> {
removed.any { it.type == Label.Type.UNREAD } || added.any { it.type == Label.Type.UNREAD } -> { swipeableMessageAdapter.remove(message)
swipeableMessageAdapter.update(message) }
removed.any { it.type == Label.Type.UNREAD } || added.any { it.type == Label.Type.UNREAD } -> {
swipeableMessageAdapter.update(message)
}
}
} }
} }
} }
} if (removed.any { it.type == Label.Type.UNREAD } || added.any { it.type == Label.Type.UNREAD }) {
if (removed.any { it.type == Label.Type.UNREAD } || added.any { it.type == Label.Type.UNREAD }) {
MainActivity.apply {
updateUnread() updateUnread()
} }
} }

View File

@ -5,6 +5,7 @@ import android.app.job.JobService
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.Preferences
/** /**
@ -19,7 +20,7 @@ class StartupNodeOnWifiService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean { override fun onStartJob(params: JobParameters?): Boolean {
val bmc = Singleton.getBitmessageContext(this) val bmc = Singleton.getBitmessageContext(this)
if (Preferences.isFullNodeActive(this) && !bmc.isRunning()) { if (Preferences.isFullNodeActive(this) && !bmc.isRunning()) {
applicationContext.startService(Intent(this, BitmessageService::class.java)) NetworkUtils.doStartBitmessageService(applicationContext)
} }
return true return true
} }

View File

@ -8,6 +8,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import android.support.v4.content.ContextCompat
import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.MainActivity
import ch.dissem.apps.abit.dialog.FullNodeDialogActivity import ch.dissem.apps.abit.dialog.FullNodeDialogActivity
import ch.dissem.apps.abit.service.BitmessageService import ch.dissem.apps.abit.service.BitmessageService
@ -23,7 +24,7 @@ object NetworkUtils {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scheduleNodeStart(ctx) scheduleNodeStart(ctx)
} else { } else {
ctx.startService(Intent(ctx, BitmessageService::class.java)) doStartBitmessageService(ctx)
MainActivity.updateNodeSwitch() MainActivity.updateNodeSwitch()
} }
} else if (ask) { } else if (ask) {
@ -37,11 +38,15 @@ object NetworkUtils {
scheduleNodeStart(ctx) scheduleNodeStart(ctx)
} }
} else { } else {
ctx.startService(Intent(ctx, BitmessageService::class.java)) doStartBitmessageService(ctx)
MainActivity.updateNodeSwitch() MainActivity.updateNodeSwitch()
} }
} }
fun doStartBitmessageService(ctx: Context) {
ContextCompat.startForegroundService(ctx, Intent(ctx, BitmessageService::class.java))
}
fun disableNode(ctx: Context) { fun disableNode(ctx: Context) {
Preferences.setFullNodeActive(ctx, false) Preferences.setFullNodeActive(ctx, false)
ctx.stopService(Intent(ctx, BitmessageService::class.java)) ctx.stopService(Intent(ctx, BitmessageService::class.java))

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.2.10' ext.kotlin_version = '1.2.20'
ext.anko_version = '0.10.4' ext.anko_version = '0.10.4'
repositories { repositories {
jcenter() jcenter()