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 deleted file mode 100644 index 7573dc9..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.java +++ /dev/null @@ -1,53 +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.notification; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; - -/** - * Some base class to create and handle notifications. - */ -public abstract class AbstractNotification { - protected final Context ctx; - protected final NotificationManager manager; - protected Notification notification; - - - public AbstractNotification(Context ctx) { - this.ctx = ctx.getApplicationContext(); - this.manager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); - } - - /** - * @return an id unique to this notification class - */ - protected abstract int getNotificationId(); - - public Notification getNotification() { - return notification; - } - - public void show() { - manager.notify(getNotificationId(), notification); - } - - public void hide() { - manager.cancel(getNotificationId()); - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.kt new file mode 100644 index 0000000..2da0e98 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/notification/AbstractNotification.kt @@ -0,0 +1,48 @@ +/* + * 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.notification + +import android.app.Notification +import android.app.NotificationManager +import android.content.Context + +/** + * Some base class to create and handle notifications. + */ +abstract class AbstractNotification(ctx: Context) { + protected val ctx = ctx.applicationContext + protected val manager = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + var notification: Notification? = null + protected set + protected var showing = false + private set + + /** + * @return an id unique to this notification class + */ + protected abstract val notificationId: Int + + open fun show() { + manager.notify(notificationId, notification) + showing = true + } + + fun hide() { + showing = false + manager.cancel(notificationId) + } +} 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 deleted file mode 100644 index e7a0654..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.java +++ /dev/null @@ -1,61 +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.notification; - -import android.content.Context; -import android.support.annotation.StringRes; -import android.support.v7.app.NotificationCompat; - -import ch.dissem.apps.abit.R; - -/** - * Easily create notifications with error messages. Use carefully, users probably won't like them. - * (But they are useful during development/testing) - * - * @author Christian Basler - */ -public class ErrorNotification extends AbstractNotification { - public static final int ERROR_NOTIFICATION_ID = 4; - - private final NotificationCompat.Builder builder; - - public ErrorNotification(Context ctx) { - super(ctx); - builder = new NotificationCompat.Builder(ctx); - builder.setContentTitle(ctx.getString(R.string.app_name)) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); - } - - 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(@StringRes int resId, Object... args) { - builder.setSmallIcon(R.drawable.ic_notification_error) - .setContentText(ctx.getString(resId, args)); - notification = builder.build(); - return this; - } - - @Override - protected int getNotificationId() { - return ERROR_NOTIFICATION_ID; - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.kt new file mode 100644 index 0000000..a652cfe --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/notification/ErrorNotification.kt @@ -0,0 +1,56 @@ +/* + * 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.notification + +import android.content.Context +import android.support.annotation.StringRes +import android.support.v7.app.NotificationCompat + +import ch.dissem.apps.abit.R + +/** + * Easily create notifications with error messages. Use carefully, users probably won't like them. + * (But they are useful during development/testing) + + * @author Christian Basler + */ +class ErrorNotification(ctx: Context) : AbstractNotification(ctx) { + + private val builder = NotificationCompat.Builder(ctx) + .setContentTitle(ctx.getString(R.string.app_name)) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + + fun setWarning(@StringRes resId: Int, vararg args: Any): ErrorNotification { + builder.setSmallIcon(R.drawable.ic_notification_warning) + .setContentText(ctx.getString(resId, *args)) + notification = builder.build() + return this + } + + fun setError(@StringRes resId: Int, vararg args: Any): ErrorNotification { + builder.setSmallIcon(R.drawable.ic_notification_error) + .setContentText(ctx.getString(resId, *args)) + notification = builder.build() + return this + } + + override val notificationId = ERROR_NOTIFICATION_ID + + companion object { + val ERROR_NOTIFICATION_ID = 4 + } +} 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 deleted file mode 100644 index 2bce79b..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.java +++ /dev/null @@ -1,141 +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.notification; - -import android.annotation.SuppressLint; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.support.v7.app.NotificationCompat; - -import java.util.Timer; -import java.util.TimerTask; - -import ch.dissem.apps.abit.MainActivity; -import ch.dissem.apps.abit.R; -import ch.dissem.apps.abit.service.BitmessageIntentService; -import ch.dissem.apps.abit.service.BitmessageService; -import ch.dissem.bitmessage.utils.Property; - -import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; - -/** - * Shows the network status (as long as the client is connected as a full node) - */ -public class NetworkNotification extends AbstractNotification { - public static final int NETWORK_NOTIFICATION_ID = 2; - - private final NotificationCompat.Builder builder; - private Timer timer; - - public NetworkNotification(Context ctx) { - super(ctx); - Intent showAppIntent = new Intent(ctx, MainActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, showAppIntent, 0); - builder = new NotificationCompat.Builder(ctx); - builder.setSmallIcon(R.drawable.ic_notification_full_node) - .setContentTitle(ctx.getString(R.string.bitmessage_full_node)) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setShowWhen(false) - .setContentIntent(pendingIntent); - } - - @SuppressLint("StringFormatMatches") - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean update() { - boolean running = BitmessageService.isRunning(); - builder.setOngoing(running); - Property connections = BitmessageService.getStatus().getProperty("network", "connections"); - if (!running) { - builder.setContentText(ctx.getString(R.string.connection_info_disconnected)); - } else if (connections.getProperties().length == 0) { - builder.setContentText(ctx.getString(R.string.connection_info_pending)); - } else { - StringBuilder info = new StringBuilder(); - for (Property stream : connections.getProperties()) { - int streamNumber = Integer.parseInt(stream.getName().substring("stream ".length())); - Integer nodeCount = (Integer) stream.getProperty("nodes").getValue(); - if (nodeCount == 1) { - info.append(ctx.getString(R.string.connection_info_1, - streamNumber)); - } else { - info.append(ctx.getString(R.string.connection_info_n, - streamNumber, nodeCount)); - } - info.append('\n'); - } - builder.setContentText(info); - } - builder.mActions.clear(); - Intent intent = new Intent(ctx, BitmessageIntentService.class); - if (running) { - intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true); - builder.addAction(R.drawable.ic_notification_node_stop, - ctx.getString(R.string.full_node_stop), - PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)); - } else { - intent.putExtra(BitmessageIntentService.EXTRA_STARTUP_NODE, true); - builder.addAction(R.drawable.ic_notification_node_start, - ctx.getString(R.string.full_node_restart), - PendingIntent.getService(ctx, 1, intent, FLAG_UPDATE_CURRENT)); - } - notification = builder.build(); - return running; - } - - @Override - public void show() { - super.show(); - - timer = new Timer(); - timer.schedule(new TimerTask() { - @Override - public void run() { - if (!update()) { - cancel(); - ctx.stopService(new Intent(ctx, BitmessageService.class)); - } - NetworkNotification.super.show(); - } - }, 10_000, 10_000); - } - - public void showShutdown() { - if (timer != null) { - timer.cancel(); - } - update(); - super.show(); - } - - @Override - protected int getNotificationId() { - return NETWORK_NOTIFICATION_ID; - } - - public void connecting() { - builder.setOngoing(true); - builder.setContentText(ctx.getString(R.string.connection_info_pending)); - Intent intent = new Intent(ctx, BitmessageIntentService.class); - intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true); - builder.mActions.clear(); - builder.addAction(R.drawable.ic_notification_node_stop, - ctx.getString(R.string.full_node_stop), - PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)); - notification = builder.build(); - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.kt new file mode 100644 index 0000000..0e268a7 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/notification/NetworkNotification.kt @@ -0,0 +1,128 @@ +/* + * 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.notification + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_UPDATE_CURRENT +import android.content.Context +import android.content.Intent +import android.support.v7.app.NotificationCompat +import ch.dissem.apps.abit.MainActivity +import ch.dissem.apps.abit.R +import ch.dissem.apps.abit.service.BitmessageIntentService +import ch.dissem.apps.abit.service.BitmessageService +import java.util.* +import kotlin.concurrent.fixedRateTimer + +/** + * Shows the network status (as long as the client is connected as a full node) + */ +class NetworkNotification(ctx: Context) : AbstractNotification(ctx) { + + private val builder = NotificationCompat.Builder(ctx) + private var timer: Timer? = null + + init { + val showAppIntent = Intent(ctx, MainActivity::class.java) + val pendingIntent = PendingIntent.getActivity(ctx, 1, showAppIntent, 0) + builder + .setSmallIcon(R.drawable.ic_notification_full_node) + .setContentTitle(ctx.getString(R.string.bitmessage_full_node)) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setShowWhen(false) + .setContentIntent(pendingIntent) + } + + @SuppressLint("StringFormatMatches") + private fun update(): Boolean { + val running = BitmessageService.isRunning + builder.setOngoing(running) + val connections = BitmessageService.status.getProperty("network", "connections") + if (!running) { + builder.setContentText(ctx.getString(R.string.connection_info_disconnected)) + } else if (connections!!.properties.isEmpty()) { + builder.setContentText(ctx.getString(R.string.connection_info_pending)) + } else { + val info = StringBuilder() + for (stream in connections.properties) { + val streamNumber = Integer.parseInt(stream.name.substring("stream ".length)) + val nodeCount = stream.getProperty("nodes")!!.value as Int? + if (nodeCount == 1) { + info.append(ctx.getString(R.string.connection_info_1, + streamNumber)) + } else { + info.append(ctx.getString(R.string.connection_info_n, + streamNumber, nodeCount)) + } + info.append('\n') + } + builder.setContentText(info) + } + builder.mActions.clear() + val intent = Intent(ctx, BitmessageIntentService::class.java) + if (running) { + intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true) + builder.addAction(R.drawable.ic_notification_node_stop, + ctx.getString(R.string.full_node_stop), + PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)) + } else { + intent.putExtra(BitmessageIntentService.EXTRA_STARTUP_NODE, true) + builder.addAction(R.drawable.ic_notification_node_start, + ctx.getString(R.string.full_node_restart), + PendingIntent.getService(ctx, 1, intent, FLAG_UPDATE_CURRENT)) + } + notification = builder.build() + return running + } + + override fun show() { + super.show() + + timer = fixedRateTimer(initialDelay = 10000, period = 10000) { + if (!update()) { + cancel() + ctx.stopService(Intent(ctx, BitmessageService::class.java)) + } + super@NetworkNotification.show() + } + } + + fun showShutdown() { + timer?.cancel() + update() + super.show() + } + + override val notificationId = NETWORK_NOTIFICATION_ID + + fun connecting() { + builder.setOngoing(true) + builder.setContentText(ctx.getString(R.string.connection_info_pending)) + val intent = Intent(ctx, BitmessageIntentService::class.java) + intent.putExtra(BitmessageIntentService.EXTRA_SHUTDOWN_NODE, true) + builder.mActions.clear() + builder.addAction(R.drawable.ic_notification_node_stop, + ctx.getString(R.string.full_node_stop), + PendingIntent.getService(ctx, 0, intent, FLAG_UPDATE_CURRENT)) + notification = builder.build() + } + + companion object { + val NETWORK_NOTIFICATION_ID = 2 + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.java b/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.java deleted file mode 100644 index 3bddc2e..0000000 --- a/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.java +++ /dev/null @@ -1,122 +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.notification; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.graphics.Typeface; -import android.support.v7.app.NotificationCompat; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.style.StyleSpan; - -import java.util.Collection; - -import ch.dissem.apps.abit.Identicon; -import ch.dissem.apps.abit.MainActivity; -import ch.dissem.apps.abit.R; -import ch.dissem.apps.abit.service.BitmessageIntentService; -import ch.dissem.bitmessage.entity.Plaintext; - -import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; -import static ch.dissem.apps.abit.MainActivity.EXTRA_REPLY_TO_MESSAGE; -import static ch.dissem.apps.abit.MainActivity.EXTRA_SHOW_MESSAGE; -import static ch.dissem.apps.abit.service.BitmessageIntentService.EXTRA_DELETE_MESSAGE; -import static ch.dissem.apps.abit.util.Drawables.toBitmap; - -public class NewMessageNotification extends AbstractNotification { - private static final int NEW_MESSAGE_NOTIFICATION_ID = 1; - private static final StyleSpan SPAN_EMPHASIS = new StyleSpan(Typeface.BOLD); - - public NewMessageNotification(Context ctx) { - super(ctx); - } - - public NewMessageNotification singleNotification(Plaintext plaintext) { - NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); - Spannable bigText = new SpannableString(plaintext.getSubject() + "\n" + plaintext.getText - ()); - bigText.setSpan(SPAN_EMPHASIS, 0, plaintext.getSubject().length(), Spanned - .SPAN_INCLUSIVE_EXCLUSIVE); - builder.setSmallIcon(R.drawable.ic_notification_new_message) - .setLargeIcon(toBitmap(new Identicon(plaintext.getFrom()), 192)) - .setContentTitle(plaintext.getFrom().toString()) - .setContentText(plaintext.getSubject()) - .setStyle(new NotificationCompat.BigTextStyle().bigText(bigText)) - .setContentInfo("Info"); - - builder.setContentIntent( - createActivityIntent(EXTRA_SHOW_MESSAGE, plaintext)); - builder.addAction(R.drawable.ic_action_reply, ctx.getString(R.string.reply), - createActivityIntent(EXTRA_REPLY_TO_MESSAGE, plaintext)); - builder.addAction(R.drawable.ic_action_delete, ctx.getString(R.string.delete), - createServiceIntent(ctx, EXTRA_DELETE_MESSAGE, plaintext)); - notification = builder.build(); - return this; - } - - private PendingIntent createActivityIntent(String action, Plaintext message) { - Intent intent = new Intent(ctx, MainActivity.class); - intent.putExtra(action, message); - return PendingIntent.getActivity(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT); - } - - private PendingIntent createServiceIntent(Context ctx, String action, Plaintext message) { - Intent intent = new Intent(ctx, BitmessageIntentService.class); - intent.putExtra(action, message); - return PendingIntent.getService(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT); - } - - /** - * @param unacknowledged will be accessed from different threads, so make sure wherever it's - * accessed it will be in a synchronized(unacknowledged) - * {} block - */ - public NewMessageNotification multiNotification(Collection unacknowledged, int - numberOfUnacknowledgedMessages) { - NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); - builder.setSmallIcon(R.drawable.ic_notification_new_message) - .setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages)) - .setContentText(ctx.getString(R.string.app_name)); - - NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); - //noinspection SynchronizationOnLocalVariableOrMethodParameter - synchronized (unacknowledged) { - for (Plaintext msg : unacknowledged) { - Spannable sb = new SpannableString(msg.getFrom() + " " + msg.getSubject()); - sb.setSpan(SPAN_EMPHASIS, 0, String.valueOf(msg.getFrom()).length(), Spannable - .SPAN_INCLUSIVE_EXCLUSIVE); - inboxStyle.addLine(sb); - } - } - builder.setStyle(inboxStyle); - - Intent intent = new Intent(ctx, MainActivity.class); - intent.setAction(MainActivity.ACTION_SHOW_INBOX); - PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 1, intent, 0); - builder.setContentIntent(pendingIntent); - notification = builder.build(); - return this; - } - - @Override - protected int getNotificationId() { - return NEW_MESSAGE_NOTIFICATION_ID; - } -} diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt new file mode 100644 index 0000000..c603684 --- /dev/null +++ b/app/src/main/java/ch/dissem/apps/abit/notification/NewMessageNotification.kt @@ -0,0 +1,116 @@ +/* + * 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.notification + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.Typeface +import android.support.v7.app.NotificationCompat +import android.support.v4.app.NotificationCompat.BigTextStyle +import android.support.v4.app.NotificationCompat.InboxStyle +import android.text.Spannable +import android.text.SpannableString +import android.text.Spanned +import android.text.style.StyleSpan + +import ch.dissem.apps.abit.Identicon +import ch.dissem.apps.abit.MainActivity +import ch.dissem.apps.abit.R +import ch.dissem.apps.abit.service.BitmessageIntentService +import ch.dissem.bitmessage.entity.Plaintext + +import android.app.PendingIntent.FLAG_UPDATE_CURRENT +import ch.dissem.apps.abit.MainActivity.EXTRA_REPLY_TO_MESSAGE +import ch.dissem.apps.abit.MainActivity.EXTRA_SHOW_MESSAGE +import ch.dissem.apps.abit.service.BitmessageIntentService.EXTRA_DELETE_MESSAGE +import ch.dissem.apps.abit.util.Drawables.toBitmap + +class NewMessageNotification(ctx: Context) : AbstractNotification(ctx) { + + fun singleNotification(plaintext: Plaintext): NewMessageNotification { + val builder = NotificationCompat.Builder(ctx) + val bigText = SpannableString(plaintext.subject + "\n" + plaintext.text) + bigText.setSpan(SPAN_EMPHASIS, 0, plaintext.subject!!.length, Spanned + .SPAN_INCLUSIVE_EXCLUSIVE) + builder.setSmallIcon(R.drawable.ic_notification_new_message) + .setLargeIcon(toBitmap(Identicon(plaintext.from), 192)) + .setContentTitle(plaintext.from.toString()) + .setContentText(plaintext.subject) + .setStyle(BigTextStyle().bigText(bigText)) + .setContentInfo("Info") + + builder.setContentIntent( + createActivityIntent(EXTRA_SHOW_MESSAGE, plaintext)) + builder.addAction(R.drawable.ic_action_reply, ctx.getString(R.string.reply), + createActivityIntent(EXTRA_REPLY_TO_MESSAGE, plaintext)) + builder.addAction(R.drawable.ic_action_delete, ctx.getString(R.string.delete), + createServiceIntent(ctx, EXTRA_DELETE_MESSAGE, plaintext)) + notification = builder.build() + return this + } + + private fun createActivityIntent(action: String, message: Plaintext): PendingIntent { + val intent = Intent(ctx, MainActivity::class.java) + intent.putExtra(action, message) + return PendingIntent.getActivity(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT) + } + + private fun createServiceIntent(ctx: Context, action: String, message: Plaintext): PendingIntent { + val intent = Intent(ctx, BitmessageIntentService::class.java) + intent.putExtra(action, message) + return PendingIntent.getService(ctx, action.hashCode(), intent, FLAG_UPDATE_CURRENT) + } + + /** + * @param unacknowledged will be accessed from different threads, so make sure wherever it's + * * accessed it will be in a `synchronized(unacknowledged) + * * {}` block + */ + fun multiNotification(unacknowledged: Collection<Plaintext>, numberOfUnacknowledgedMessages: Int): NewMessageNotification { + val builder = NotificationCompat.Builder(ctx) + builder.setSmallIcon(R.drawable.ic_notification_new_message) + .setContentTitle(ctx.getString(R.string.n_new_messages, numberOfUnacknowledgedMessages)) + .setContentText(ctx.getString(R.string.app_name)) + + val inboxStyle = InboxStyle() + + synchronized(unacknowledged) { + for (msg in unacknowledged) { + val sb = SpannableString(msg.from.toString() + " " + msg.subject) + sb.setSpan(SPAN_EMPHASIS, 0, msg.from.toString().length, Spannable + .SPAN_INCLUSIVE_EXCLUSIVE) + inboxStyle.addLine(sb) + } + } + builder.setStyle(inboxStyle) + + val intent = Intent(ctx, MainActivity::class.java) + intent.action = MainActivity.ACTION_SHOW_INBOX + val pendingIntent = PendingIntent.getActivity(ctx, 1, intent, 0) + builder.setContentIntent(pendingIntent) + notification = builder.build() + return this + } + + override val notificationId = NEW_MESSAGE_NOTIFICATION_ID + + companion object { + private val NEW_MESSAGE_NOTIFICATION_ID = 1 + private val SPAN_EMPHASIS = StyleSpan(Typeface.BOLD) + } +} diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.kt index 5076097..e2190ec 100644 --- a/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.kt +++ b/app/src/main/java/ch/dissem/apps/abit/notification/ProofOfWorkNotification.kt @@ -23,36 +23,44 @@ import android.support.v7.app.NotificationCompat import ch.dissem.apps.abit.MainActivity import ch.dissem.apps.abit.R +import ch.dissem.apps.abit.service.ProofOfWorkService +import ch.dissem.apps.abit.util.PowStats +import java.util.* +import kotlin.concurrent.fixedRateTimer /** * Ongoing notification while proof of work is in progress. */ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) { + private val builder = NotificationCompat.Builder(ctx) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setUsesChronometer(true) + .setOngoing(true) + .setSmallIcon(R.drawable.ic_notification_proof_of_work) + .setContentTitle(ctx.getString(R.string.proof_of_work_title)) + private var startTime = 0L + private var progress = 0 + private var progressMax = 0 + + private var timer: Timer? = null + init { update(0) } - override fun getNotificationId(): Int { - return ONGOING_NOTIFICATION_ID - } + override val notificationId = ONGOING_NOTIFICATION_ID fun update(numberOfItems: Int): ProofOfWorkNotification { - val builder = NotificationCompat.Builder(ctx) val showMessageIntent = Intent(ctx, MainActivity::class.java) val pendingIntent = PendingIntent.getActivity(ctx, 0, showMessageIntent, PendingIntent.FLAG_UPDATE_CURRENT) - builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setUsesChronometer(true) - .setOngoing(true) - .setSmallIcon(R.drawable.ic_notification_proof_of_work) - .setContentTitle(ctx.getString(R.string.proof_of_work_title)) - .setContentText(if (numberOfItems == 0) - ctx.getString(R.string.proof_of_work_text_0) - else - ctx.getString(R.string.proof_of_work_text_n, numberOfItems)) + builder.setContentText(if (numberOfItems == 0) + ctx.getString(R.string.proof_of_work_text_0) + else + ctx.getString(R.string.proof_of_work_text_n, numberOfItems)) .setContentIntent(pendingIntent) notification = builder.build() @@ -62,4 +70,35 @@ class ProofOfWorkNotification(ctx: Context) : AbstractNotification(ctx) { companion object { @JvmField val ONGOING_NOTIFICATION_ID = 3 } + + fun start(item: ProofOfWorkService.PowItem) { + val expectedPowTimeInMilliseconds = PowStats.getExpectedPowTimeInMilliseconds(ctx, item.targetValue) + val delta = (expectedPowTimeInMilliseconds / 2).toInt() + startTime = System.currentTimeMillis() + progress = 0 + progressMax = delta + builder.setProgress(progressMax, progress, false) + notification = builder.build() + show() + + timer = fixedRateTimer(initialDelay = 5000, period = 5000){ + val elapsedTime = System.currentTimeMillis() - startTime + progress = elapsedTime.toInt() + progressMax = progress + delta + builder.setProgress(progressMax, progress, false) + notification = builder.build() + show() + } + } + + fun finished(item: ProofOfWorkService.PowItem) { + timer?.cancel() + progress = 0 + progressMax = 0 + if (showing) { + builder.setProgress(0, 0, false) + notification = builder.build() + show() + } + } } diff --git a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt index 5bd0339..3058152 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt +++ b/app/src/main/java/ch/dissem/apps/abit/service/BitmessageService.kt @@ -20,7 +20,7 @@ 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.apps.abit.notification.NetworkNotification.Companion.NETWORK_NOTIFICATION_ID import ch.dissem.bitmessage.BitmessageContext import ch.dissem.bitmessage.utils.Property diff --git a/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.kt b/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.kt index 0f3f6c5..443e59c 100644 --- a/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.kt +++ b/app/src/main/java/ch/dissem/apps/abit/service/ProofOfWorkService.kt @@ -69,9 +69,11 @@ class ProofOfWorkService : Service() { data class PowItem(val initialHash: ByteArray, val targetValue: ByteArray, val callback: ProofOfWorkEngine.Callback) private fun calculateNonce(item: PowItem) { + notification.start(item) val startTime = System.currentTimeMillis() engine.calculateNonce(item.initialHash, item.targetValue, object : ProofOfWorkEngine.Callback { override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + notification.finished(item) val time = System.currentTimeMillis() - startTime PowStats.addPow(this@ProofOfWorkService, time, item.targetValue) try { diff --git a/app/src/main/java/ch/dissem/apps/abit/util/PowStats.kt b/app/src/main/java/ch/dissem/apps/abit/util/PowStats.kt index 25892d4..af9ec7f 100644 --- a/app/src/main/java/ch/dissem/apps/abit/util/PowStats.kt +++ b/app/src/main/java/ch/dissem/apps/abit/util/PowStats.kt @@ -1,31 +1,47 @@ package ch.dissem.apps.abit.util import android.content.Context +import android.preference.PreferenceManager +import ch.dissem.apps.abit.util.Constants.PREFERENCE_POW_AVERAGE +import ch.dissem.apps.abit.util.Constants.PREFERENCE_POW_COUNT +import java.math.BigInteger /** - * Created by chrigu on 02.08.17. + * POW statistics that might help estimate the POW time, depending on */ object PowStats { - var powUnitTime: Long = 0 - var powCount: Long = 0 + private val TWO_POW_64 = BigInteger.valueOf(2).pow(64)!! + + var averagePowUnitTime = 0L + var powCount = 0L @JvmStatic - fun getExpectedPowTime(ctx: Context, target: ByteArray): Long { -// val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) -// return preferences.getLong(Constants.PREFERENCE_POW_AVERAGE, 0L) - return 0 + fun getExpectedPowTimeInMilliseconds(ctx: Context, target: ByteArray): Long { + if (averagePowUnitTime == 0L) { + val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) + synchronized(this) { + averagePowUnitTime = preferences.getLong(PREFERENCE_POW_AVERAGE, 0L) + powCount = preferences.getLong(PREFERENCE_POW_COUNT, 0L) + } + } + return (BigInteger.valueOf(averagePowUnitTime) * BigInteger(target) / TWO_POW_64).toLong() } -// fun updatePowTelemetry(ctx: Context, averagePowTime: Long, powCount: Long) { -// val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) -// preferences.edit() -// .putLong(Constants.PREFERENCE_POW_AVERAGE, averagePowTime) -// .putLong(Constants.PREFERENCE_POW_COUNT, powCount) -// .apply() -// } - @JvmStatic fun addPow(ctx: Context, time: Long, target: ByteArray) { - powCount++ + val targetBigInt = BigInteger(target) + val powCountBefore = BigInteger.valueOf(powCount) + synchronized(this) { + powCount++ + averagePowUnitTime = ( + (BigInteger.valueOf(averagePowUnitTime) * powCountBefore + (BigInteger.valueOf(time) * TWO_POW_64 / targetBigInt)) / BigInteger.valueOf(powCount) + ).toLong() + + val preferences = PreferenceManager.getDefaultSharedPreferences(ctx) + preferences.edit() + .putLong(PREFERENCE_POW_AVERAGE, averagePowUnitTime) + .putLong(PREFERENCE_POW_COUNT, powCount) + .apply() + } } }