Some notification improvements
POW progress probably needs some tweaking
This commit is contained in:
		| @@ -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()); | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
|     } | ||||
| } | ||||
| @@ -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(); | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
|     } | ||||
| } | ||||
| @@ -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 <code>synchronized(unacknowledged) | ||||
|      *                       {}</code> block | ||||
|      */ | ||||
|     public NewMessageNotification multiNotification(Collection<Plaintext> 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; | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user