Merge branch 'feature/fix-connectivity-issues' into develop

This commit is contained in:
Christian Basler 2018-08-21 07:39:57 +02:00
commit 9f2508c1a5
37 changed files with 312 additions and 1045 deletions

View File

@ -11,8 +11,6 @@
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
<application <application
android:name="android.support.multidex.MultiDexApplication" android:name="android.support.multidex.MultiDexApplication"
@ -120,20 +118,10 @@
</intent-filter> </intent-filter>
</activity> </activity>
<service
android:name=".service.BitmessageService"
android:exported="false" />
<service <service
android:name=".service.ProofOfWorkService" android:name=".service.ProofOfWorkService"
android:exported="false" /> android:exported="false" />
<!-- Synchronization -->
<provider
android:name=".synchronization.StubProvider"
android:authorities="ch.dissem.apps.abit.provider"
android:exported="false"
android:syncable="true" />
<!-- Exports --> <!-- Exports -->
<provider <provider
android:name="android.support.v4.content.FileProvider" android:name="android.support.v4.content.FileProvider"
@ -145,30 +133,6 @@
android:resource="@xml/file_paths" /> android:resource="@xml/file_paths" />
</provider> </provider>
<service
android:name=".synchronization.AuthenticatorService"
android:exported="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<service
android:name=".synchronization.SyncService"
android:exported="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter" />
</service>
<service <service
android:name=".service.BitmessageIntentService" android:name=".service.BitmessageIntentService"
android:exported="false" /> android:exported="false" />
@ -182,7 +146,12 @@
</receiver> </receiver>
<service <service
android:name=".service.StartupNodeOnWifiService" android:name=".service.NodeStartupService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
<service
android:name=".service.CleanupService"
android:exported="true" android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" /> android:permission="android.permission.BIND_JOB_SERVICE" />

View File

@ -35,7 +35,7 @@ import ch.dissem.apps.abit.ComposeMessageActivity.Companion.EXTRA_SUBJECT
import ch.dissem.apps.abit.adapter.ContactAdapter import ch.dissem.apps.abit.adapter.ContactAdapter
import ch.dissem.apps.abit.dialog.SelectEncodingDialogFragment import ch.dissem.apps.abit.dialog.SelectEncodingDialogFragment
import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.preferences
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
@ -76,7 +76,7 @@ class ComposeMessageFragment : Fragment() {
parents.addAll(draft.parents) parents.addAll(draft.parents)
} else { } else {
var id = getSerializable(EXTRA_IDENTITY) as? BitmessageAddress var id = getSerializable(EXTRA_IDENTITY) as? BitmessageAddress
if (context != null && (id == null || id.privateKey == null)) { if (context != null && id?.privateKey == null) {
id = Singleton.getIdentity(context!!) id = Singleton.getIdentity(context!!)
} }
if (id?.privateKey != null) { if (id?.privateKey != null) {
@ -94,8 +94,7 @@ class ComposeMessageFragment : Fragment() {
if (containsKey(EXTRA_CONTENT)) { if (containsKey(EXTRA_CONTENT)) {
content = getString(EXTRA_CONTENT) content = getString(EXTRA_CONTENT)
} }
encoding = getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding ?: encoding = getSerializable(EXTRA_ENCODING) as? Plaintext.Encoding ?: Plaintext.Encoding.SIMPLE
Plaintext.Encoding.SIMPLE
if (containsKey(EXTRA_PARENT)) { if (containsKey(EXTRA_PARENT)) {
val parent = getSerializable(EXTRA_PARENT) as Plaintext val parent = getSerializable(EXTRA_PARENT) as Plaintext
@ -221,7 +220,7 @@ class ComposeMessageFragment : Fragment() {
} }
val sender = sender_input.selectedItem as? ch.dissem.bitmessage.entity.BitmessageAddress val sender = sender_input.selectedItem as? ch.dissem.bitmessage.entity.BitmessageAddress
sender?.let { builder.from(it) } sender?.let { builder.from(it) }
if (!Preferences.requestAcknowledgements(ctx)) { if (!ctx.preferences.requestAcknowledgements) {
builder.preventAck() builder.preventAck()
} }
when (encoding) { when (encoding) {

View File

@ -31,11 +31,7 @@ import ch.dissem.apps.abit.listener.ListSelectionListener
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.service.Singleton import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.service.Singleton.currentLabel import ch.dissem.apps.abit.service.Singleton.currentLabel
import ch.dissem.apps.abit.synchronization.SyncAdapter import ch.dissem.apps.abit.util.*
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences
import ch.dissem.apps.abit.util.getColor
import ch.dissem.apps.abit.util.getIcon
import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.BitmessageContext
import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Conversation import ch.dissem.bitmessage.entity.Conversation
@ -145,11 +141,6 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
ComposeMessageActivity.launchReplyTo(this, item) ComposeMessageActivity.launchReplyTo(this, item)
} }
if (Preferences.useTrustedNode(this)) {
SyncAdapter.startSync(this)
} else {
SyncAdapter.stopSync(this)
}
if (drawer.isDrawerOpen) { if (drawer.isDrawerOpen) {
MaterialShowcaseView.Builder(this) MaterialShowcaseView.Builder(this)
.setMaskColour(R.color.colorPrimary) .setMaskColour(R.color.colorPrimary)
@ -179,8 +170,6 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
.setDelay(1000) .setDelay(1000)
.show() .show()
} }
SyncAdapter.startSync(this)
} }
private fun <F> changeList(listFragment: F) where F : Fragment, F : ListHolder<*> { private fun <F> changeList(listFragment: F) where F : Fragment, F : ListHolder<*> {
@ -259,14 +248,13 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
nodeSwitch = SwitchDrawerItem() nodeSwitch = SwitchDrawerItem()
.withIdentifier(ID_NODE_SWITCH) .withIdentifier(ID_NODE_SWITCH)
.withName(R.string.full_node) .withName(R.string.online)
.withIcon(CommunityMaterial.Icon.cmd_cloud_outline) .withIcon(CommunityMaterial.Icon.cmd_cloud_outline)
.withChecked(Preferences.isFullNodeActive(this)) .withChecked(preferences.online)
.withOnCheckedChangeListener { _, _, isChecked -> .withOnCheckedChangeListener { _, _, isChecked ->
preferences.online = isChecked
if (isChecked) { if (isChecked) {
NetworkUtils.enableNode(this@MainActivity) network.enableNode(true)
} else {
NetworkUtils.disableNode(this@MainActivity)
} }
} }
@ -369,10 +357,8 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
} }
override fun onResume() { override fun onResume() {
network.enableNode(false)
updateUnread() updateUnread()
if (Preferences.isFullNodeActive(this) && Preferences.isConnectionAllowed(this@MainActivity)) {
NetworkUtils.enableNode(this, false)
}
Singleton.getMessageListener(this).resetNotification() Singleton.getMessageListener(this).resetNotification()
currentLabel.addObserver(this) { label -> currentLabel.addObserver(this) { label ->
if (label != null && label.id is Long) { if (label != null && label.id is Long) {
@ -578,15 +564,6 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
private var instance: WeakReference<MainActivity>? = null private var instance: WeakReference<MainActivity>? = null
fun updateNodeSwitch() {
apply {
runOnUiThread {
nodeSwitch.withChecked(Preferences.isFullNodeActive(this))
drawer.updateStickyFooterItem(nodeSwitch)
}
}
}
/** /**
* Runs the given code in the main activity context, if it currently exists. Otherwise, * Runs the given code in the main activity context, if it currently exists. Otherwise,
* it's ignored. * it's ignored.

View File

@ -134,23 +134,27 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
} }
private fun doUpdateList(label: Label?) { private fun doUpdateList(label: Label?) {
val mainActivity = activity as? MainActivity // If the menu item isn't available yet, we should wait - the method will be called again once it's
swipeableMessageAdapter?.clear(label) // initialized.
if (label == null) { emptyTrashMenuItem?.let { menuItem ->
mainActivity?.updateTitle(getString(R.string.app_name)) val mainActivity = activity as? MainActivity
swipeableMessageAdapter?.notifyDataSetChanged() swipeableMessageAdapter?.clear(label)
return if (label == null) {
} mainActivity?.updateTitle(getString(R.string.app_name))
emptyTrashMenuItem?.isVisible = label.type == Label.Type.TRASH swipeableMessageAdapter?.notifyDataSetChanged()
mainActivity?.apply { return
if ("archive" == label.toString()) { }
updateTitle(getString(R.string.archive)) menuItem.isVisible = label.type == Label.Type.TRASH
} else { mainActivity?.apply {
updateTitle(label.toString()) if ("archive" == label.toString()) {
updateTitle(getString(R.string.archive))
} else {
updateTitle(label.toString())
}
} }
}
loadMoreItems() loadMoreItems()
}
} }
override fun onCreateView( override fun onCreateView(
@ -296,6 +300,7 @@ class MessageListFragment : Fragment(), ListHolder<Label> {
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.message_list, menu) inflater.inflate(R.menu.message_list, menu)
emptyTrashMenuItem = menu.findItem(R.id.empty_trash) emptyTrashMenuItem = menu.findItem(R.id.empty_trash)
currentLabel.value?.let { doUpdateList(it) }
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
} }

View File

@ -17,7 +17,10 @@
package ch.dissem.apps.abit package ch.dissem.apps.abit
import android.app.Activity import android.app.Activity
import android.content.* import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
@ -33,12 +36,9 @@ import android.widget.Toast
import ch.dissem.apps.abit.service.BatchProcessorService import ch.dissem.apps.abit.service.BatchProcessorService
import ch.dissem.apps.abit.service.SimpleJob import ch.dissem.apps.abit.service.SimpleJob
import ch.dissem.apps.abit.service.Singleton import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.synchronization.SyncAdapter
import ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW
import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE
import ch.dissem.apps.abit.util.Exports import ch.dissem.apps.abit.util.Exports
import ch.dissem.apps.abit.util.NetworkUtils import ch.dissem.apps.abit.util.network
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.preferences
import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.Plaintext
import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.aboutlibraries.LibsBuilder
@ -51,8 +51,7 @@ import java.util.*
/** /**
* @author Christian Basler * @author Christian Basler
*/ */
class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener, class SettingsFragment : PreferenceFragmentCompat(), PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey) setPreferencesFromResource(R.xml.preferences, rootKey)
@ -103,7 +102,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
val bmc = Singleton.getBitmessageContext(ctx) val bmc = Singleton.getBitmessageContext(ctx)
bmc.internals.nodeRegistry.clear() bmc.internals.nodeRegistry.clear()
bmc.cleanup() bmc.cleanup()
Preferences.cleanupExportDirectory(ctx) ctx.preferences.cleanupExportDirectory()
uiThread { uiThread {
Toast.makeText( Toast.makeText(
@ -122,7 +121,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
indeterminateProgressDialog(R.string.export_data_summary, R.string.export_data).apply { indeterminateProgressDialog(R.string.export_data_summary, R.string.export_data).apply {
doAsync { doAsync {
val exportDirectory = Preferences.getExportDirectory(ctx) val exportDirectory = ctx.preferences.exportDirectory
exportDirectory.mkdirs() exportDirectory.mkdirs()
val file = Exports.exportData(exportDirectory, ctx) val file = Exports.exportData(exportDirectory, ctx)
val contentUri = getUriForFile(ctx, "ch.dissem.apps.abit.fileprovider", file) val contentUri = getUriForFile(ctx, "ch.dissem.apps.abit.fileprovider", file)
@ -161,7 +160,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
val ctx = context ?: throw IllegalStateException("No context available") val ctx = context ?: throw IllegalStateException("No context available")
when (requestCode) { when (requestCode) {
WRITE_EXPORT_REQUEST_CODE -> Preferences.cleanupExportDirectory(ctx) WRITE_EXPORT_REQUEST_CODE -> ctx.preferences.cleanupExportDirectory()
READ_IMPORT_REQUEST_CODE -> { READ_IMPORT_REQUEST_CODE -> {
if (resultCode == Activity.RESULT_OK && data?.data != null) { if (resultCode == Activity.RESULT_OK && data?.data != null) {
indeterminateProgressDialog(R.string.import_data_summary, R.string.import_data).apply { indeterminateProgressDialog(R.string.import_data_summary, R.string.import_data).apply {
@ -187,24 +186,6 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
} }
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) {
PREFERENCE_SERVER_POW -> toggleSyncServerPOW(sharedPreferences)
}
}
private fun toggleSyncServerPOW(sharedPreferences: SharedPreferences) {
val node = sharedPreferences.getString(PREFERENCE_TRUSTED_NODE, null)
if (node != null) {
val ctx = context ?: throw IllegalStateException("No context available")
if (sharedPreferences.getBoolean(PREFERENCE_SERVER_POW, false)) {
SyncAdapter.startPowSync(ctx)
} else {
SyncAdapter.stopPowSync(ctx)
}
}
}
private val connection = object : ServiceConnection { private val connection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) { override fun onServiceConnected(name: ComponentName, service: IBinder) {
if (service is BatchProcessorService.BatchBinder) { if (service is BatchProcessorService.BatchBinder) {
@ -250,11 +231,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
private fun connectivityChangeListener() = private fun connectivityChangeListener() =
OnPreferenceChangeListener { _, _ -> OnPreferenceChangeListener { _, _ ->
context?.let { ctx -> context?.network?.scheduleNodeStart()
if (Preferences.isFullNodeActive(ctx)) {
NetworkUtils.scheduleNodeStart(ctx)
}
}
true true
} }

View File

@ -1,48 +0,0 @@
/*
* Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.apps.abit.adapter
import android.content.Context
import android.preference.PreferenceManager
import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
/**
* Switches between two [ProofOfWorkEngine]s depending on the configuration.
*
* @author Christian Basler
*/
class SwitchingProofOfWorkEngine(
private val ctx: Context,
private val preference: String,
private val option: ProofOfWorkEngine,
private val fallback: ProofOfWorkEngine
) : ProofOfWorkEngine, InternalContext.ContextHolder {
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) {
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
if (preferences.getBoolean(preference, false)) {
option.calculateNonce(initialHash, target, callback)
} else {
fallback.calculateNonce(initialHash, target, callback)
}
}
override fun setContext(context: InternalContext) = listOf(option, fallback)
.filterIsInstance<InternalContext.ContextHolder>()
.forEach { it.setContext(context) }
}

View File

@ -19,8 +19,8 @@ package ch.dissem.apps.abit.dialog
import android.app.Activity import android.app.Activity
import android.os.Bundle import android.os.Bundle
import ch.dissem.apps.abit.R import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.util.NetworkUtils import ch.dissem.apps.abit.util.network
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.preferences
import kotlinx.android.synthetic.main.dialog_full_node.* import kotlinx.android.synthetic.main.dialog_full_node.*
/** /**
@ -31,12 +31,12 @@ class FullNodeDialogActivity : Activity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.dialog_full_node) setContentView(R.layout.dialog_full_node)
ok.setOnClickListener { ok.setOnClickListener {
Preferences.setWifiOnly(this@FullNodeDialogActivity, false) preferences.wifiOnly = false
NetworkUtils.enableNode(applicationContext) network.enableNode()
finish() finish()
} }
dismiss.setOnClickListener { dismiss.setOnClickListener {
NetworkUtils.scheduleNodeStart(applicationContext) network.scheduleNodeStart()
finish() finish()
} }
} }

View File

@ -49,7 +49,7 @@ class SelectEncodingDialogFragment : AppCompatDialogFragment() {
when (encoding) { when (encoding) {
SIMPLE -> radioGroup.check(R.id.simple) SIMPLE -> radioGroup.check(R.id.simple)
EXTENDED -> radioGroup.check(R.id.extended) EXTENDED -> radioGroup.check(R.id.extended)
else -> LOG.warn("Unexpected encoding: " + encoding) else -> LOG.warn("Unexpected encoding: $encoding")
} }
ok.setOnClickListener(View.OnClickListener { ok.setOnClickListener(View.OnClickListener {
encoding = when (radioGroup.checkedRadioButtonId) { encoding = when (radioGroup.checkedRadioButtonId) {

View File

@ -19,7 +19,7 @@ package ch.dissem.apps.abit.listener
import android.content.Context import android.content.Context
import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.MainActivity
import ch.dissem.apps.abit.notification.NewMessageNotification import ch.dissem.apps.abit.notification.NewMessageNotification
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.preferences
import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.BitmessageContext
import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.ports.MessageRepository import ch.dissem.bitmessage.ports.MessageRepository
@ -50,7 +50,7 @@ class MessageListener(ctx: Context) : BitmessageContext.Listener.WithContext {
private lateinit var conversationService: ConversationService private lateinit var conversationService: ConversationService
init { init {
emulateConversations = Preferences.isEmulateConversations(ctx) emulateConversations = ctx.preferences.emulateConversations
} }
override fun receive(plaintext: Plaintext) { override fun receive(plaintext: Plaintext) {
@ -81,7 +81,7 @@ class MessageListener(ctx: Context) : BitmessageContext.Listener.WithContext {
} }
} }
fun updateConversation(plaintext: Plaintext) { private fun updateConversation(plaintext: Plaintext) {
if (emulateConversations && plaintext.encoding != Plaintext.Encoding.EXTENDED) { if (emulateConversations && plaintext.encoding != Plaintext.Encoding.EXTENDED) {
conversationService.getSubject(listOf(plaintext))?.let { subject -> conversationService.getSubject(listOf(plaintext))?.let { subject ->
plaintext.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray()) plaintext.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray())

View File

@ -25,7 +25,7 @@ import android.support.v4.app.NotificationCompat
import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.MainActivity
import ch.dissem.apps.abit.R import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.service.BitmessageIntentService import ch.dissem.apps.abit.service.BitmessageIntentService
import ch.dissem.apps.abit.service.BitmessageService import ch.dissem.apps.abit.service.NodeStartupService
import java.util.* import java.util.*
import kotlin.concurrent.fixedRateTimer import kotlin.concurrent.fixedRateTimer
@ -51,9 +51,9 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
@SuppressLint("StringFormatMatches") @SuppressLint("StringFormatMatches")
private fun update(): Boolean { private fun update(): Boolean {
val running = BitmessageService.isRunning val running = NodeStartupService.isRunning
builder.setOngoing(running) builder.setOngoing(running)
val connections = BitmessageService.status.getProperty("network", "connections") val connections = NodeStartupService.status.getProperty("network", "connections")
if (!running) { if (!running) {
builder.setSmallIcon(R.drawable.ic_notification_full_node_disconnected) builder.setSmallIcon(R.drawable.ic_notification_full_node_disconnected)
builder.setContentText(ctx.getString(R.string.connection_info_disconnected)) builder.setContentText(ctx.getString(R.string.connection_info_disconnected))
@ -112,7 +112,6 @@ class NetworkNotification(ctx: Context) : AbstractNotification(ctx) {
timer = fixedRateTimer(initialDelay = 10000, period = 10000) { timer = fixedRateTimer(initialDelay = 10000, period = 10000) {
if (!update()) { if (!update()) {
cancel() cancel()
ctx.stopService(Intent(ctx, BitmessageService::class.java))
} }
super@NetworkNotification.show() super@NetworkNotification.show()
} }

View File

@ -1,83 +0,0 @@
/*
* Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.apps.abit.pow
import android.content.Context
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.synchronization.SyncAdapter
import ch.dissem.apps.abit.util.Preferences
import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
import ch.dissem.bitmessage.utils.Singleton.cryptography
import org.slf4j.LoggerFactory
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
/**
* @author Christian Basler
*/
class ServerPowEngine(private val ctx: Context) : ProofOfWorkEngine, InternalContext.ContextHolder {
private lateinit var context: InternalContext
private val pool: ExecutorService
init {
pool = Executors.newCachedThreadPool { r ->
val thread = Executors.defaultThreadFactory().newThread(r)
thread.priority = Thread.MIN_PRIORITY
thread
}
}
override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) =
pool.execute {
val identity = Singleton.getIdentity(ctx) ?: throw RuntimeException("No Identity for calculating POW")
val request = ProofOfWorkRequest(identity, initialHash,
CALCULATE, target)
SyncAdapter.startPowSync(ctx)
try {
val cryptoMsg = CryptoCustomMessage(request)
cryptoMsg.signAndEncrypt(
identity,
cryptography().createPublicKey(identity.publicDecryptionKey)
)
val node = Preferences.getTrustedNode(ctx)
if (node == null) {
LOG.error("trusted node is not defined")
} else {
context.networkHandler.send(
node,
Preferences.getTrustedNodePort(ctx),
cryptoMsg)
}
} catch (e: Exception) {
LOG.error(e.message, e)
}
}
override fun setContext(context: InternalContext) {
this.context = context
}
companion object {
private val LOG = LoggerFactory.getLogger(ServerPowEngine::class.java)
}
}

View File

@ -18,7 +18,7 @@ package ch.dissem.apps.abit.service
import android.app.IntentService import android.app.IntentService
import android.content.Intent import android.content.Intent
import ch.dissem.apps.abit.util.NetworkUtils import ch.dissem.apps.abit.util.network
import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.BitmessageContext
import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.Plaintext
@ -44,10 +44,10 @@ class BitmessageIntentService : IntentService("BitmessageIntentService") {
Singleton.getMessageListener(this).resetNotification() Singleton.getMessageListener(this).resetNotification()
} }
if (it.hasExtra(EXTRA_STARTUP_NODE)) { if (it.hasExtra(EXTRA_STARTUP_NODE)) {
NetworkUtils.enableNode(this) network.enableNode()
} }
if (it.hasExtra(EXTRA_SHUTDOWN_NODE)) { if (it.hasExtra(EXTRA_SHUTDOWN_NODE)) {
NetworkUtils.disableNode(this) network.disableNode()
} }
} }
} }

View File

@ -1,113 +0,0 @@
/*
* Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.apps.abit.service
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.os.Handler
import ch.dissem.apps.abit.notification.NetworkNotification
import ch.dissem.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID
import ch.dissem.apps.abit.util.Preferences
import ch.dissem.bitmessage.BitmessageContext
import ch.dissem.bitmessage.utils.Property
import org.jetbrains.anko.doAsync
/**
* Define a Service that returns an IBinder for the
* sync adapter class, allowing the sync adapter framework to call
* onPerformSync().
*/
class BitmessageService : Service() {
private val bmc: BitmessageContext by lazy { Singleton.getBitmessageContext(this) }
private lateinit var notification: NetworkNotification
private val connectivityReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (bmc.isRunning() && !Preferences.isConnectionAllowed(this@BitmessageService)) {
bmc.shutdown()
}
}
}
private val cleanupHandler = Handler()
private val cleanupTask: Runnable = object : Runnable {
override fun run() {
bmc.cleanup()
if (isRunning) {
cleanupHandler.postDelayed(this, 24 * 60 * 60 * 1000L) // once a day
}
}
}
override fun onCreate() {
registerReceiver(
connectivityReceiver,
IntentFilter().apply {
addAction(ConnectivityManager.CONNECTIVITY_ACTION)
addAction(Intent.ACTION_BATTERY_CHANGED)
}
)
notification = NetworkNotification(this)
running = false
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (!isRunning) {
running = true
notification.connecting()
startForeground(NETWORK_NOTIFICATION_ID, notification.notification)
if (!bmc.isRunning()) {
bmc.startup()
}
notification.show()
cleanupHandler.postDelayed(cleanupTask, 24 * 60 * 60 * 1000L)
}
return Service.START_STICKY
}
override fun onDestroy() {
if (bmc.isRunning()) {
bmc.shutdown()
}
running = false
notification.showShutdown()
cleanupHandler.removeCallbacks(cleanupTask)
doAsync {
bmc.cleanup()
}
unregisterReceiver(connectivityReceiver)
stopSelf()
}
override fun onBind(intent: Intent) = null
companion object {
@Volatile
private var running = false
val isRunning: Boolean
get() = running && Singleton.bitmessageContext?.isRunning() == true
val status: Property
get() = Singleton.bitmessageContext?.status() ?: Property("bitmessage context")
}
}

View File

@ -0,0 +1,18 @@
package ch.dissem.apps.abit.service
import android.app.job.JobParameters
import android.app.job.JobService
import org.jetbrains.anko.doAsync
class CleanupService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
doAsync {
Singleton.getBitmessageContext(this@CleanupService).cleanup()
jobFinished(params, false)
}
return true
}
override fun onStopJob(params: JobParameters?) = false
}

View File

@ -0,0 +1,93 @@
package ch.dissem.apps.abit.service
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import ch.dissem.apps.abit.notification.NetworkNotification
import ch.dissem.apps.abit.util.network
import ch.dissem.apps.abit.util.preferences
import ch.dissem.bitmessage.BitmessageContext
import ch.dissem.bitmessage.utils.Property
import org.jetbrains.anko.doAsync
/**
* Starts the full node if
* * it is active
* * it is not already running
*
* And stops it when the preconditions for the job (unmetered network) aren't met anymore.
*/
class NodeStartupService : JobService() {
private val bmc: BitmessageContext by lazy { Singleton.getBitmessageContext(this) }
private lateinit var notification: NetworkNotification
private val connectivityReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (bmc.isRunning() && !preferences.connectionAllowed) {
bmc.shutdown()
}
}
}
override fun onStartJob(params: JobParameters?): Boolean {
if (preferences.online) {
registerReceiver(
connectivityReceiver,
IntentFilter().apply {
addAction(ConnectivityManager.CONNECTIVITY_ACTION)
addAction(Intent.ACTION_BATTERY_CHANGED)
}
)
notification = NetworkNotification(this)
NodeStartupService.running = false
if (!isRunning) {
running = true
notification.connecting()
if (!bmc.isRunning()) {
bmc.startup()
}
notification.show()
}
}
return true
}
override fun onDestroy() {
if (bmc.isRunning()) {
bmc.shutdown()
}
running = false
notification.showShutdown()
doAsync {
bmc.cleanup()
}
unregisterReceiver(connectivityReceiver)
stopSelf()
}
/**
* Don't actually stop the service, otherwise it will be stopped after 1 or 10 minutes
* depending on Android version.
*/
override fun onStopJob(params: JobParameters?): Boolean {
network.scheduleNodeStart()
return false
}
companion object {
@Volatile
private var running = false
val isRunning: Boolean
get() = running && Singleton.bitmessageContext?.isRunning() == true
val status: Property
get() = Singleton.bitmessageContext?.status() ?: Property("bitmessage context")
}
}

View File

@ -21,11 +21,8 @@ import android.widget.Toast
import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.MainActivity
import ch.dissem.apps.abit.R import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter import ch.dissem.apps.abit.adapter.SwipeableMessageAdapter
import ch.dissem.apps.abit.adapter.SwitchingProofOfWorkEngine
import ch.dissem.apps.abit.listener.MessageListener import ch.dissem.apps.abit.listener.MessageListener
import ch.dissem.apps.abit.pow.ServerPowEngine
import ch.dissem.apps.abit.repository.* import ch.dissem.apps.abit.repository.*
import ch.dissem.apps.abit.util.Constants
import ch.dissem.apps.abit.util.Observable import ch.dissem.apps.abit.util.Observable
import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.BitmessageContext
import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography
@ -107,11 +104,7 @@ object Singleton {
TTL.pubkey = 2 * DAY TTL.pubkey = 2 * DAY
val ctx = context.applicationContext val ctx = context.applicationContext
val sqlHelper = SqlHelper(ctx) val sqlHelper = SqlHelper(ctx)
proofOfWorkEngine = SwitchingProofOfWorkEngine( proofOfWorkEngine = ServicePowEngine(ctx)
ctx, Constants.PREFERENCE_SERVER_POW,
ServerPowEngine(ctx),
ServicePowEngine(ctx)
)
cryptography = SpongyCryptography() cryptography = SpongyCryptography()
nodeRegistry = AndroidNodeRegistry(sqlHelper) nodeRegistry = AndroidNodeRegistry(sqlHelper)
inventory = AndroidInventory(sqlHelper) inventory = AndroidInventory(sqlHelper)
@ -138,8 +131,6 @@ object Singleton {
fun getAddressRepository(ctx: Context) = getBitmessageContext(ctx).addresses as AndroidAddressRepository fun getAddressRepository(ctx: Context) = getBitmessageContext(ctx).addresses as AndroidAddressRepository
fun getProofOfWorkRepository(ctx: Context) = powRepo ?: getBitmessageContext(ctx).internals.proofOfWorkRepository
fun getIdentity(ctx: Context): BitmessageAddress? = fun getIdentity(ctx: Context): BitmessageAddress? =
init<BitmessageAddress?>(ctx, { identity }, { identity = it }) { bmc -> init<BitmessageAddress?>(ctx, { identity }, { identity = it }) { bmc ->
val identities = bmc.addresses.getIdentities() val identities = bmc.addresses.getIdentities()

View File

@ -3,18 +3,17 @@ package ch.dissem.apps.abit.service
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.util.NetworkUtils import android.content.Intent.ACTION_BOOT_COMPLETED
import ch.dissem.apps.abit.util.Preferences import ch.dissem.apps.abit.util.network
import ch.dissem.apps.abit.util.preferences
/** /**
* Starts the Bitmessage "full node" service if conditions allow it * Starts the Bitmessage "full node" service if conditions allow it
*/ */
class StartServiceReceiver : BroadcastReceiver() { class StartServiceReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action == "android.intent.action.BOOT_COMPLETED") { if (intent?.action == ACTION_BOOT_COMPLETED && context.preferences.online) {
if (Preferences.isFullNodeActive(context)) { context.network.enableNode(false)
NetworkUtils.enableNode(context, false)
}
} }
} }
} }

View File

@ -1,33 +0,0 @@
package ch.dissem.apps.abit.service
import android.app.job.JobParameters
import android.app.job.JobService
import android.os.Build
import android.support.annotation.RequiresApi
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences
/**
* Starts the full node if
* * it is active
* * it is not already running
*
* And stops it when the preconditions for the job (unmetered network) aren't met anymore.
*/
class StartupNodeOnWifiService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
val bmc = Singleton.getBitmessageContext(this)
if (Preferences.isFullNodeActive(this) && !bmc.isRunning()) {
NetworkUtils.doStartBitmessageService(applicationContext)
}
return true
}
/**
* Don't actually stop the service, otherwise it will be stopped after 1 or 10 minutes
* depending on Android version.
*/
override fun onStopJob(params: JobParameters?) = Preferences.isFullNodeActive(this)
}

View File

@ -1,62 +0,0 @@
/*
* Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.apps.abit.synchronization
import android.accounts.AbstractAccountAuthenticator
import android.accounts.Account
import android.accounts.AccountAuthenticatorResponse
import android.accounts.NetworkErrorException
import android.content.Context
import android.os.Bundle
/**
* Implement AbstractAccountAuthenticator and stub out all
* of its methods
*/
class Authenticator(context: Context) : AbstractAccountAuthenticator(context) {
override fun editProperties(r: AccountAuthenticatorResponse, s: String) =
throw UnsupportedOperationException("Editing properties is not supported")
// Don't add additional accounts
@Throws(NetworkErrorException::class)
override fun addAccount(r: AccountAuthenticatorResponse, s: String, s2: String, strings: Array<String>, bundle: Bundle) = null
// Ignore attempts to confirm credentials
@Throws(NetworkErrorException::class)
override fun confirmCredentials(r: AccountAuthenticatorResponse, account: Account, bundle: Bundle) = null
@Throws(NetworkErrorException::class)
override fun getAuthToken(r: AccountAuthenticatorResponse, account: Account, s: String, bundle: Bundle) =
throw UnsupportedOperationException("Getting an authentication token is not supported")
override fun getAuthTokenLabel(s: String) =
throw UnsupportedOperationException("Getting a label for the auth token is not supported")
@Throws(NetworkErrorException::class)
override fun updateCredentials(r: AccountAuthenticatorResponse, account: Account, s: String, bundle: Bundle) =
throw UnsupportedOperationException("Updating user credentials is not supported")
@Throws(NetworkErrorException::class)
override fun hasFeatures(r: AccountAuthenticatorResponse, account: Account, strings: Array<String>) =
throw UnsupportedOperationException("Checking features for the account is not supported")
companion object {
val ACCOUNT_SYNC = Account("Bitmessage", "ch.dissem.bitmessage")
val ACCOUNT_POW = Account("Proof of Work ", "ch.dissem.bitmessage")
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.apps.abit.synchronization
import android.app.Service
import android.content.Intent
/**
* A bound Service that instantiates the authenticator
* when started.
*/
class AuthenticatorService : Service() {
/**
* Instance field that stores the authenticator object
*/
private var authenticator: Authenticator? = null
override fun onCreate() {
// Create a new authenticator object
authenticator = Authenticator(this)
}
/*
* When the system binds to this Service to make the RPC call
* return the authenticator's IBinder.
*/
override fun onBind(intent: Intent) = authenticator?.iBinder
}

View File

@ -1,72 +0,0 @@
/*
* Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.apps.abit.synchronization
import android.content.ContentProvider
import android.content.ContentValues
import android.net.Uri
/*
* Define an implementation of ContentProvider that stubs out
* all methods
*/
class StubProvider : ContentProvider() {
/**
* Always return true, indicating that the
* provider loaded correctly.
*/
override fun onCreate() = true
/**
* Return no type for MIME type
*/
override fun getType(uri: Uri) = null
/**
* query() always returns no results
*/
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?) = null
/**
* insert() always returns null (no URI)
*/
override fun insert(uri: Uri, values: ContentValues?) = null
/**
* delete() always returns "no rows affected" (0)
*/
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = 0
/**
* update() always returns "no rows affected" (0)
*/
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?) = 0
companion object {
const val AUTHORITY = "ch.dissem.apps.abit.provider"
}
}

View File

@ -1,188 +0,0 @@
/*
* Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.apps.abit.synchronization
import android.accounts.Account
import android.accounts.AccountManager
import android.content.*
import android.os.Bundle
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.synchronization.Authenticator.Companion.ACCOUNT_POW
import ch.dissem.apps.abit.synchronization.Authenticator.Companion.ACCOUNT_SYNC
import ch.dissem.apps.abit.synchronization.StubProvider.Companion.AUTHORITY
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences
import ch.dissem.bitmessage.exception.DecryptionFailedException
import ch.dissem.bitmessage.extensions.CryptoCustomMessage
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.COMPLETE
import ch.dissem.bitmessage.utils.Singleton.cryptography
import org.slf4j.LoggerFactory
import java.io.IOException
/**
* Sync Adapter to synchronize with the Bitmessage network - fetches
* new objects and then disconnects.
*/
class SyncAdapter(context: Context, autoInitialize: Boolean) : AbstractThreadedSyncAdapter(context, autoInitialize) {
private val bmc = Singleton.getBitmessageContext(context)
override fun onPerformSync(
account: Account,
extras: Bundle,
authority: String,
provider: ContentProviderClient,
syncResult: SyncResult
) {
try {
if (account == ACCOUNT_SYNC) {
if (Preferences.isConnectionAllowed(context)) {
syncData()
}
} else if (account == ACCOUNT_POW) {
syncPOW()
} else {
syncResult.stats.numAuthExceptions++
}
} catch (e: IOException) {
syncResult.stats.numIoExceptions++
} catch (e: DecryptionFailedException) {
syncResult.stats.numAuthExceptions++
}
}
private fun syncData() {
// If the Bitmessage context acts as a full node, synchronization isn't necessary
if (bmc.isRunning()) {
LOG.info("Synchronization skipped, Abit is acting as a full node")
return
}
val trustedNode = Preferences.getTrustedNode(context)
if (trustedNode == null) {
// As Abit tends to get killed by the system, let's leverage the sync mechanism to start it again:
NetworkUtils.scheduleNodeStart(context)
return
}
LOG.info("Synchronization started")
bmc.synchronize(
trustedNode,
Preferences.getTrustedNodePort(context),
Preferences.getTimeoutInSeconds(context),
true
)
LOG.info("Synchronization finished")
}
private fun syncPOW() {
val identity = Singleton.getIdentity(context)
if (identity == null) {
LOG.info("No identity available - skipping POW synchronization")
return
}
val trustedNode = Preferences.getTrustedNode(context)
if (trustedNode == null) {
LOG.info("Trusted node not available, disabling POW synchronization")
stopPowSync(context)
return
}
// If the Bitmessage context acts as a full node, synchronization isn't necessary
LOG.info("Looking for completed POW")
val privateKey =
identity.privateKey?.privateEncryptionKey ?: throw IllegalStateException("Identity without private key")
val signingKey = cryptography().createPublicKey(identity.publicDecryptionKey)
val reader = ProofOfWorkRequest.Reader(identity)
val powRepo = Singleton.getProofOfWorkRepository(context)
val items = powRepo.getItems()
for (initialHash in items) {
val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
val target = cryptography().getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
val cryptoMsg = CryptoCustomMessage(
ProofOfWorkRequest(identity, initialHash, CALCULATE, target)
)
cryptoMsg.signAndEncrypt(identity, signingKey)
val response = bmc.send(
trustedNode,
Preferences.getTrustedNodePort(context),
cryptoMsg
)
if (response.isError) {
LOG.error("Server responded with error: ${String(response.getData())}")
} else {
val (_, _, request, data) = CryptoCustomMessage.read(response, reader).decrypt(privateKey)
if (request == COMPLETE) {
bmc.internals.proofOfWorkService.onNonceCalculated(initialHash, data)
}
}
}
if (items.isEmpty()) {
stopPowSync(context)
}
LOG.info("Synchronization finished")
}
companion object {
private val LOG = LoggerFactory.getLogger(SyncAdapter::class.java)
private const val SYNC_FREQUENCY = 15 * 60L // seconds
fun startSync(ctx: Context) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
val account = addAccount(ctx, ACCOUNT_SYNC)
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle(), SYNC_FREQUENCY)
}
fun stopSync(ctx: Context) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
val account = addAccount(ctx, ACCOUNT_SYNC)
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle())
}
fun startPowSync(ctx: Context) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
val account = addAccount(ctx, ACCOUNT_POW)
// Recommend a schedule for automatic synchronization. The system may modify this based
// on other scheduled syncs and network utilization.
ContentResolver.addPeriodicSync(account, AUTHORITY, Bundle(), SYNC_FREQUENCY)
}
fun stopPowSync(ctx: Context) {
// Create account, if it's missing. (Either first run, or user has deleted account.)
val account = addAccount(ctx, ACCOUNT_POW)
ContentResolver.removePeriodicSync(account, AUTHORITY, Bundle())
}
private fun addAccount(ctx: Context, account: Account): Account {
if (AccountManager.get(ctx).addAccountExplicitly(account, null, null)) {
// Inform the system that this account supports sync
ContentResolver.setIsSyncable(account, AUTHORITY, 1)
// Inform the system that this account is eligible for auto sync when the network is up
ContentResolver.setSyncAutomatically(account, AUTHORITY, true)
}
return account
}
}
}

View File

@ -1,50 +0,0 @@
/*
* Copyright 2016 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.dissem.apps.abit.synchronization
import android.app.Service
import android.content.Intent
/**
* Define a Service that returns an IBinder for the
* sync adapter class, allowing the sync adapter framework to call
* onPerformSync().
*/
class SyncService : Service() {
/**
* Instantiate the sync adapter object.
*/
override fun onCreate() = synchronized(syncAdapterLock) {
if (syncAdapter == null) {
syncAdapter = SyncAdapter(this, true)
}
}
/**
* Return an object that allows the system to invoke
* the sync adapter.
*/
override fun onBind(intent: Intent) = syncAdapter?.syncAdapterBinder
companion object {
// Storage for an instance of the sync adapter
private var syncAdapter: SyncAdapter? = null
// Object to use as a thread-safe lock
private val syncAdapterLock = Any()
}
}

View File

@ -22,13 +22,10 @@ import java.util.regex.Pattern
* @author Christian Basler * @author Christian Basler
*/ */
object Constants { object Constants {
const val PREFERENCE_ONLINE = "online"
const val PREFERENCE_WIFI_ONLY = "wifi_only" const val PREFERENCE_WIFI_ONLY = "wifi_only"
const val PREFERENCE_REQUIRE_CHARGING = "require_charging" const val PREFERENCE_REQUIRE_CHARGING = "require_charging"
const val PREFERENCE_EMULATE_CONVERSATIONS = "emulate_conversations" const val PREFERENCE_EMULATE_CONVERSATIONS = "emulate_conversations"
const val PREFERENCE_TRUSTED_NODE = "trusted_node"
const val PREFERENCE_SYNC_TIMEOUT = "sync_timeout"
const val PREFERENCE_SERVER_POW = "server_pow"
const val PREFERENCE_FULL_NODE = "full_node"
const val PREFERENCE_REQUEST_ACK = "request_acknowledgments" const val PREFERENCE_REQUEST_ACK = "request_acknowledgments"
const val PREFERENCE_POW_AVERAGE = "average_pow_time_ms" const val PREFERENCE_POW_AVERAGE = "average_pow_time_ms"
const val PREFERENCE_POW_COUNT = "pow_count" const val PREFERENCE_POW_COUNT = "pow_count"

View File

@ -6,20 +6,21 @@ import android.app.job.JobScheduler
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.support.v4.content.ContextCompat import android.os.Build
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.CleanupService
import ch.dissem.apps.abit.service.StartupNodeOnWifiService import ch.dissem.apps.abit.service.NodeStartupService
import java.lang.ref.WeakReference
import java.util.concurrent.TimeUnit
val Context.network get() = NetworkUtils.getInstance(this)
object NetworkUtils { class NetworkUtils internal constructor(private val ctx: Context) {
fun enableNode(ctx: Context, ask: Boolean = true) { private val jobScheduler by lazy { ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler }
Preferences.setFullNodeActive(ctx, true)
if (Preferences.isConnectionAllowed(ctx) || !ask) { fun enableNode(ask: Boolean = true) {
scheduleNodeStart(ctx) if (ask && !ctx.preferences.connectionAllowed) {
} else {
// Ask for connection // Ask for connection
val dialogIntent = Intent(ctx, FullNodeDialogActivity::class.java) val dialogIntent = Intent(ctx, FullNodeDialogActivity::class.java)
if (ctx !is Activity) { if (ctx !is Activity) {
@ -27,30 +28,50 @@ object NetworkUtils {
ctx.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) ctx.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
} }
ctx.startActivity(dialogIntent) ctx.startActivity(dialogIntent)
} else {
scheduleNodeStart()
} }
} }
fun doStartBitmessageService(ctx: Context) { fun disableNode() {
ContextCompat.startForegroundService(ctx, Intent(ctx, BitmessageService::class.java)) jobScheduler.cancelAll()
} }
fun disableNode(ctx: Context) { fun scheduleNodeStart() {
Preferences.setFullNodeActive(ctx, false) JobInfo.Builder(0, ComponentName(ctx, NodeStartupService::class.java)).let { builder ->
ctx.stopService(Intent(ctx, BitmessageService::class.java)) when {
} ctx.preferences.wifiOnly ->
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ->
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING)
else ->
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
}
builder.setRequiresCharging(ctx.preferences.requireCharging)
builder.setPersisted(true)
fun scheduleNodeStart(ctx: Context) { jobScheduler.schedule(builder.build())
val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val serviceComponent = ComponentName(ctx, StartupNodeOnWifiService::class.java)
val builder = JobInfo.Builder(0, serviceComponent)
if (Preferences.isWifiOnly(ctx)) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
} }
if (Preferences.requireCharging(ctx)) {
JobInfo.Builder(1, ComponentName(ctx, CleanupService::class.java)).let { builder ->
builder.setPeriodic(TimeUnit.DAYS.toMillis(1))
builder.setRequiresDeviceIdle(true)
builder.setRequiresCharging(true) builder.setRequiresCharging(true)
jobScheduler.schedule(builder.build())
}
}
companion object {
private var instance: WeakReference<NetworkUtils>? = null
internal fun getInstance(ctx: Context): NetworkUtils {
var networkUtils = instance?.get()
if (networkUtils == null) {
networkUtils = NetworkUtils(ctx.applicationContext)
instance = WeakReference(networkUtils)
}
return networkUtils
} }
builder.setBackoffCriteria(0L, JobInfo.BACKOFF_POLICY_LINEAR)
builder.setPersisted(true)
jobScheduler.schedule(builder.build())
} }
} }

View File

@ -8,11 +8,11 @@ import kotlin.properties.Delegates
class Observable<T>(value: T) { class Observable<T>(value: T) {
private val observers = mutableMapOf<Any, (T) -> Unit>() private val observers = mutableMapOf<Any, (T) -> Unit>()
var value: T by Delegates.observable(value, { _, old, new -> var value: T by Delegates.observable(value) { _, old, new ->
if (old != new) { if (old != new) {
observers.values.forEach { it.invoke(new) } observers.values.forEach { it.invoke(new) }
} }
}) }
/** /**
* The key will make sure the observer can easily be removed. Usually the key should be either * The key will make sure the observer can easily be removed. Usually the key should be either

View File

@ -17,127 +17,63 @@
package ch.dissem.apps.abit.util package ch.dissem.apps.abit.util
import android.content.Context import android.content.Context
import ch.dissem.apps.abit.R import android.content.Intent
import ch.dissem.apps.abit.notification.ErrorNotification import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import ch.dissem.apps.abit.util.Constants.PREFERENCE_EMULATE_CONVERSATIONS import ch.dissem.apps.abit.util.Constants.PREFERENCE_EMULATE_CONVERSATIONS
import ch.dissem.apps.abit.util.Constants.PREFERENCE_FULL_NODE import ch.dissem.apps.abit.util.Constants.PREFERENCE_ONLINE
import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUEST_ACK import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUEST_ACK
import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUIRE_CHARGING import ch.dissem.apps.abit.util.Constants.PREFERENCE_REQUIRE_CHARGING
import ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT
import ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE
import ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY import ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY
import org.jetbrains.anko.batteryManager import org.jetbrains.anko.batteryManager
import org.jetbrains.anko.connectivityManager import org.jetbrains.anko.connectivityManager
import org.jetbrains.anko.defaultSharedPreferences import org.jetbrains.anko.defaultSharedPreferences
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File import java.io.File
import java.io.IOException import java.lang.ref.WeakReference
import java.net.InetAddress
import android.os.BatteryManager
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
val Context.preferences get() = Preferences.getInstance(this)
/** /**
* @author Christian Basler * @author Christian Basler
*/ */
object Preferences { class Preferences internal constructor(private val ctx: Context) {
private val LOG = LoggerFactory.getLogger(Preferences::class.java) private val LOG = LoggerFactory.getLogger(Preferences::class.java)
fun useTrustedNode(ctx: Context): Boolean { val connectionAllowed get() = isAllowedForWiFi && isAllowedForCharging
val trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return false
return trustedNode.trim { it <= ' ' }.isNotEmpty()
}
/** private val isAllowedForWiFi get() = !wifiOnly || !ctx.connectivityManager.isActiveNetworkMetered
* Warning, this method might do a network call and therefore can't be called from
* the UI thread.
*/
@Throws(IOException::class)
fun getTrustedNode(ctx: Context): InetAddress? {
var trustedNode: String = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return null
trustedNode = trustedNode.trim { it <= ' ' }
if (trustedNode.isEmpty()) return null
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$".toRegex())) { private val isAllowedForCharging get() = !requireCharging || isCharging
val index = trustedNode.lastIndexOf(':')
trustedNode = trustedNode.substring(0, index) private val isCharging
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ctx.batteryManager.isCharging
} else {
val intent = ctx.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
} }
return InetAddress.getByName(trustedNode)
}
fun getTrustedNodePort(ctx: Context): Int { var wifiOnly
var trustedNode: String = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return 8444 get() = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_WIFI_ONLY, true)
trustedNode = trustedNode.trim { it <= ' ' } set(value) {
ctx.defaultSharedPreferences.edit()
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$".toRegex())) { .putBoolean(PREFERENCE_WIFI_ONLY, value)
val index = trustedNode.lastIndexOf(':') .apply()
val portString = trustedNode.substring(index + 1)
try {
return Integer.parseInt(portString)
} catch (e: NumberFormatException) {
ErrorNotification(ctx)
.setError(R.string.error_invalid_sync_port, portString)
.show()
}
} }
return 8444
}
fun getTimeoutInSeconds(ctx: Context): Long = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT)?.toLong() ?: 120 val requireCharging get() = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_REQUIRE_CHARGING, true)
private fun getPreference(ctx: Context, name: String): String? = ctx.defaultSharedPreferences.getString(name, null) val emulateConversations get() = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_EMULATE_CONVERSATIONS, true)
fun isConnectionAllowed(ctx: Context) = isAllowedForWiFi(ctx) && isAllowedForCharging(ctx) val exportDirectory by lazy { File(ctx.filesDir, "exports") }
private fun isAllowedForWiFi(ctx: Context) = !isWifiOnly(ctx) || !ctx.connectivityManager.isActiveNetworkMetered val requestAcknowledgements = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_REQUEST_ACK, true)
private fun isAllowedForCharging(ctx: Context) = !requireCharging(ctx) || isCharging(ctx) fun cleanupExportDirectory() {
private fun isCharging(ctx: Context) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ctx.batteryManager.isCharging
} else {
val intent = ctx.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
}
fun isWifiOnly(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_WIFI_ONLY, true)
fun setWifiOnly(ctx: Context, status: Boolean) {
ctx.defaultSharedPreferences.edit()
.putBoolean(PREFERENCE_WIFI_ONLY, status)
.apply()
}
fun requireCharging(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_REQUIRE_CHARGING, true)
fun setRequireCharging(ctx: Context, status: Boolean) {
ctx.defaultSharedPreferences.edit()
.putBoolean(PREFERENCE_REQUIRE_CHARGING, status)
.apply()
}
fun isEmulateConversations(ctx: Context) =
ctx.defaultSharedPreferences.getBoolean(PREFERENCE_EMULATE_CONVERSATIONS, true)
fun isFullNodeActive(ctx: Context) =
ctx.defaultSharedPreferences.getBoolean(PREFERENCE_FULL_NODE, false)
fun setFullNodeActive(ctx: Context, status: Boolean) {
ctx.defaultSharedPreferences.edit()
.putBoolean(PREFERENCE_FULL_NODE, status)
.apply()
}
fun getExportDirectory(ctx: Context) = File(ctx.filesDir, "exports")
fun requestAcknowledgements(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_REQUEST_ACK, true)
fun cleanupExportDirectory(ctx: Context) {
val exportDirectory = getExportDirectory(ctx)
if (exportDirectory.exists()) { if (exportDirectory.exists()) {
exportDirectory.listFiles().forEach { file -> exportDirectory.listFiles().forEach { file ->
try { try {
@ -150,4 +86,30 @@ object Preferences {
} }
} }
} }
var online
get() = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_ONLINE, true)
set(value) {
ctx.defaultSharedPreferences.edit()
.putBoolean(PREFERENCE_ONLINE, value)
.apply()
if (value) {
ctx.network.enableNode(true)
} else {
ctx.network.disableNode()
}
}
companion object {
private var instance: WeakReference<Preferences>? = null
internal fun getInstance(ctx: Context): Preferences {
var prefs = instance?.get()
if (prefs == null) {
prefs = Preferences(ctx.applicationContext)
instance = WeakReference(prefs)
}
return prefs
}
}
} }

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#ffffff">
<item
android:id="@android:id/mask"
android:drawable="@android:color/white"/>
</ripple>

View File

@ -30,8 +30,6 @@
<string name="reply">رد</string> <string name="reply">رد</string>
<string name="delete">حذف</string> <string name="delete">حذف</string>
<string name="empty_trash">أفرغ المهملات</string> <string name="empty_trash">أفرغ المهملات</string>
<string name="trusted_node">عقدة موثوقة</string>
<string name="trusted_node_summary">استخدام العقدة في التزامن</string>
<string name="write_message">كتابة رسالة</string> <string name="write_message">كتابة رسالة</string>
<string name="full_node">عقدة كاملة</string> <string name="full_node">عقدة كاملة</string>
<string name="send">إرسال</string> <string name="send">إرسال</string>
@ -51,15 +49,10 @@
<string name="mark_unread">حدد كمقروء</string> <string name="mark_unread">حدد كمقروء</string>
<string name="archive">أرشيف</string> <string name="archive">أرشيف</string>
<string name="stream_number">بث #%d</string> <string name="stream_number">بث #%d</string>
<string name="sync_timeout">انتهاء مهلة التزامن</string>
<string name="sync_timeout_summary">مهلة الإتصال بالثواني</string>
<string name="proof_of_work_text_n" tools:ignore="PluralsCandidate">جاري إثبات العمل لإرسال الرسالة (%1$d في قائمة الانتظار)</string> <string name="proof_of_work_text_n" tools:ignore="PluralsCandidate">جاري إثبات العمل لإرسال الرسالة (%1$d في قائمة الانتظار)</string>
<string name="error_invalid_sync_port">إعدادات منفذ التزامن غير صالحة</string>
<string name="compose_body_hint">كتابة رسالة</string> <string name="compose_body_hint">كتابة رسالة</string>
<string name="contacts_and_subscriptions">جهات اتصال</string> <string name="contacts_and_subscriptions">جهات اتصال</string>
<string name="subscribed">تم الاشتراك</string> <string name="subscribed">تم الاشتراك</string>
<string name="server_pow">خادم إثبات العمل</string>
<string name="server_pow_summary">عقدة موثوقة تقوم بإثبات العمل</string>
<string name="full_node_warning">تشغيل عقدة Bitmessage كاملة يستهلك الكثير من البيانات، مما قد يكون مكلفًا لبيانات الهاتف، هل أنت متأكد أنك تريد تشغيل عقدة كاملة؟</string> <string name="full_node_warning">تشغيل عقدة Bitmessage كاملة يستهلك الكثير من البيانات، مما قد يكون مكلفًا لبيانات الهاتف، هل أنت متأكد أنك تريد تشغيل عقدة كاملة؟</string>
<string name="about">عن Abit</string> <string name="about">عن Abit</string>
<string name="about_summary">التبعيات مفتوحة المصدر.</string> <string name="about_summary">التبعيات مفتوحة المصدر.</string>

View File

@ -35,10 +35,6 @@
<string name="archive">Archiv</string> <string name="archive">Archiv</string>
<string name="empty_trash">Papierkorb leeren</string> <string name="empty_trash">Papierkorb leeren</string>
<string name="stream_number">Stream %d</string> <string name="stream_number">Stream %d</string>
<string name="trusted_node">Vertrauenswürdiger Knoten</string>
<string name="trusted_node_summary">Diese Adresse wird für die Synchronisation verwendet</string>
<string name="sync_timeout">Zeitbeschränkung der Synchronisierung</string>
<string name="sync_timeout_summary">Timeout in Sekunden</string>
<string name="write_message">Schreiben</string> <string name="write_message">Schreiben</string>
<string name="full_node">Aktiver Knoten</string> <string name="full_node">Aktiver Knoten</string>
<string name="send">Senden</string> <string name="send">Senden</string>
@ -47,12 +43,9 @@
<string name="proof_of_work_title">Proof of Work</string> <string name="proof_of_work_title">Proof of Work</string>
<string name="proof_of_work_text_0">Arbeite am Versenden</string> <string name="proof_of_work_text_0">Arbeite am Versenden</string>
<string name="proof_of_work_text_n">Arbeite am Versenden (%1$d in Warteschlange)</string> <string name="proof_of_work_text_n">Arbeite am Versenden (%1$d in Warteschlange)</string>
<string name="error_invalid_sync_port">Ungültiger Port in den Synchronisationseinstellungen: %s</string>
<string name="compose_body_hint">Nachricht schreiben</string> <string name="compose_body_hint">Nachricht schreiben</string>
<string name="contacts_and_subscriptions">Kontakte</string> <string name="contacts_and_subscriptions">Kontakte</string>
<string name="subscribed">Abonniert</string> <string name="subscribed">Abonniert</string>
<string name="server_pow">Server POW</string>
<string name="server_pow_summary">Der vertrauenswürdige Knoten macht den Proof of Work</string>
<string name="full_node_warning">Ein aktiver Bitmessage-Knoten muss viel hoch- und herunterladen, was auf einem mobilen Netzwerk teuer sein kann. Soll tatsächlich ein aktiver Knoten gestartet werden?</string> <string name="full_node_warning">Ein aktiver Bitmessage-Knoten muss viel hoch- und herunterladen, was auf einem mobilen Netzwerk teuer sein kann. Soll tatsächlich ein aktiver Knoten gestartet werden?</string>
<string name="about">Über Abit</string> <string name="about">Über Abit</string>
<string name="about_summary">Opensource Abhängigkeiten.</string> <string name="about_summary">Opensource Abhängigkeiten.</string>
@ -139,4 +132,17 @@ Als Alternative kann in den Einstellungen ein vertrauenswürdiger Knoten konfigu
<string name="encoding_extended">erweitert</string> <string name="encoding_extended">erweitert</string>
<string name="emulate_conversations">Konversation erraten</string> <string name="emulate_conversations">Konversation erraten</string>
<string name="emulate_conversations_summary">Benutze Betreff um zu erraten welche Nachrichten zusammengehören. Die Reihenfolge stimmt häufig nicht.</string> <string name="emulate_conversations_summary">Benutze Betreff um zu erraten welche Nachrichten zusammengehören. Die Reihenfolge stimmt häufig nicht.</string>
<string name="online">Online</string>
<string name="ok">OK</string>
<string name="unknown">Unbekannt</string>
<string name="require_charging_summary">Nur verbinden wenn das Gerät geladen wird.</string>
<string name="require_charging">Laden erforderlich</string>
<string name="emulate_conversations_batch">Bestehende Nachrichten werden nach Betreff gruppiert</string>
<string name="emulate_conversations_initialize">Bestehende Nachrichten nach Betreff gruppieren</string>
<string name="preference_group_advanced">Erweitert</string>
<string name="preference_group_network_and_performance">Netzwerk &amp; Performanz</string>
<string name="preference_group_network_and_performance_summary">Feineinstellungen für Netzwerk und Protokoll-Details</string>
<string name="preference_group_user_experience">Verhalten</string>
<string name="preference_group_user_experience_summary">Ändern, wie Nachrichten dargestellt werden</string>
<string name="bitmessage_service_description">Hält die Verbindung zum Bitmessage-Netzwerk.</string>
</resources> </resources>

View File

@ -36,9 +36,6 @@
<string name="mark_unread">Marquer non lu</string> <string name="mark_unread">Marquer non lu</string>
<string name="archive">Archive</string> <string name="archive">Archive</string>
<string name="stream_number">"Flux nº%d"</string> <string name="stream_number">"Flux nº%d"</string>
<string name="trusted_node">Nœud de confiance</string>
<string name="trusted_node_summary">Cette adresse est utilisée pour la synchronisation</string>
<string name="sync_timeout">Limitation du temps de synchronisation</string>
<string name="write_message">Écrire un message</string> <string name="write_message">Écrire un message</string>
<string name="full_node">Nœud actif</string> <string name="full_node">Nœud actif</string>
<string name="send">Transmission</string> <string name="send">Transmission</string>
@ -47,7 +44,6 @@
<string name="proof_of_work_title">Preuve de travail</string> <string name="proof_of_work_title">Preuve de travail</string>
<string name="proof_of_work_text_0">Faire du travail pour envoyer la message</string> <string name="proof_of_work_text_0">Faire du travail pour envoyer la message</string>
<string name="proof_of_work_text_n" tools:ignore="PluralsCandidate">Faire du travail pour envoyer la message (%1$d en file d\'attente)</string> <string name="proof_of_work_text_n" tools:ignore="PluralsCandidate">Faire du travail pour envoyer la message (%1$d en file d\'attente)</string>
<string name="error_invalid_sync_port">Port non valide dans les paramètres de synchronisation : %s</string>
<string name="compose_body_hint">Écrire un message</string> <string name="compose_body_hint">Écrire un message</string>
<string name="contacts_and_subscriptions">Contacts</string> <string name="contacts_and_subscriptions">Contacts</string>
<string name="subscribed">Souscrit</string> <string name="subscribed">Souscrit</string>
@ -94,9 +90,6 @@
<string name="use_mobile_network">Utiliser le réseau mobile</string> <string name="use_mobile_network">Utiliser le réseau mobile</string>
<string name="personal_message">Message</string> <string name="personal_message">Message</string>
<string name="empty_trash">Vider les ordures</string> <string name="empty_trash">Vider les ordures</string>
<string name="sync_timeout_summary">Délai d\'expiration en secondes</string>
<string name="server_pow">POW sur le serveur</string>
<string name="server_pow_summary">Le nœud de confiance fait la preuve de travail</string>
<string name="share">Partager</string> <string name="share">Partager</string>
<string name="compose_message">Composer</string> <string name="compose_message">Composer</string>
<string name="no_identity_warning">Veuillez réessayer dès qu\'une identité est disponible.</string> <string name="no_identity_warning">Veuillez réessayer dès qu\'une identité est disponible.</string>
@ -134,4 +127,9 @@
<string name="broadcasts">Diffusions</string> <string name="broadcasts">Diffusions</string>
<string name="encoding_simple">simple</string> <string name="encoding_simple">simple</string>
<string name="encoding_extended">étendu</string> <string name="encoding_extended">étendu</string>
<string name="online">En ligne</string>
<string name="unknown">Inconnue</string>
<string name="require_charging">Exigence d\'une charge</string>
<string name="require_charging_summary">Connecter uniquement lorsque l\'appareil est branché.</string>
<string name="bitmessage_service_description">Garde la connexion au réseau bitmessage.</string>
</resources> </resources>

View File

@ -153,4 +153,7 @@ As an alternative you could configure a trusted node in the settings, but as of
<string name="require_charging_summary">Only connect when device is plugged in</string> <string name="require_charging_summary">Only connect when device is plugged in</string>
<string name="unknown">Unknown</string> <string name="unknown">Unknown</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="online">Online</string>
<string name="warning_low_memory">Low memory!</string>
<string name="bitmessage_service_description">Keeps the connection to the bitmessage network.</string>
</resources> </resources>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="ch.dissem.bitmessage"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:smallIcon="@mipmap/ic_launcher" />

View File

@ -63,35 +63,10 @@
android:summary="@string/import_data_summary" android:summary="@string/import_data_summary"
android:title="@string/import_data" /> android:title="@string/import_data" />
<PreferenceScreen <Preference
android:key="preference_experimental" android:key="status"
android:title="@string/preference_group_experimental" android:summary="@string/status_summary"
android:summary="@string/preference_group_experimental_summary" android:title="@string/status" />
android:persistent="false">
<EditTextPreference
android:inputType="textUri"
android:key="trusted_node"
android:summary="@string/trusted_node_summary"
android:title="@string/trusted_node" />
<EditTextPreference
android:defaultValue="120"
android:inputType="number"
android:key="sync_timeout"
android:summary="@string/sync_timeout_summary"
android:title="@string/sync_timeout" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:dependency="trusted_node"
android:key="server_pow"
android:summary="@string/server_pow_summary"
android:title="@string/server_pow" />
<Preference
android:key="status"
android:summary="@string/status_summary"
android:title="@string/status" />
</PreferenceScreen>
</PreferenceScreen> </PreferenceScreen>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<sync-swipeableMessageAdapter
xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="ch.dissem.apps.abit.provider"
android:accountType="ch.dissem.bitmessage"
android:userVisible="true"
android:supportsUploading="true"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"/>

View File

@ -110,7 +110,7 @@ open class TestBase {
} }
fun loadIdentity(address: String): BitmessageAddress { fun loadIdentity(address: String): BitmessageAddress {
val privateKey = PrivateKey.read(getResource(address + ".privkey")) val privateKey = PrivateKey.read(getResource("$address.privkey"))
val identity = BitmessageAddress(privateKey) val identity = BitmessageAddress(privateKey)
Assert.assertEquals(address, identity.address) Assert.assertEquals(address, identity.address)
return identity return identity

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.2.50' ext.kotlin_version = '1.2.51'
ext.anko_version = '0.10.5' ext.anko_version = '0.10.5'
repositories { repositories {
jcenter() jcenter()