🔥 Massively simplified how Abit connects to the network

* Removed features "Synchronization" and "Server POW"
* Service isn't foreground anymore (not yet sure this is a good decision)
* "Full node" renamed to "online"
This commit is contained in:
Christian Basler 2018-07-05 19:44:04 +02:00
parent 87bc01701c
commit 3767d976c8
31 changed files with 101 additions and 833 deletions

View File

@ -11,8 +11,6 @@
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_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
android:name="android.support.multidex.MultiDexApplication"
@ -122,18 +120,12 @@
<service
android:name=".service.BitmessageService"
android:description="@string/bitmessage_service_description"
android:exported="false" />
<service
android:name=".service.ProofOfWorkService"
android:exported="false" />
<!-- Synchronization -->
<provider
android:name=".synchronization.StubProvider"
android:authorities="ch.dissem.apps.abit.provider"
android:exported="false"
android:syncable="true" />
<!-- Exports -->
<provider
android:name="android.support.v4.content.FileProvider"
@ -145,30 +137,6 @@
android:resource="@xml/file_paths" />
</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
android:name=".service.BitmessageIntentService"
android:exported="false" />
@ -182,7 +150,7 @@
</receiver>
<service
android:name=".service.StartupNodeOnWifiService"
android:name=".service.NodeStartupService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />

View File

@ -76,7 +76,7 @@ class ComposeMessageFragment : Fragment() {
parents.addAll(draft.parents)
} else {
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!!)
}
if (id?.privateKey != null) {

View File

@ -31,7 +31,6 @@ import ch.dissem.apps.abit.listener.ListSelectionListener
import ch.dissem.apps.abit.repository.AndroidLabelRepository.Companion.LABEL_ARCHIVE
import ch.dissem.apps.abit.service.Singleton
import ch.dissem.apps.abit.service.Singleton.currentLabel
import ch.dissem.apps.abit.synchronization.SyncAdapter
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences
import ch.dissem.apps.abit.util.getColor
@ -145,11 +144,6 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
ComposeMessageActivity.launchReplyTo(this, item)
}
if (Preferences.useTrustedNode(this)) {
SyncAdapter.startSync(this)
} else {
SyncAdapter.stopSync(this)
}
if (drawer.isDrawerOpen) {
MaterialShowcaseView.Builder(this)
.setMaskColour(R.color.colorPrimary)
@ -179,8 +173,6 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
.setDelay(1000)
.show()
}
SyncAdapter.startSync(this)
}
private fun <F> changeList(listFragment: F) where F : Fragment, F : ListHolder<*> {
@ -259,14 +251,13 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
nodeSwitch = SwitchDrawerItem()
.withIdentifier(ID_NODE_SWITCH)
.withName(R.string.full_node)
.withName(R.string.online)
.withIcon(CommunityMaterial.Icon.cmd_cloud_outline)
.withChecked(Preferences.isFullNodeActive(this))
.withChecked(Preferences.isOnline(this))
.withOnCheckedChangeListener { _, _, isChecked ->
Preferences.setOnline(this, isChecked)
if (isChecked) {
NetworkUtils.enableNode(this@MainActivity)
} else {
NetworkUtils.disableNode(this@MainActivity)
NetworkUtils.enableNode(this, true)
}
}
@ -369,10 +360,8 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
}
override fun onResume() {
NetworkUtils.enableNode(this, false)
updateUnread()
if (Preferences.isFullNodeActive(this) && Preferences.isConnectionAllowed(this@MainActivity)) {
NetworkUtils.enableNode(this, false)
}
Singleton.getMessageListener(this).resetNotification()
currentLabel.addObserver(this) { label ->
if (label != null && label.id is Long) {
@ -578,15 +567,6 @@ class MainActivity : AppCompatActivity(), ListSelectionListener<Serializable> {
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,
* it's ignored.

View File

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

View File

@ -33,9 +33,6 @@ import android.widget.Toast
import ch.dissem.apps.abit.service.BatchProcessorService
import ch.dissem.apps.abit.service.SimpleJob
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.NetworkUtils
import ch.dissem.apps.abit.util.Preferences
@ -51,8 +48,7 @@ import java.util.*
/**
* @author Christian Basler
*/
class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener,
PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
class SettingsFragment : PreferenceFragmentCompat(), PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
@ -187,24 +183,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 {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
if (service is BatchProcessorService.BatchBinder) {
@ -250,11 +228,7 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
private fun connectivityChangeListener() =
OnPreferenceChangeListener { _, _ ->
context?.let { ctx ->
if (Preferences.isFullNodeActive(ctx)) {
NetworkUtils.scheduleNodeStart(ctx)
}
}
context?.let { ctx -> NetworkUtils.scheduleNodeStart(ctx) }
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

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

View File

@ -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) {
conversationService.getSubject(listOf(plaintext))?.let { subject ->
plaintext.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray())

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

@ -23,8 +23,9 @@ import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.os.Handler
import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.notification.ErrorNotification
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
@ -74,7 +75,6 @@ class BitmessageService : Service() {
if (!isRunning) {
running = true
notification.connecting()
startForeground(NETWORK_NOTIFICATION_ID, notification.notification)
if (!bmc.isRunning()) {
bmc.startup()
}

View File

@ -2,8 +2,6 @@ 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
@ -14,11 +12,11 @@ import ch.dissem.apps.abit.util.Preferences
*
* And stops it when the preconditions for the job (unmetered network) aren't met anymore.
*/
class StartupNodeOnWifiService : JobService() {
class NodeStartupService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
val bmc = Singleton.getBitmessageContext(this)
if (Preferences.isFullNodeActive(this) && !bmc.isRunning()) {
if (Preferences.isOnline(this) && !bmc.isRunning()) {
NetworkUtils.doStartBitmessageService(applicationContext)
}
return true
@ -28,6 +26,6 @@ class StartupNodeOnWifiService : JobService() {
* 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)
override fun onStopJob(params: JobParameters?) = false
}

View File

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

View File

@ -3,6 +3,7 @@ package ch.dissem.apps.abit.service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_BOOT_COMPLETED
import ch.dissem.apps.abit.util.NetworkUtils
import ch.dissem.apps.abit.util.Preferences
@ -11,10 +12,8 @@ import ch.dissem.apps.abit.util.Preferences
*/
class StartServiceReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action == "android.intent.action.BOOT_COMPLETED") {
if (Preferences.isFullNodeActive(context)) {
NetworkUtils.enableNode(context, false)
}
if (intent?.action == ACTION_BOOT_COMPLETED && Preferences.isOnline(context)) {
NetworkUtils.enableNode(context, false)
}
}
}

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
*/
object Constants {
const val PREFERENCE_ONLINE = "online"
const val PREFERENCE_WIFI_ONLY = "wifi_only"
const val PREFERENCE_REQUIRE_CHARGING = "require_charging"
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_POW_AVERAGE = "average_pow_time_ms"
const val PREFERENCE_POW_COUNT = "pow_count"

View File

@ -6,17 +6,15 @@ import android.app.job.JobScheduler
import android.content.ComponentName
import android.content.Context
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.service.BitmessageService
import ch.dissem.apps.abit.service.StartupNodeOnWifiService
import ch.dissem.apps.abit.service.NodeStartupService
object NetworkUtils {
fun enableNode(ctx: Context, ask: Boolean = true) {
Preferences.setFullNodeActive(ctx, true)
if (Preferences.isConnectionAllowed(ctx) || !ask) {
scheduleNodeStart(ctx)
} else {
@ -31,25 +29,29 @@ object NetworkUtils {
}
fun doStartBitmessageService(ctx: Context) {
ContextCompat.startForegroundService(ctx, Intent(ctx, BitmessageService::class.java))
ctx.startService(Intent(ctx, BitmessageService::class.java))
}
fun disableNode(ctx: Context) {
Preferences.setFullNodeActive(ctx, false)
ctx.stopService(Intent(ctx, BitmessageService::class.java))
}
fun scheduleNodeStart(ctx: Context) {
val jobScheduler = ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val serviceComponent = ComponentName(ctx, StartupNodeOnWifiService::class.java)
val serviceComponent = ComponentName(ctx, NodeStartupService::class.java)
val builder = JobInfo.Builder(0, serviceComponent)
if (Preferences.isWifiOnly(ctx)) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
when {
Preferences.isWifiOnly(ctx) ->
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)
}
if (Preferences.requireCharging(ctx)) {
builder.setRequiresCharging(true)
}
builder.setBackoffCriteria(0L, JobInfo.BACKOFF_POLICY_LINEAR)
builder.setPeriodic(15 * 60 * 1000L)
builder.setPersisted(true)
jobScheduler.schedule(builder.build())
}

View File

@ -8,11 +8,11 @@ import kotlin.properties.Delegates
class Observable<T>(value: T) {
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) {
observers.values.forEach { it.invoke(new) }
}
})
}
/**
* The key will make sure the observer can easily be removed. Usually the key should be either

View File

@ -17,26 +17,20 @@
package ch.dissem.apps.abit.util
import android.content.Context
import ch.dissem.apps.abit.R
import ch.dissem.apps.abit.notification.ErrorNotification
import android.content.Intent
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_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_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 org.jetbrains.anko.batteryManager
import org.jetbrains.anko.connectivityManager
import org.jetbrains.anko.defaultSharedPreferences
import org.slf4j.LoggerFactory
import java.io.File
import java.io.IOException
import java.net.InetAddress
import android.os.BatteryManager
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
/**
@ -45,50 +39,6 @@ import android.os.Build
object Preferences {
private val LOG = LoggerFactory.getLogger(Preferences::class.java)
fun useTrustedNode(ctx: Context): Boolean {
val trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return false
return trustedNode.trim { it <= ' ' }.isNotEmpty()
}
/**
* 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())) {
val index = trustedNode.lastIndexOf(':')
trustedNode = trustedNode.substring(0, index)
}
return InetAddress.getByName(trustedNode)
}
fun getTrustedNodePort(ctx: Context): Int {
var trustedNode: String = getPreference(ctx, PREFERENCE_TRUSTED_NODE) ?: return 8444
trustedNode = trustedNode.trim { it <= ' ' }
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$".toRegex())) {
val index = trustedNode.lastIndexOf(':')
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
private fun getPreference(ctx: Context, name: String): String? = ctx.defaultSharedPreferences.getString(name, null)
fun isConnectionAllowed(ctx: Context) = isAllowedForWiFi(ctx) && isAllowedForCharging(ctx)
private fun isAllowedForWiFi(ctx: Context) = !isWifiOnly(ctx) || !ctx.connectivityManager.isActiveNetworkMetered
@ -113,25 +63,9 @@ object Preferences {
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)
@ -150,4 +84,18 @@ object Preferences {
}
}
}
fun isOnline(ctx: Context) = ctx.defaultSharedPreferences.getBoolean(PREFERENCE_ONLINE, true)
fun setOnline(ctx: Context, status: Boolean) {
ctx.defaultSharedPreferences.edit()
.putBoolean(PREFERENCE_ONLINE, status)
.apply()
if (status) {
NetworkUtils.enableNode(ctx, true)
} else {
NetworkUtils.disableNode(ctx)
}
}
}

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="delete">حذف</string>
<string name="empty_trash">أفرغ المهملات</string>
<string name="trusted_node">عقدة موثوقة</string>
<string name="trusted_node_summary">استخدام العقدة في التزامن</string>
<string name="write_message">كتابة رسالة</string>
<string name="full_node">عقدة كاملة</string>
<string name="send">إرسال</string>
@ -51,15 +49,10 @@
<string name="mark_unread">حدد كمقروء</string>
<string name="archive">أرشيف</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="error_invalid_sync_port">إعدادات منفذ التزامن غير صالحة</string>
<string name="compose_body_hint">كتابة رسالة</string>
<string name="contacts_and_subscriptions">جهات اتصال</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="about">عن Abit</string>
<string name="about_summary">التبعيات مفتوحة المصدر.</string>

View File

@ -35,10 +35,6 @@
<string name="archive">Archiv</string>
<string name="empty_trash">Papierkorb leeren</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="full_node">Aktiver Knoten</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_text_0">Arbeite am Versenden</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="contacts_and_subscriptions">Kontakte</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="about">Über Abit</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="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="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>

View File

@ -36,9 +36,6 @@
<string name="mark_unread">Marquer non lu</string>
<string name="archive">Archive</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="full_node">Nœud actif</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_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="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="contacts_and_subscriptions">Contacts</string>
<string name="subscribed">Souscrit</string>
@ -94,9 +90,6 @@
<string name="use_mobile_network">Utiliser le réseau mobile</string>
<string name="personal_message">Message</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="compose_message">Composer</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="encoding_simple">simple</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>

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="unknown">Unknown</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>

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:title="@string/import_data" />
<PreferenceScreen
android:key="preference_experimental"
android:title="@string/preference_group_experimental"
android:summary="@string/preference_group_experimental_summary"
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>
<Preference
android:key="status"
android:summary="@string/status_summary"
android:title="@string/status" />
</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 {
val privateKey = PrivateKey.read(getResource(address + ".privkey"))
val privateKey = PrivateKey.read(getResource("$address.privkey"))
val identity = BitmessageAddress(privateKey)
Assert.assertEquals(address, identity.address)
return identity