diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 577db59..f0fbceb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -102,7 +102,8 @@ - + + drawerItems = new ArrayList<>(); for (Label label : messageRepo.getLabels()) { @@ -414,11 +416,24 @@ public class MessageListActivity extends AppCompatActivity super.onStop(); } - private class IncomingHandler extends Handler { + private static class IncomingHandler extends Handler { + private WeakReference accountHeaderRef; + + private IncomingHandler() { + accountHeaderRef = new WeakReference<>(null); + } + + public void updateAccountHeader(AccountHeader accountHeader){ + accountHeaderRef = new WeakReference<>(accountHeader); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { case BitmessageService.MSG_CREATE_IDENTITY: { + AccountHeader accountHeader = accountHeaderRef.get(); + if (accountHeader == null) break; + Serializable data = msg.getData().getSerializable(DATA_FIELD_IDENTITY); if (data instanceof BitmessageAddress) { BitmessageAddress identity = (BitmessageAddress) data; diff --git a/app/src/main/java/ch/dissem/apps/abit/OpenBitmessageLinkActivity.java b/app/src/main/java/ch/dissem/apps/abit/OpenBitmessageLinkActivity.java index 8f87f8a..17ffe41 100644 --- a/app/src/main/java/ch/dissem/apps/abit/OpenBitmessageLinkActivity.java +++ b/app/src/main/java/ch/dissem/apps/abit/OpenBitmessageLinkActivity.java @@ -37,13 +37,13 @@ import android.widget.TextView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ch.dissem.apps.abit.synchronization.BitmessageService; +import ch.dissem.apps.abit.service.BitmessageService; import ch.dissem.bitmessage.entity.BitmessageAddress; -import static ch.dissem.apps.abit.synchronization.BitmessageService.DATA_FIELD_ADDRESS; -import static ch.dissem.apps.abit.synchronization.BitmessageService.MSG_ADD_CONTACT; -import static ch.dissem.apps.abit.synchronization.BitmessageService.MSG_SUBSCRIBE; -import static ch.dissem.apps.abit.synchronization.BitmessageService.MSG_SUBSCRIBE_AND_ADD_CONTACT; +import static ch.dissem.apps.abit.service.BitmessageService.DATA_FIELD_ADDRESS; +import static ch.dissem.apps.abit.service.BitmessageService.MSG_ADD_CONTACT; +import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE; +import static ch.dissem.apps.abit.service.BitmessageService.MSG_SUBSCRIBE_AND_ADD_CONTACT; public class OpenBitmessageLinkActivity extends AppCompatActivity { private static final Logger LOG = LoggerFactory.getLogger(OpenBitmessageLinkActivity.class); diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.java b/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.java index 60eb282..21ff505 100644 --- a/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.java +++ b/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.java @@ -14,7 +14,7 @@ public abstract class AbstractNotification { public AbstractNotification(Context ctx) { - this.ctx = ctx; + this.ctx = ctx.getApplicationContext(); this.manager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); } @@ -23,6 +23,10 @@ public abstract class AbstractNotification { */ protected abstract int getNotificationId(); + public Notification getNotification() { + return notification; + } + public void show() { manager.notify(getNotificationId(), notification); } diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.java b/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.java index d14aa78..c64185e 100644 --- a/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.java +++ b/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.java @@ -1,6 +1,7 @@ package ch.dissem.apps.abit.notification; import android.content.Context; +import android.support.annotation.StringRes; import android.support.v7.app.NotificationCompat; import ch.dissem.apps.abit.R; @@ -20,14 +21,14 @@ public class ErrorNotification extends AbstractNotification { .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); } - public ErrorNotification setWarning(int resId, Object... args) { + public ErrorNotification setWarning(@StringRes int resId, Object... args) { builder.setSmallIcon(R.drawable.ic_notification_warning) .setContentText(ctx.getString(resId, args)); notification = builder.build(); return this; } - public ErrorNotification setError(int resId, Object... args) { + public ErrorNotification setError(@StringRes int resId, Object... args) { builder.setSmallIcon(R.drawable.ic_notification_error) .setContentText(ctx.getString(resId, args)); notification = builder.build(); diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.java b/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.java index 3530c2a..bff6e92 100644 --- a/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.java +++ b/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.java @@ -12,7 +12,6 @@ import java.util.TimerTask; import ch.dissem.apps.abit.MessageListActivity; import ch.dissem.apps.abit.R; -import ch.dissem.apps.abit.service.Singleton; import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.utils.Property; @@ -26,7 +25,7 @@ public class NetworkNotification extends AbstractNotification { private NotificationCompat.Builder builder; public NetworkNotification(Context ctx, BitmessageContext bmc) { - super(ctx.getApplicationContext()); + super(ctx); this.bmc = bmc; builder = new NotificationCompat.Builder(ctx); builder.setSmallIcon(R.drawable.ic_notification_full_node) @@ -34,6 +33,7 @@ public class NetworkNotification extends AbstractNotification { .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); } + @Override public Notification getNotification() { update(); return notification; diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.java b/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.java new file mode 100644 index 0000000..f917a83 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.java @@ -0,0 +1,38 @@ +package ch.dissem.apps.abit.notification; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.support.v7.app.NotificationCompat; + +import ch.dissem.apps.abit.MessageListActivity; +import ch.dissem.apps.abit.R; + +/** + * Ongoing notification while proof of work is in progress. + */ +public class ProofOfWorkNotification extends AbstractNotification { + public static final int ONGOING_NOTIFICATION_ID = 3; + + public ProofOfWorkNotification(Context ctx) { + super(ctx); + NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); + + Intent showMessageIntent = new Intent(ctx, MessageListActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setUsesChronometer(true) + .setSmallIcon(R.drawable.ic_notification_proof_of_work) + .setContentTitle(ctx.getString(R.string.proof_of_work_title)) + .setContentText(ctx.getString(R.string.proof_of_work_text)) + .setContentIntent(pendingIntent); + + notification = builder.build(); + } + + @Override + protected int getNotificationId() { + return ONGOING_NOTIFICATION_ID; + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/synchronization/BitmessageService.java b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java similarity index 67% rename from app/src/main/java/ch/dissem/apps/abit/synchronization/BitmessageService.java rename to app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java index bb15042..e52eb95 100644 --- a/app/src/main/java/ch/dissem/apps/abit/synchronization/BitmessageService.java +++ b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.java @@ -1,25 +1,21 @@ -package ch.dissem.apps.abit.synchronization; +package ch.dissem.apps.abit.service; import android.app.Service; import android.content.Intent; -import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; -import android.preference.PreferenceManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; -import java.net.InetAddress; -import java.net.UnknownHostException; +import java.lang.ref.WeakReference; import ch.dissem.apps.abit.notification.NetworkNotification; -import ch.dissem.apps.abit.service.Singleton; import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.entity.BitmessageAddress; @@ -33,7 +29,6 @@ import static ch.dissem.apps.abit.notification.NetworkNotification.ONGOING_NOTIF public class BitmessageService extends Service { public static final Logger LOG = LoggerFactory.getLogger(BitmessageService.class); - public static final int MSG_SYNC = 2; public static final int MSG_CREATE_IDENTITY = 10; public static final int MSG_SUBSCRIBE = 20; public static final int MSG_ADD_CONTACT = 21; @@ -68,7 +63,7 @@ public class BitmessageService extends Service { if (bmc == null) { bmc = Singleton.getBitmessageContext(this); notification = new NetworkNotification(this, bmc); - messenger = new Messenger(new IncomingHandler()); + messenger = new Messenger(new IncomingHandler(this)); } } } @@ -93,7 +88,13 @@ public class BitmessageService extends Service { return messenger.getBinder(); } - private class IncomingHandler extends Handler { + private static class IncomingHandler extends Handler { + private WeakReference service; + + private IncomingHandler(BitmessageService service) { + this.service = new WeakReference<>(service); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -119,45 +120,6 @@ public class BitmessageService extends Service { } break; } - case MSG_SYNC: { - LOG.info("Synchronizing Bitmessage"); - // If the Bitmessage context acts as a full node, synchronization isn't necessary - if (bmc.isRunning()) break; - - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( - BitmessageService.this); - - String trustedNode = preferences.getString("trusted_node", null); - if (trustedNode == null) break; - trustedNode = trustedNode.trim(); - if (trustedNode.isEmpty()) break; - - int port; - if (trustedNode.matches("^(?![0-9a-fA-F]*:[0-9a-fA-F]*:).*(:[0-9]+)$")) { - int index = trustedNode.lastIndexOf(':'); - String portString = trustedNode.substring(index + 1); - trustedNode = trustedNode.substring(0, index); - try { - port = Integer.parseInt(portString); - } catch (NumberFormatException e) { - LOG.error("Invalid port " + portString); - // TODO: show error as notification - return; - } - } else { - port = 8444; - } - long timeoutInSeconds = preferences.getInt("sync_timeout", 120); - try { - LOG.info("Synchronization started"); - bmc.synchronize(InetAddress.getByName(trustedNode), port, timeoutInSeconds, true); - LOG.info("Synchronization finished"); - } catch (UnknownHostException e) { - LOG.error("Couldn't synchronize", e); - // TODO: show error as notification - } - break; - } case MSG_SEND_MESSAGE: { Serializable identity = msg.getData().getSerializable(DATA_FIELD_IDENTITY); Serializable address = msg.getData().getSerializable(DATA_FIELD_ADDRESS); @@ -182,17 +144,17 @@ public class BitmessageService extends Service { case MSG_START_NODE: // TODO: warn user, option to restrict to WiFi // (I'm not quite sure this can be done here, though) - startService(new Intent(BitmessageService.this, BitmessageService.class)); + service.get().startService(new Intent(service.get(), BitmessageService.class)); running = true; - startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification()); + service.get().startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification()); bmc.startup(); notification.show(); break; case MSG_STOP_NODE: bmc.shutdown(); running = false; - stopForeground(false); - stopService(new Intent(BitmessageService.this, BitmessageService.class)); + service.get().stopForeground(false); + service.get().stopSelf(); break; default: super.handleMessage(msg); diff --git a/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.java b/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.java new file mode 100644 index 0000000..7d86ef8 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.java @@ -0,0 +1,88 @@ +package ch.dissem.apps.abit.service; + +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.support.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.ref.WeakReference; + +import ch.dissem.apps.abit.notification.ProofOfWorkNotification; +import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine; +import ch.dissem.bitmessage.ports.ProofOfWorkEngine; + +import static ch.dissem.apps.abit.notification.ProofOfWorkNotification.ONGOING_NOTIFICATION_ID; + +/** + * The Proof of Work Service makes sure POW is done in a foreground process, so it shouldn't be + * killed by the system before the nonce is found. + */ +public class ProofOfWorkService extends Service { + public static final Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class); + + // Object to use as a thread-safe lock + private static final Object lock = new Object(); + private static ProofOfWorkEngine engine; + + @Override + public void onCreate() { + synchronized (lock) { + if (engine == null) { + engine = new MultiThreadedPOWEngine(); + } + } + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return new PowBinder(engine, this); + } + + public static class PowBinder extends Binder { + private final ProofOfWorkEngine engine; + + private PowBinder(ProofOfWorkEngine engine, ProofOfWorkService service) { + this.engine = new EngineWrapper(engine, service); + } + + public ProofOfWorkEngine getEngine() { + return engine; + } + } + + private static class EngineWrapper implements ProofOfWorkEngine { + private final ProofOfWorkNotification notification; + private final ProofOfWorkEngine engine; + private final WeakReference serviceRef; + + private EngineWrapper(ProofOfWorkEngine engine, ProofOfWorkService service) { + this.engine = engine; + this.serviceRef = new WeakReference<>(service); + this.notification = new ProofOfWorkNotification(service); + } + + @Override + public void calculateNonce(byte[] initialHash, byte[] target, final Callback callback) { + final ProofOfWorkService service = serviceRef.get(); + service.startService(new Intent(service, ProofOfWorkService.class)); + service.startForeground(ONGOING_NOTIFICATION_ID, notification.getNotification()); + engine.calculateNonce(initialHash, target, new ProofOfWorkEngine.Callback() { + @Override + public void onNonceCalculated(byte[] nonce) { + try { + callback.onNonceCalculated(nonce); + } finally { + service.stopForeground(true); + service.stopSelf(); + } + } + }); + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ch/dissem/apps/abit/service/ServicePowEngine.java b/app/src/main/java/ch/dissem/apps/abit/service/ServicePowEngine.java new file mode 100644 index 0000000..16d2aaa --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/service/ServicePowEngine.java @@ -0,0 +1,61 @@ +package ch.dissem.apps.abit.service; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; + +import java.util.concurrent.Semaphore; + +import ch.dissem.apps.abit.service.ProofOfWorkService.PowBinder; +import ch.dissem.bitmessage.ports.ProofOfWorkEngine; + +import static android.content.Context.BIND_AUTO_CREATE; + +/** + * Proof of Work engine that uses the Proof of Work service. + */ +public class ServicePowEngine implements ProofOfWorkEngine, ProofOfWorkEngine.Callback { + private final Semaphore semaphore = new Semaphore(1, true); + private final Context ctx; + + private byte[] initialHash, targetValue; + private Callback callback; + + public ServicePowEngine(Context ctx) { + this.ctx = ctx; + } + + private ServiceConnection connection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + ((PowBinder) service).getEngine().calculateNonce(initialHash, targetValue, ServicePowEngine.this); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + semaphore.release(); + } + }; + + @Override + public void calculateNonce(byte[] initialHash, byte[] targetValue, Callback callback) { + try { + semaphore.acquire(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + this.initialHash = initialHash; + this.targetValue = targetValue; + this.callback = callback; + ctx.bindService(new Intent(ctx, ProofOfWorkService.class), connection, BIND_AUTO_CREATE); + } + + @Override + public void onNonceCalculated(byte[] bytes) { + callback.onNonceCalculated(bytes); + ctx.unbindService(connection); + } + +} diff --git a/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java b/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java index f00d3c7..a178519 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java +++ b/app/src/main/java/ch/dissem/apps/abit/service/Singleton.java @@ -2,9 +2,6 @@ package ch.dissem.apps.abit.service; import android.content.Context; -import java.util.Objects; - -import ch.dissem.apps.abit.MessageListActivity; import ch.dissem.apps.abit.listener.MessageListener; import ch.dissem.apps.abit.repository.AndroidAddressRepository; import ch.dissem.apps.abit.repository.AndroidInventory; @@ -15,7 +12,6 @@ import ch.dissem.bitmessage.networking.DefaultNetworkHandler; import ch.dissem.bitmessage.ports.AddressRepository; import ch.dissem.bitmessage.ports.MemoryNodeRegistry; import ch.dissem.bitmessage.ports.MessageRepository; -import ch.dissem.bitmessage.ports.Security; import ch.dissem.bitmessage.security.sc.SpongySecurity; /** @@ -33,6 +29,7 @@ public class Singleton { final Context ctx = context.getApplicationContext(); SqlHelper sqlHelper = new SqlHelper(ctx); bitmessageContext = new BitmessageContext.Builder() + .proofOfWorkEngine(new ServicePowEngine(ctx)) .security(new SpongySecurity()) .nodeRegistry(new MemoryNodeRegistry()) .inventory(new AndroidInventory(sqlHelper)) diff --git a/app/src/main/java/ch/dissem/apps/abit/synchronization/SyncAdapter.java b/app/src/main/java/ch/dissem/apps/abit/synchronization/SyncAdapter.java index 4bbc627..7654833 100644 --- a/app/src/main/java/ch/dissem/apps/abit/synchronization/SyncAdapter.java +++ b/app/src/main/java/ch/dissem/apps/abit/synchronization/SyncAdapter.java @@ -15,6 +15,8 @@ import org.slf4j.LoggerFactory; import java.net.InetAddress; import java.net.UnknownHostException; +import ch.dissem.apps.abit.R; +import ch.dissem.apps.abit.notification.ErrorNotification; import ch.dissem.apps.abit.service.Singleton; import ch.dissem.bitmessage.BitmessageContext; @@ -59,8 +61,9 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter { try { port = Integer.parseInt(portString); } catch (NumberFormatException e) { - LOG.error("Invalid port " + portString); - // TODO: show error as notification + new ErrorNotification(getContext()) + .setError(R.string.error_invalid_sync_port, portString) + .show(); return; } } else { @@ -72,8 +75,11 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter { bmc.synchronize(InetAddress.getByName(trustedNode), port, timeoutInSeconds, true); LOG.info("Synchronization finished"); } catch (UnknownHostException e) { - LOG.error("Couldn't synchronize", e); - // TODO: show error as notification + new ErrorNotification(getContext()) + .setError(R.string.error_invalid_sync_host) + .show(); + } catch (RuntimeException e) { + LOG.error(e.getMessage(), e); } } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 383adde..7020bf7 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -42,4 +42,8 @@ Stream %1$d: %2$d Verbindungen Getrennt Verbindung wird aufgebaut… + Warnung: dies könnte das Gerät erwärmen bis die Batterie leer ist. + Proof of Work + Synchronisation fehlgeschlagen: der vertrauenswürdige Knoten konnte nicht erreicht werden. + Ungültiger Port in den Synchronisationseinstellungen: %s \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b493dc2..ed1cd40 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,4 +42,8 @@ Send Disconnected Connecting… + Proof of Work + Warning: This might heat your device until the battery\'s dead. + Invalid port in synchronization settings: %s + Synchronization failed: Trusted node could not be reached.