Some Bugfixes and some Kotlin that helped fixing the bugs
This commit is contained in:
parent
e064012551
commit
898c49802b
@ -1,4 +1,5 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'idea'
|
apply plugin: 'idea'
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
@ -41,6 +42,7 @@ ext.jabitVersion = 'feature-exports-SNAPSHOT'
|
|||||||
ext.supportVersion = '25.3.1'
|
ext.supportVersion = '25.3.1'
|
||||||
dependencies {
|
dependencies {
|
||||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||||
|
|
||||||
compile "com.android.support:appcompat-v7:$supportVersion"
|
compile "com.android.support:appcompat-v7:$supportVersion"
|
||||||
compile "com.android.support:preference-v7:$supportVersion"
|
compile "com.android.support:preference-v7:$supportVersion"
|
||||||
|
@ -1,195 +0,0 @@
|
|||||||
package ch.dissem.apps.abit.repository;
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteConstraintException;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteDoneException;
|
|
||||||
import android.database.sqlite.SQLiteStatement;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
|
||||||
import ch.dissem.bitmessage.exception.ApplicationException;
|
|
||||||
import ch.dissem.bitmessage.ports.NodeRegistry;
|
|
||||||
import ch.dissem.bitmessage.utils.Collections;
|
|
||||||
import ch.dissem.bitmessage.utils.SqlStrings;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes;
|
|
||||||
import static ch.dissem.bitmessage.utils.Strings.hex;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.now;
|
|
||||||
import static java.lang.String.valueOf;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class AndroidNodeRegistry implements NodeRegistry {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AndroidInventory.class);
|
|
||||||
|
|
||||||
private static final String TABLE_NAME = "Node";
|
|
||||||
private static final String COLUMN_STREAM = "stream";
|
|
||||||
private static final String COLUMN_ADDRESS = "address";
|
|
||||||
private static final String COLUMN_PORT = "port";
|
|
||||||
private static final String COLUMN_SERVICES = "services";
|
|
||||||
private static final String COLUMN_TIME = "time";
|
|
||||||
|
|
||||||
private final ThreadLocal<SQLiteStatement> loadExistingStatement = new ThreadLocal<>();
|
|
||||||
|
|
||||||
private final SqlHelper sql;
|
|
||||||
private Map<Long, Set<NetworkAddress>> stableNodes;
|
|
||||||
|
|
||||||
public AndroidNodeRegistry(SqlHelper sql) {
|
|
||||||
this.sql = sql;
|
|
||||||
cleanUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanUp() {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
db.delete(TABLE_NAME, "time < ?", new String[]{valueOf(now() - 28 * DAY)});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clear() {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
db.delete(TABLE_NAME, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long loadExistingTime(NetworkAddress node) {
|
|
||||||
SQLiteStatement statement = loadExistingStatement.get();
|
|
||||||
if (statement == null) {
|
|
||||||
statement = sql.getWritableDatabase().compileStatement(
|
|
||||||
"SELECT " + COLUMN_TIME +
|
|
||||||
" FROM " + TABLE_NAME +
|
|
||||||
" WHERE stream=? AND address=? AND port=?"
|
|
||||||
);
|
|
||||||
loadExistingStatement.set(statement);
|
|
||||||
}
|
|
||||||
statement.bindLong(1, node.getStream());
|
|
||||||
statement.bindBlob(2, node.getIPv6());
|
|
||||||
statement.bindLong(3, node.getPort());
|
|
||||||
try {
|
|
||||||
return statement.simpleQueryForLong();
|
|
||||||
} catch (SQLiteDoneException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public List<NetworkAddress> getKnownAddresses(int limit, long... streams) {
|
|
||||||
String[] projection = {
|
|
||||||
COLUMN_STREAM,
|
|
||||||
COLUMN_ADDRESS,
|
|
||||||
COLUMN_PORT,
|
|
||||||
COLUMN_SERVICES,
|
|
||||||
COLUMN_TIME
|
|
||||||
};
|
|
||||||
|
|
||||||
List<NetworkAddress> result = new LinkedList<>();
|
|
||||||
SQLiteDatabase db = sql.getReadableDatabase();
|
|
||||||
try (Cursor c = db.query(
|
|
||||||
TABLE_NAME, projection,
|
|
||||||
"stream IN (?)",
|
|
||||||
new String[]{SqlStrings.join(streams)},
|
|
||||||
null, null,
|
|
||||||
"time DESC",
|
|
||||||
valueOf(limit)
|
|
||||||
)) {
|
|
||||||
while (c.moveToNext()) {
|
|
||||||
result.add(
|
|
||||||
new NetworkAddress.Builder()
|
|
||||||
.stream(c.getLong(c.getColumnIndex(COLUMN_STREAM)))
|
|
||||||
.ipv6(c.getBlob(c.getColumnIndex(COLUMN_ADDRESS)))
|
|
||||||
.port(c.getInt(c.getColumnIndex(COLUMN_PORT)))
|
|
||||||
.services(c.getLong(c.getColumnIndex(COLUMN_SERVICES)))
|
|
||||||
.time(c.getLong(c.getColumnIndex(COLUMN_TIME)))
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
throw new ApplicationException(e);
|
|
||||||
}
|
|
||||||
if (result.isEmpty()) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (stableNodes == null) {
|
|
||||||
stableNodes = loadStableNodes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (long stream : streams) {
|
|
||||||
Set<NetworkAddress> nodes = stableNodes.get(stream);
|
|
||||||
if (nodes != null && !nodes.isEmpty()) {
|
|
||||||
result.add(Collections.selectRandom(nodes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void offerAddresses(List<NetworkAddress> nodes) {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
db.beginTransaction();
|
|
||||||
try {
|
|
||||||
cleanUp();
|
|
||||||
for (NetworkAddress node : nodes) {
|
|
||||||
if (node.getTime() < now() + 5 * MINUTE && node.getTime() > now() - 28 * DAY) {
|
|
||||||
synchronized (this) {
|
|
||||||
Long existing = loadExistingTime(node);
|
|
||||||
if (existing == null) {
|
|
||||||
insert(node);
|
|
||||||
} else if (node.getTime() > existing) {
|
|
||||||
update(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
db.setTransactionSuccessful();
|
|
||||||
} finally {
|
|
||||||
db.endTransaction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insert(NetworkAddress node) {
|
|
||||||
try {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
// Create a new map of values, where column names are the keys
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(COLUMN_STREAM, node.getStream());
|
|
||||||
values.put(COLUMN_ADDRESS, node.getIPv6());
|
|
||||||
values.put(COLUMN_PORT, node.getPort());
|
|
||||||
values.put(COLUMN_SERVICES, node.getServices());
|
|
||||||
values.put(COLUMN_TIME, node.getTime());
|
|
||||||
|
|
||||||
db.insertOrThrow(TABLE_NAME, null, values);
|
|
||||||
} catch (SQLiteConstraintException e) {
|
|
||||||
LOG.trace(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void update(NetworkAddress node) {
|
|
||||||
try {
|
|
||||||
SQLiteDatabase db = sql.getWritableDatabase();
|
|
||||||
// Create a new map of values, where column names are the keys
|
|
||||||
ContentValues values = new ContentValues();
|
|
||||||
values.put(COLUMN_SERVICES, node.getServices());
|
|
||||||
values.put(COLUMN_TIME, node.getTime());
|
|
||||||
|
|
||||||
db.update(TABLE_NAME, values,
|
|
||||||
"stream=" + node.getStream() + " AND address=X'" + hex(node.getIPv6()) + "' AND " +
|
|
||||||
"port=" + node.getPort(),
|
|
||||||
null);
|
|
||||||
} catch (SQLiteConstraintException e) {
|
|
||||||
LOG.trace(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,172 @@
|
|||||||
|
package ch.dissem.apps.abit.repository
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.database.sqlite.SQLiteConstraintException
|
||||||
|
import android.database.sqlite.SQLiteDoneException
|
||||||
|
import android.database.sqlite.SQLiteStatement
|
||||||
|
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
|
||||||
|
import ch.dissem.bitmessage.exception.ApplicationException
|
||||||
|
import ch.dissem.bitmessage.ports.NodeRegistry
|
||||||
|
import ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes
|
||||||
|
import ch.dissem.bitmessage.utils.Collections
|
||||||
|
import ch.dissem.bitmessage.utils.SqlStrings
|
||||||
|
import ch.dissem.bitmessage.utils.Strings.hex
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.MINUTE
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.now
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.concurrent.getOrSet
|
||||||
|
|
||||||
|
const val MAX_ENTRY_AGE = 7 * DAY
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
class AndroidNodeRegistry(private val sql: SqlHelper) : NodeRegistry {
|
||||||
|
|
||||||
|
private val loadExistingStatement = ThreadLocal<SQLiteStatement>()
|
||||||
|
private var stableNodes: Map<Long, Set<NetworkAddress>> = emptyMap()
|
||||||
|
get() {
|
||||||
|
if (field.isEmpty())
|
||||||
|
field = loadStableNodes()
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
cleanUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanUp() {
|
||||||
|
sql.writableDatabase.delete(TABLE_NAME, "time < ?", arrayOf((now - MAX_ENTRY_AGE).toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
sql.writableDatabase.delete(TABLE_NAME, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadExistingTime(node: NetworkAddress): Long? {
|
||||||
|
val statement: SQLiteStatement = loadExistingStatement.getOrSet {
|
||||||
|
sql.writableDatabase.compileStatement(
|
||||||
|
"SELECT $COLUMN_TIME FROM $TABLE_NAME WHERE stream=? AND address=? AND port=?"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
statement.bindLong(1, node.stream)
|
||||||
|
statement.bindBlob(2, node.IPv6)
|
||||||
|
statement.bindLong(3, node.port.toLong())
|
||||||
|
try {
|
||||||
|
return statement.simpleQueryForLong()
|
||||||
|
} catch (e: SQLiteDoneException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress> {
|
||||||
|
val projection = arrayOf(COLUMN_STREAM, COLUMN_ADDRESS, COLUMN_PORT, COLUMN_SERVICES, COLUMN_TIME)
|
||||||
|
|
||||||
|
val result = LinkedList<NetworkAddress>()
|
||||||
|
try {
|
||||||
|
sql.readableDatabase.query(
|
||||||
|
TABLE_NAME, projection,
|
||||||
|
"stream IN (?)",
|
||||||
|
arrayOf(SqlStrings.join(*streams)), null, null,
|
||||||
|
"time DESC",
|
||||||
|
limit.toString()
|
||||||
|
).use { c ->
|
||||||
|
while (c.moveToNext()) {
|
||||||
|
result.add(NetworkAddress(
|
||||||
|
time = c.getLong(c.getColumnIndex(COLUMN_TIME)),
|
||||||
|
stream = c.getLong(c.getColumnIndex(COLUMN_STREAM)),
|
||||||
|
services = c.getLong(c.getColumnIndex(COLUMN_SERVICES)),
|
||||||
|
IPv6 = c.getBlob(c.getColumnIndex(COLUMN_ADDRESS)),
|
||||||
|
port = c.getInt(c.getColumnIndex(COLUMN_PORT))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
LOG.error(e.message, e)
|
||||||
|
throw ApplicationException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
streams
|
||||||
|
.asSequence()
|
||||||
|
.mapNotNull { stableNodes[it] }
|
||||||
|
.filterNot { it.isEmpty() }
|
||||||
|
.mapTo(result) { Collections.selectRandom(it) }
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun offerAddresses(nodes: List<NetworkAddress>) {
|
||||||
|
val db = sql.writableDatabase
|
||||||
|
db.beginTransaction()
|
||||||
|
try {
|
||||||
|
cleanUp()
|
||||||
|
nodes
|
||||||
|
.filter {
|
||||||
|
// Don't accept nodes from the future, it might be a trap
|
||||||
|
it.time < now + 5 * MINUTE && it.time > now - MAX_ENTRY_AGE
|
||||||
|
}
|
||||||
|
.forEach { node ->
|
||||||
|
synchronized(this) {
|
||||||
|
val existing = loadExistingTime(node)
|
||||||
|
if (existing == null) {
|
||||||
|
insert(node)
|
||||||
|
} else if (node.time > existing) {
|
||||||
|
update(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.setTransactionSuccessful()
|
||||||
|
} finally {
|
||||||
|
db.endTransaction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun insert(node: NetworkAddress) {
|
||||||
|
try {
|
||||||
|
// Create a new map of values, where column names are the keys
|
||||||
|
val values = ContentValues()
|
||||||
|
values.put(COLUMN_STREAM, node.stream)
|
||||||
|
values.put(COLUMN_ADDRESS, node.IPv6)
|
||||||
|
values.put(COLUMN_PORT, node.port)
|
||||||
|
values.put(COLUMN_SERVICES, node.services)
|
||||||
|
values.put(COLUMN_TIME, node.time)
|
||||||
|
|
||||||
|
sql.writableDatabase.insertOrThrow(TABLE_NAME, null, values)
|
||||||
|
} catch (e: SQLiteConstraintException) {
|
||||||
|
LOG.trace(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun update(node: NetworkAddress) {
|
||||||
|
try {
|
||||||
|
// Create a new map of values, where column names are the keys
|
||||||
|
val values = ContentValues()
|
||||||
|
values.put(COLUMN_SERVICES, node.services)
|
||||||
|
values.put(COLUMN_TIME, node.time)
|
||||||
|
|
||||||
|
sql.writableDatabase.update(
|
||||||
|
TABLE_NAME,
|
||||||
|
values,
|
||||||
|
"stream=${node.stream} AND address=X'${hex(node.IPv6)}' AND port=${node.port}",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} catch (e: SQLiteConstraintException) {
|
||||||
|
LOG.trace(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
private val LOG = LoggerFactory.getLogger(AndroidInventory::class.java)
|
||||||
|
|
||||||
|
private const val TABLE_NAME = "Node"
|
||||||
|
private const val COLUMN_STREAM = "stream"
|
||||||
|
private const val COLUMN_ADDRESS = "address"
|
||||||
|
private const val COLUMN_PORT = "port"
|
||||||
|
private const val COLUMN_SERVICES = "services"
|
||||||
|
private const val COLUMN_TIME = "time"
|
||||||
|
}
|
||||||
|
}
|
@ -1,106 +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.Intent;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.notification.NetworkNotification;
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext;
|
|
||||||
import ch.dissem.bitmessage.utils.Property;
|
|
||||||
|
|
||||||
import static ch.dissem.apps.abit.notification.NetworkNotification.NETWORK_NOTIFICATION_ID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a Service that returns an IBinder for the
|
|
||||||
* sync adapter class, allowing the sync adapter framework to call
|
|
||||||
* onPerformSync().
|
|
||||||
*/
|
|
||||||
public class BitmessageService extends Service {
|
|
||||||
private static BitmessageContext bmc = null;
|
|
||||||
private static volatile boolean running = false;
|
|
||||||
|
|
||||||
private NetworkNotification notification = null;
|
|
||||||
|
|
||||||
private final Handler cleanupHandler = new Handler();
|
|
||||||
private final Runnable cleanupTask = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
bmc.cleanup();
|
|
||||||
if (isRunning()) {
|
|
||||||
cleanupHandler.postDelayed(this, 24 * 60 * 60 * 1000L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static boolean isRunning() {
|
|
||||||
return running && bmc.isRunning();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
if (bmc == null) {
|
|
||||||
bmc = Singleton.getBitmessageContext(this);
|
|
||||||
}
|
|
||||||
notification = new NetworkNotification(this);
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
|
||||||
if (!isRunning()) {
|
|
||||||
running = true;
|
|
||||||
notification.connecting();
|
|
||||||
startForeground(NETWORK_NOTIFICATION_ID, notification.getNotification());
|
|
||||||
if (!bmc.isRunning()) {
|
|
||||||
bmc.startup();
|
|
||||||
}
|
|
||||||
notification.show();
|
|
||||||
cleanupHandler.postDelayed(cleanupTask, 24 * 60 * 60 * 1000L);
|
|
||||||
}
|
|
||||||
return Service.START_STICKY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
if (bmc.isRunning()) {
|
|
||||||
bmc.shutdown();
|
|
||||||
}
|
|
||||||
running = false;
|
|
||||||
notification.showShutdown();
|
|
||||||
cleanupHandler.removeCallbacks(cleanupTask);
|
|
||||||
bmc.cleanup();
|
|
||||||
stopSelf();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Property getStatus() {
|
|
||||||
if (bmc != null) {
|
|
||||||
return bmc.status();
|
|
||||||
} else {
|
|
||||||
return new Property("bitmessage context");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* 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.Intent
|
||||||
|
import android.os.Handler
|
||||||
|
import ch.dissem.apps.abit.notification.NetworkNotification
|
||||||
|
import ch.dissem.apps.abit.notification.NetworkNotification.NETWORK_NOTIFICATION_ID
|
||||||
|
import ch.dissem.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.utils.Property
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 cleanupHandler = Handler()
|
||||||
|
private val cleanupTask = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
bmc.cleanup()
|
||||||
|
if (isRunning) {
|
||||||
|
cleanupHandler.postDelayed(this, 24 * 60 * 60 * 1000L) // once a day
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
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)
|
||||||
|
bmc.cleanup()
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent) = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Volatile private var running = false
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val isRunning: Boolean
|
||||||
|
get() = running && Singleton.bitmessageContext?.isRunning() ?: false
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val status: Property
|
||||||
|
get() = Singleton.bitmessageContext?.status() ?: Property("bitmessage context")
|
||||||
|
}
|
||||||
|
}
|
@ -1,177 +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.content.Context;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.MainActivity;
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
import ch.dissem.apps.abit.adapter.AndroidCryptography;
|
|
||||||
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.AndroidAddressRepository;
|
|
||||||
import ch.dissem.apps.abit.repository.AndroidInventory;
|
|
||||||
import ch.dissem.apps.abit.repository.AndroidMessageRepository;
|
|
||||||
import ch.dissem.apps.abit.repository.AndroidNodeRegistry;
|
|
||||||
import ch.dissem.apps.abit.repository.AndroidProofOfWorkRepository;
|
|
||||||
import ch.dissem.apps.abit.repository.SqlHelper;
|
|
||||||
import ch.dissem.apps.abit.util.Constants;
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext;
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
|
||||||
import ch.dissem.bitmessage.networking.nio.NioNetworkHandler;
|
|
||||||
import ch.dissem.bitmessage.ports.AddressRepository;
|
|
||||||
import ch.dissem.bitmessage.ports.MessageRepository;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
|
||||||
import ch.dissem.bitmessage.utils.ConversationService;
|
|
||||||
import ch.dissem.bitmessage.utils.TTL;
|
|
||||||
|
|
||||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides singleton objects across the application.
|
|
||||||
*/
|
|
||||||
public class Singleton {
|
|
||||||
private static BitmessageContext bitmessageContext;
|
|
||||||
private static ConversationService conversationService;
|
|
||||||
private static MessageListener messageListener;
|
|
||||||
private static BitmessageAddress identity;
|
|
||||||
private static AndroidProofOfWorkRepository powRepo;
|
|
||||||
private static boolean creatingIdentity;
|
|
||||||
|
|
||||||
public static BitmessageContext getBitmessageContext(Context context) {
|
|
||||||
if (bitmessageContext == null) {
|
|
||||||
synchronized (Singleton.class) {
|
|
||||||
if (bitmessageContext == null) {
|
|
||||||
final Context ctx = context.getApplicationContext();
|
|
||||||
SqlHelper sqlHelper = new SqlHelper(ctx);
|
|
||||||
powRepo = new AndroidProofOfWorkRepository(sqlHelper);
|
|
||||||
TTL.pubkey(2 * DAY);
|
|
||||||
bitmessageContext = new BitmessageContext.Builder()
|
|
||||||
.proofOfWorkEngine(new SwitchingProofOfWorkEngine(
|
|
||||||
ctx, Constants.PREFERENCE_SERVER_POW,
|
|
||||||
new ServerPowEngine(ctx),
|
|
||||||
new ServicePowEngine(ctx)
|
|
||||||
))
|
|
||||||
.cryptography(new AndroidCryptography())
|
|
||||||
.nodeRegistry(new AndroidNodeRegistry(sqlHelper))
|
|
||||||
.inventory(new AndroidInventory(sqlHelper))
|
|
||||||
.addressRepo(new AndroidAddressRepository(sqlHelper))
|
|
||||||
.messageRepo(new AndroidMessageRepository(sqlHelper, ctx))
|
|
||||||
.powRepo(powRepo)
|
|
||||||
.networkHandler(new NioNetworkHandler())
|
|
||||||
.listener(getMessageListener(ctx))
|
|
||||||
.doNotSendPubkeyOnIdentityCreation()
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bitmessageContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MessageListener getMessageListener(Context ctx) {
|
|
||||||
if (messageListener == null) {
|
|
||||||
synchronized (Singleton.class) {
|
|
||||||
if (messageListener == null) {
|
|
||||||
messageListener = new MessageListener(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return messageListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AndroidMessageRepository getMessageRepository(Context ctx) {
|
|
||||||
return (AndroidMessageRepository) getBitmessageContext(ctx).messages();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AndroidAddressRepository getAddressRepository(Context ctx) {
|
|
||||||
return (AndroidAddressRepository) getBitmessageContext(ctx).addresses();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ProofOfWorkRepository getProofOfWorkRepository(Context ctx) {
|
|
||||||
if (powRepo == null) getBitmessageContext(ctx);
|
|
||||||
return powRepo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BitmessageAddress getIdentity(final Context ctx) {
|
|
||||||
if (identity == null) {
|
|
||||||
final BitmessageContext bmc = getBitmessageContext(ctx);
|
|
||||||
synchronized (Singleton.class) {
|
|
||||||
if (identity == null) {
|
|
||||||
List<BitmessageAddress> identities = bmc.addresses()
|
|
||||||
.getIdentities();
|
|
||||||
if (identities.size() > 0) {
|
|
||||||
identity = identities.get(0);
|
|
||||||
} else {
|
|
||||||
if (!creatingIdentity) {
|
|
||||||
creatingIdentity = true;
|
|
||||||
new AsyncTask<Void, Void, BitmessageAddress>() {
|
|
||||||
@Override
|
|
||||||
protected BitmessageAddress doInBackground(Void... args) {
|
|
||||||
BitmessageAddress identity = bmc.createIdentity(false,
|
|
||||||
Pubkey.Feature.DOES_ACK);
|
|
||||||
identity.setAlias(
|
|
||||||
ctx.getString(R.string.alias_default_identity)
|
|
||||||
);
|
|
||||||
bmc.addresses().save(identity);
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(BitmessageAddress identity) {
|
|
||||||
Singleton.identity = identity;
|
|
||||||
Toast.makeText(ctx,
|
|
||||||
R.string.toast_identity_created,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
MainActivity mainActivity = MainActivity.getInstance();
|
|
||||||
if (mainActivity != null) {
|
|
||||||
mainActivity.addIdentityEntry(identity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setIdentity(BitmessageAddress identity) {
|
|
||||||
if (identity.getPrivateKey() == null)
|
|
||||||
throw new IllegalArgumentException("Identity expected, but no private key available");
|
|
||||||
Singleton.identity = identity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ConversationService getConversationService(Context ctx) {
|
|
||||||
if (conversationService == null) {
|
|
||||||
final BitmessageContext bmc = getBitmessageContext(ctx);
|
|
||||||
synchronized (Singleton.class) {
|
|
||||||
if (conversationService == null) {
|
|
||||||
conversationService = new ConversationService(bmc.messages());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conversationService;
|
|
||||||
}
|
|
||||||
}
|
|
168
app/src/main/java/ch/dissem/apps/abit/service/Singleton.kt
Normal file
168
app/src/main/java/ch/dissem/apps/abit/service/Singleton.kt
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* 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.content.Context
|
||||||
|
import android.os.AsyncTask
|
||||||
|
import android.widget.Toast
|
||||||
|
import ch.dissem.apps.abit.MainActivity
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.apps.abit.adapter.AndroidCryptography
|
||||||
|
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.bitmessage.BitmessageContext
|
||||||
|
import ch.dissem.bitmessage.entity.BitmessageAddress
|
||||||
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
||||||
|
import ch.dissem.bitmessage.networking.nio.NioNetworkHandler
|
||||||
|
import ch.dissem.bitmessage.ports.ProofOfWorkRepository
|
||||||
|
import ch.dissem.bitmessage.utils.ConversationService
|
||||||
|
import ch.dissem.bitmessage.utils.TTL
|
||||||
|
import ch.dissem.bitmessage.utils.UnixTime.DAY
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides singleton objects across the application.
|
||||||
|
*/
|
||||||
|
object Singleton {
|
||||||
|
var bitmessageContext: BitmessageContext? = null
|
||||||
|
private set
|
||||||
|
private var conversationService: ConversationService? = null
|
||||||
|
private var messageListener: MessageListener? = null
|
||||||
|
private var identity: BitmessageAddress? = null
|
||||||
|
private var powRepo: AndroidProofOfWorkRepository? = null
|
||||||
|
private var creatingIdentity: Boolean = false
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getBitmessageContext(context: Context): BitmessageContext {
|
||||||
|
if (bitmessageContext == null) {
|
||||||
|
synchronized(Singleton::class.java) {
|
||||||
|
if (bitmessageContext == null) {
|
||||||
|
val ctx = context.applicationContext
|
||||||
|
val sqlHelper = SqlHelper(ctx)
|
||||||
|
powRepo = AndroidProofOfWorkRepository(sqlHelper)
|
||||||
|
TTL.pubkey = 2 * DAY
|
||||||
|
bitmessageContext = BitmessageContext.Builder()
|
||||||
|
.proofOfWorkEngine(SwitchingProofOfWorkEngine(
|
||||||
|
ctx, Constants.PREFERENCE_SERVER_POW,
|
||||||
|
ServerPowEngine(ctx),
|
||||||
|
ServicePowEngine(ctx)
|
||||||
|
))
|
||||||
|
.cryptography(AndroidCryptography())
|
||||||
|
.nodeRegistry(AndroidNodeRegistry(sqlHelper))
|
||||||
|
.inventory(AndroidInventory(sqlHelper))
|
||||||
|
.addressRepo(AndroidAddressRepository(sqlHelper))
|
||||||
|
.messageRepo(AndroidMessageRepository(sqlHelper, ctx))
|
||||||
|
.powRepo(powRepo!!)
|
||||||
|
.networkHandler(NioNetworkHandler())
|
||||||
|
.listener(getMessageListener(ctx))
|
||||||
|
.doNotSendPubkeyOnIdentityCreation()
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bitmessageContext!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getMessageListener(ctx: Context): MessageListener {
|
||||||
|
if (messageListener == null) {
|
||||||
|
synchronized(Singleton::class.java) {
|
||||||
|
if (messageListener == null) {
|
||||||
|
messageListener = MessageListener(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messageListener!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getMessageRepository(ctx: Context): AndroidMessageRepository {
|
||||||
|
return getBitmessageContext(ctx).messages as AndroidMessageRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getAddressRepository(ctx: Context): AndroidAddressRepository {
|
||||||
|
return getBitmessageContext(ctx).addresses as AndroidAddressRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getProofOfWorkRepository(ctx: Context): ProofOfWorkRepository {
|
||||||
|
if (powRepo == null) getBitmessageContext(ctx)
|
||||||
|
return powRepo!!
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getIdentity(ctx: Context): BitmessageAddress? {
|
||||||
|
if (identity == null) {
|
||||||
|
val bmc = getBitmessageContext(ctx)
|
||||||
|
synchronized(Singleton::class) {
|
||||||
|
if (identity == null) {
|
||||||
|
val identities = bmc.addresses.getIdentities()
|
||||||
|
if (identities.isNotEmpty()) {
|
||||||
|
identity = identities[0]
|
||||||
|
} else {
|
||||||
|
if (!creatingIdentity) {
|
||||||
|
creatingIdentity = true
|
||||||
|
object : AsyncTask<Void, Void, BitmessageAddress>() {
|
||||||
|
override fun doInBackground(vararg args: Void): BitmessageAddress {
|
||||||
|
val identity = bmc.createIdentity(false,
|
||||||
|
Pubkey.Feature.DOES_ACK)
|
||||||
|
identity.alias = ctx.getString(R.string.alias_default_identity)
|
||||||
|
bmc.addresses.save(identity)
|
||||||
|
return identity
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPostExecute(identity: BitmessageAddress) {
|
||||||
|
Singleton.identity = identity
|
||||||
|
Toast.makeText(ctx,
|
||||||
|
R.string.toast_identity_created,
|
||||||
|
Toast.LENGTH_SHORT).show()
|
||||||
|
val mainActivity = MainActivity.getInstance()
|
||||||
|
mainActivity?.addIdentityEntry(identity)
|
||||||
|
}
|
||||||
|
}.execute()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return identity
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setIdentity(identity: BitmessageAddress) {
|
||||||
|
if (identity.privateKey == null)
|
||||||
|
throw IllegalArgumentException("Identity expected, but no private key available")
|
||||||
|
Singleton.identity = identity
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getConversationService(ctx: Context): ConversationService {
|
||||||
|
if (conversationService == null) {
|
||||||
|
val bmc = getBitmessageContext(ctx)
|
||||||
|
synchronized(Singleton::class.java) {
|
||||||
|
if (conversationService == null) {
|
||||||
|
conversationService = ConversationService(bmc.messages)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return conversationService!!
|
||||||
|
}
|
||||||
|
}
|
@ -1,98 +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
|
|
||||||
*/
|
|
||||||
public class Authenticator extends AbstractAccountAuthenticator {
|
|
||||||
public static final Account ACCOUNT_SYNC = new Account("Bitmessage", "ch.dissem.bitmessage");
|
|
||||||
public static final Account ACCOUNT_POW = new Account("Proof of Work ", "ch.dissem.bitmessage");
|
|
||||||
|
|
||||||
// Simple constructor
|
|
||||||
public Authenticator(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Editing properties is not supported
|
|
||||||
@Override
|
|
||||||
public Bundle editProperties(
|
|
||||||
AccountAuthenticatorResponse r, String s) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't add additional accounts
|
|
||||||
@Override
|
|
||||||
public Bundle addAccount(
|
|
||||||
AccountAuthenticatorResponse r,
|
|
||||||
String s,
|
|
||||||
String s2,
|
|
||||||
String[] strings,
|
|
||||||
Bundle bundle) throws NetworkErrorException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore attempts to confirm credentials
|
|
||||||
@Override
|
|
||||||
public Bundle confirmCredentials(
|
|
||||||
AccountAuthenticatorResponse r,
|
|
||||||
Account account,
|
|
||||||
Bundle bundle) throws NetworkErrorException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getting an authentication token is not supported
|
|
||||||
@Override
|
|
||||||
public Bundle getAuthToken(
|
|
||||||
AccountAuthenticatorResponse r,
|
|
||||||
Account account,
|
|
||||||
String s,
|
|
||||||
Bundle bundle) throws NetworkErrorException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getting a label for the auth token is not supported
|
|
||||||
@Override
|
|
||||||
public String getAuthTokenLabel(String s) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updating user credentials is not supported
|
|
||||||
@Override
|
|
||||||
public Bundle updateCredentials(
|
|
||||||
AccountAuthenticatorResponse r,
|
|
||||||
Account account,
|
|
||||||
String s, Bundle bundle) throws NetworkErrorException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checking features for the account is not supported
|
|
||||||
@Override
|
|
||||||
public Bundle hasFeatures(
|
|
||||||
AccountAuthenticatorResponse r,
|
|
||||||
Account account, String[] strings) throws NetworkErrorException {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* 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) {
|
||||||
|
|
||||||
|
// Editing properties is not supported
|
||||||
|
override fun editProperties(r: AccountAuthenticatorResponse, s: String) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// Getting an authentication token is not supported
|
||||||
|
@Throws(NetworkErrorException::class)
|
||||||
|
override fun getAuthToken(
|
||||||
|
r: AccountAuthenticatorResponse,
|
||||||
|
account: Account,
|
||||||
|
s: String,
|
||||||
|
bundle: Bundle) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
// Getting a label for the auth token is not supported
|
||||||
|
override fun getAuthTokenLabel(s: String) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
// Updating user credentials is not supported
|
||||||
|
@Throws(NetworkErrorException::class)
|
||||||
|
override fun updateCredentials(
|
||||||
|
r: AccountAuthenticatorResponse,
|
||||||
|
account: Account,
|
||||||
|
s: String, bundle: Bundle) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
// Checking features for the account is not supported
|
||||||
|
@Throws(NetworkErrorException::class)
|
||||||
|
override fun hasFeatures(
|
||||||
|
r: AccountAuthenticatorResponse,
|
||||||
|
account: Account, strings: Array<String>) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField val ACCOUNT_SYNC = Account("Bitmessage", "ch.dissem.bitmessage")
|
||||||
|
@JvmField val ACCOUNT_POW = Account("Proof of Work ", "ch.dissem.bitmessage")
|
||||||
|
}
|
||||||
|
}
|
@ -14,34 +14,30 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ch.dissem.apps.abit.synchronization;
|
package ch.dissem.apps.abit.synchronization
|
||||||
|
|
||||||
import android.app.Service;
|
import android.app.Service
|
||||||
import android.content.Intent;
|
import android.content.Intent
|
||||||
import android.os.IBinder;
|
import android.os.IBinder
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A bound Service that instantiates the authenticator
|
* A bound Service that instantiates the authenticator
|
||||||
* when started.
|
* when started.
|
||||||
*/
|
*/
|
||||||
public class AuthenticatorService extends Service {
|
class AuthenticatorService : Service() {
|
||||||
/**
|
/**
|
||||||
* Instance field that stores the authenticator object
|
* Instance field that stores the authenticator object
|
||||||
*/
|
*/
|
||||||
private Authenticator authenticator;
|
private var authenticator: Authenticator? = null
|
||||||
|
|
||||||
@Override
|
override fun onCreate() {
|
||||||
public void onCreate() {
|
|
||||||
// Create a new authenticator object
|
// Create a new authenticator object
|
||||||
authenticator = new Authenticator(this);
|
authenticator = Authenticator(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When the system binds to this Service to make the RPC call
|
* When the system binds to this Service to make the RPC call
|
||||||
* return the authenticator's IBinder.
|
* return the authenticator's IBinder.
|
||||||
*/
|
*/
|
||||||
@Override
|
override fun onBind(intent: Intent) = authenticator?.iBinder
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return authenticator.getIBinder();
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,89 +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.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Define an implementation of ContentProvider that stubs out
|
|
||||||
* all methods
|
|
||||||
*/
|
|
||||||
public class StubProvider extends ContentProvider {
|
|
||||||
public static final String AUTHORITY = "ch.dissem.apps.abit.provider";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Always return true, indicating that the
|
|
||||||
* provider loaded correctly.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean onCreate() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return no type for MIME type
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getType(@NonNull Uri uri) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* query() always returns no results
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Cursor query(
|
|
||||||
@NonNull Uri uri,
|
|
||||||
String[] projection,
|
|
||||||
String selection,
|
|
||||||
String[] selectionArgs,
|
|
||||||
String sortOrder) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* insert() always returns null (no URI)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Uri insert(@NonNull Uri uri, ContentValues values) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* delete() always returns "no rows affected" (0)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* update() always returns "no rows affected" (0)
|
|
||||||
*/
|
|
||||||
public int update(
|
|
||||||
@NonNull Uri uri,
|
|
||||||
ContentValues values,
|
|
||||||
String selection,
|
|
||||||
String[] selectionArgs) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
}
|
||||||
|
}
|
@ -1,190 +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.AbstractThreadedSyncAdapter;
|
|
||||||
import android.content.ContentProviderClient;
|
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SyncResult;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.service.Singleton;
|
|
||||||
import ch.dissem.apps.abit.util.Preferences;
|
|
||||||
import ch.dissem.bitmessage.BitmessageContext;
|
|
||||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
|
||||||
import ch.dissem.bitmessage.entity.CustomMessage;
|
|
||||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
|
||||||
import ch.dissem.bitmessage.extensions.CryptoCustomMessage;
|
|
||||||
import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
|
|
||||||
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
|
|
||||||
|
|
||||||
import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_POW;
|
|
||||||
import static ch.dissem.apps.abit.synchronization.Authenticator.ACCOUNT_SYNC;
|
|
||||||
import static ch.dissem.apps.abit.synchronization.StubProvider.AUTHORITY;
|
|
||||||
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.CALCULATE;
|
|
||||||
import static ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest.Request.COMPLETE;
|
|
||||||
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync Adapter to synchronize with the Bitmessage network - fetches
|
|
||||||
* new objects and then disconnects.
|
|
||||||
*/
|
|
||||||
public class SyncAdapter extends AbstractThreadedSyncAdapter {
|
|
||||||
private final static Logger LOG = LoggerFactory.getLogger(SyncAdapter.class);
|
|
||||||
|
|
||||||
private static final long SYNC_FREQUENCY = 15 * 60; // seconds
|
|
||||||
|
|
||||||
private final BitmessageContext bmc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the sync adapter
|
|
||||||
*/
|
|
||||||
public SyncAdapter(Context context, boolean autoInitialize) {
|
|
||||||
super(context, autoInitialize);
|
|
||||||
bmc = Singleton.getBitmessageContext(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPerformSync(Account account, Bundle extras, String authority,
|
|
||||||
ContentProviderClient provider, SyncResult syncResult) {
|
|
||||||
try {
|
|
||||||
if (account.equals(ACCOUNT_SYNC)) {
|
|
||||||
if (Preferences.isConnectionAllowed(getContext())) {
|
|
||||||
syncData();
|
|
||||||
}
|
|
||||||
} else if (account.equals(ACCOUNT_POW)) {
|
|
||||||
syncPOW();
|
|
||||||
} else {
|
|
||||||
syncResult.stats.numAuthExceptions++;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
syncResult.stats.numIoExceptions++;
|
|
||||||
} catch (DecryptionFailedException e) {
|
|
||||||
syncResult.stats.numAuthExceptions++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void syncData() throws IOException {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
LOG.info("Synchronizing Bitmessage");
|
|
||||||
|
|
||||||
LOG.info("Synchronization started");
|
|
||||||
bmc.synchronize(
|
|
||||||
Preferences.getTrustedNode(getContext()),
|
|
||||||
Preferences.getTrustedNodePort(getContext()),
|
|
||||||
Preferences.getTimeoutInSeconds(getContext()),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
LOG.info("Synchronization finished");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void syncPOW() throws IOException, DecryptionFailedException {
|
|
||||||
// If the Bitmessage context acts as a full node, synchronization isn't necessary
|
|
||||||
LOG.info("Looking for completed POW");
|
|
||||||
|
|
||||||
BitmessageAddress identity = Singleton.getIdentity(getContext());
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
byte[] privateKey = identity.getPrivateKey().getPrivateEncryptionKey();
|
|
||||||
byte[] signingKey = cryptography().createPublicKey(identity.getPublicDecryptionKey());
|
|
||||||
ProofOfWorkRequest.Reader reader = new ProofOfWorkRequest.Reader(identity);
|
|
||||||
ProofOfWorkRepository powRepo = Singleton.getProofOfWorkRepository(getContext());
|
|
||||||
List<byte[]> items = powRepo.getItems();
|
|
||||||
for (byte[] initialHash : items) {
|
|
||||||
ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
|
|
||||||
byte[] target = cryptography().getProofOfWorkTarget(item.getObjectMessage(), item.getNonceTrialsPerByte(), item.getExtraBytes());
|
|
||||||
CryptoCustomMessage<ProofOfWorkRequest> cryptoMsg = new CryptoCustomMessage<>(
|
|
||||||
new ProofOfWorkRequest(identity, initialHash, CALCULATE, target));
|
|
||||||
cryptoMsg.signAndEncrypt(identity, signingKey);
|
|
||||||
CustomMessage response = bmc.send(
|
|
||||||
Preferences.getTrustedNode(getContext()),
|
|
||||||
Preferences.getTrustedNodePort(getContext()),
|
|
||||||
cryptoMsg
|
|
||||||
);
|
|
||||||
if (response.isError()) {
|
|
||||||
LOG.error("Server responded with error: " + new String(response.getData(),
|
|
||||||
"UTF-8"));
|
|
||||||
} else {
|
|
||||||
ProofOfWorkRequest decryptedResponse = CryptoCustomMessage.read(
|
|
||||||
response, reader).decrypt(privateKey);
|
|
||||||
if (decryptedResponse.getRequest() == COMPLETE) {
|
|
||||||
bmc.internals().getProofOfWorkService().onNonceCalculated(
|
|
||||||
initialHash, decryptedResponse.getData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (items.size() == 0) {
|
|
||||||
stopPowSync(getContext());
|
|
||||||
}
|
|
||||||
LOG.info("Synchronization finished");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void startSync(Context ctx) {
|
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
|
||||||
Account 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, new Bundle(), SYNC_FREQUENCY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void stopSync(Context ctx) {
|
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
|
||||||
Account account = addAccount(ctx, ACCOUNT_SYNC);
|
|
||||||
|
|
||||||
ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void startPowSync(Context ctx) {
|
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
|
||||||
Account 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, new Bundle(), SYNC_FREQUENCY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void stopPowSync(Context ctx) {
|
|
||||||
// Create account, if it's missing. (Either first run, or user has deleted account.)
|
|
||||||
Account account = addAccount(ctx, ACCOUNT_POW);
|
|
||||||
|
|
||||||
ContentResolver.removePeriodicSync(account, AUTHORITY, new Bundle());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Account addAccount(Context ctx, 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* 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.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) {
|
||||||
|
LOG.info("Trusted node not available, disabling synchronization")
|
||||||
|
stopSync(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
|
||||||
|
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
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,65 +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;
|
|
||||||
import android.os.IBinder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a Service that returns an IBinder for the
|
|
||||||
* sync adapter class, allowing the sync adapter framework to call
|
|
||||||
* onPerformSync().
|
|
||||||
*/
|
|
||||||
public class SyncService extends Service {
|
|
||||||
// Storage for an instance of the sync adapter
|
|
||||||
private static SyncAdapter syncAdapter = null;
|
|
||||||
// Object to use as a thread-safe lock
|
|
||||||
private static final Object syncAdapterLock = new Object();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiate the sync adapter object.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
/*
|
|
||||||
* Create the sync adapter as a singleton.
|
|
||||||
* Set the sync adapter as syncable
|
|
||||||
* Disallow parallel syncs
|
|
||||||
*/
|
|
||||||
synchronized (syncAdapterLock) {
|
|
||||||
if (syncAdapter == null) {
|
|
||||||
syncAdapter = new SyncAdapter(this, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an object that allows the system to invoke
|
|
||||||
* the sync adapter.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
/*
|
|
||||||
* Get the object that allows external processes
|
|
||||||
* to call onPerformSync(). The object is created
|
|
||||||
* in the base class code when the SyncAdapter
|
|
||||||
* constructors call super()
|
|
||||||
*/
|
|
||||||
return syncAdapter.getSyncAdapterBinder();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
import android.os.IBinder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
// Create the sync adapter as a singleton.
|
||||||
|
// Set the sync adapter as syncable
|
||||||
|
// Disallow parallel syncs
|
||||||
|
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): IBinder? {
|
||||||
|
// Get the object that allows external processes
|
||||||
|
// to call onPerformSync(). The object is created
|
||||||
|
// in the base class code when the SyncAdapter
|
||||||
|
// constructors call super()
|
||||||
|
return 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()
|
||||||
|
}
|
||||||
|
}
|
@ -1,114 +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.util;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
|
|
||||||
import ch.dissem.apps.abit.R;
|
|
||||||
import ch.dissem.apps.abit.listener.WifiReceiver;
|
|
||||||
import ch.dissem.apps.abit.notification.ErrorNotification;
|
|
||||||
|
|
||||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_FULL_NODE;
|
|
||||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_SYNC_TIMEOUT;
|
|
||||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_TRUSTED_NODE;
|
|
||||||
import static ch.dissem.apps.abit.util.Constants.PREFERENCE_WIFI_ONLY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Christian Basler
|
|
||||||
*/
|
|
||||||
public class Preferences {
|
|
||||||
public static boolean useTrustedNode(Context ctx) {
|
|
||||||
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
|
|
||||||
return trustedNode != null && !trustedNode.trim().isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Warning, this method might do a network call and therefore can't be called from
|
|
||||||
* the UI thread.
|
|
||||||
*/
|
|
||||||
public static InetAddress getTrustedNode(Context ctx) throws IOException {
|
|
||||||
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
|
|
||||||
if (trustedNode == null) return null;
|
|
||||||
trustedNode = trustedNode.trim();
|
|
||||||
if (trustedNode.isEmpty()) return null;
|
|
||||||
|
|
||||||
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
|
|
||||||
int index = trustedNode.lastIndexOf(':');
|
|
||||||
trustedNode = trustedNode.substring(0, index);
|
|
||||||
}
|
|
||||||
return InetAddress.getByName(trustedNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getTrustedNodePort(Context ctx) {
|
|
||||||
String trustedNode = getPreference(ctx, PREFERENCE_TRUSTED_NODE);
|
|
||||||
if (trustedNode == null) return 8444;
|
|
||||||
trustedNode = trustedNode.trim();
|
|
||||||
|
|
||||||
if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) {
|
|
||||||
int index = trustedNode.lastIndexOf(':');
|
|
||||||
String portString = trustedNode.substring(index + 1);
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(portString);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
new ErrorNotification(ctx)
|
|
||||||
.setError(R.string.error_invalid_sync_port, portString)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 8444;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long getTimeoutInSeconds(Context ctx) {
|
|
||||||
String preference = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT);
|
|
||||||
return preference == null ? 120 : Long.parseLong(preference);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getPreference(Context ctx, String name) {
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
|
||||||
|
|
||||||
return preferences.getString(name, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isConnectionAllowed(Context ctx) {
|
|
||||||
return !isWifiOnly(ctx) || !WifiReceiver.isConnectedToMeteredNetwork(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isWifiOnly(Context ctx) {
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
|
||||||
return preferences.getBoolean(PREFERENCE_WIFI_ONLY, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setWifiOnly(Context ctx, boolean status) {
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
|
||||||
preferences.edit().putBoolean(PREFERENCE_WIFI_ONLY, status).apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isFullNodeActive(Context ctx) {
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
|
||||||
return preferences.getBoolean(PREFERENCE_FULL_NODE, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setFullNodeActive(Context ctx, boolean status) {
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ctx);
|
|
||||||
preferences.edit().putBoolean(PREFERENCE_FULL_NODE, status).apply();
|
|
||||||
}
|
|
||||||
}
|
|
115
app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt
Normal file
115
app/src/main/java/ch/dissem/apps/abit/util/Preferences.kt
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* 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.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.preference.PreferenceManager
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.apps.abit.listener.WifiReceiver
|
||||||
|
import ch.dissem.apps.abit.notification.ErrorNotification
|
||||||
|
import ch.dissem.apps.abit.util.Constants.*
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Christian Basler
|
||||||
|
*/
|
||||||
|
object Preferences {
|
||||||
|
@JvmStatic
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun getTimeoutInSeconds(ctx: Context): Long {
|
||||||
|
val preference = getPreference(ctx, PREFERENCE_SYNC_TIMEOUT) ?: return 120
|
||||||
|
return preference.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPreference(ctx: Context, name: String): String? {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
|
||||||
|
return preferences.getString(name, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isConnectionAllowed(ctx: Context): Boolean {
|
||||||
|
return !isWifiOnly(ctx) || !WifiReceiver.isConnectedToMeteredNetwork(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isWifiOnly(ctx: Context): Boolean {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
return preferences.getBoolean(PREFERENCE_WIFI_ONLY, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setWifiOnly(ctx: Context, status: Boolean) {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
preferences.edit().putBoolean(PREFERENCE_WIFI_ONLY, status).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun isFullNodeActive(ctx: Context): Boolean {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
return preferences.getBoolean(PREFERENCE_FULL_NODE, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setFullNodeActive(ctx: Context, status: Boolean) {
|
||||||
|
val preferences = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
preferences.edit().putBoolean(PREFERENCE_FULL_NODE, status).apply()
|
||||||
|
}
|
||||||
|
}
|
@ -5,11 +5,13 @@ configurations.all {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.1.3-2'
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.3.3'
|
classpath 'com.android.tools.build:gradle:2.3.3'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
|
classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
Loading…
Reference in New Issue
Block a user