Skip to content

Commit

Permalink
Add notification channel for blockchain synchronization
Browse files Browse the repository at this point in the history
  • Loading branch information
HashEngineering committed Jul 5, 2019
1 parent 6f45ef9 commit f8e6d2a
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 5 deletions.
2 changes: 2 additions & 0 deletions wallet/src/de/schildbach/wallet/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ public final static class Files {
public static final int PEER_TIMEOUT_MS = 15 * (int) DateUtils.SECOND_IN_MILLIS;

public static final long LAST_USAGE_THRESHOLD_JUST_MS = DateUtils.HOUR_IN_MILLIS;
public static final long LAST_USAGE_THRESHOLD_2HOURS_MS = 2 + DateUtils.HOUR_IN_MILLIS;
public static final long LAST_USAGE_THRESHOLD_RECENTLY_MS = 2 * DateUtils.DAY_IN_MILLIS;
public static final long LAST_USAGE_THRESHOLD_INACTIVE_MS = 4 * DateUtils.WEEK_IN_MILLIS;

Expand All @@ -196,6 +197,7 @@ public final static class Files {
public static final int NOTIFICATION_ID_COINS_RECEIVED = 2;
public static final int NOTIFICATION_ID_MAINTENANCE = 3;
public static final int NOTIFICATION_ID_INACTIVITY = 4;
public static final int NOTIFICATION_ID_BLOCKCHAIN_SYNC = 5;
public static final String NOTIFICATION_GROUP_KEY_RECEIVED = "group-received";
public static final String NOTIFICATION_CHANNEL_ID_RECEIVED = "received";
public static final String NOTIFICATION_CHANNEL_ID_ONGOING = "ongoing";
Expand Down
117 changes: 112 additions & 5 deletions wallet/src/de/schildbach/wallet/service/BlockchainService.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence.ConfidenceType;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.listeners.AbstractPeerDataEventListener;
import org.bitcoinj.core.listeners.PeerConnectedEventListener;
import org.bitcoinj.core.listeners.PeerDataEventListener;
Expand Down Expand Up @@ -83,10 +84,12 @@
import de.schildbach.wallet.util.WalletUtils;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
Expand All @@ -96,6 +99,8 @@
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
Expand Down Expand Up @@ -157,6 +162,8 @@ public class BlockchainService extends LifecycleService {
+ ".broadcast_transaction";
private static final String ACTION_BROADCAST_TRANSACTION_HASH = "hash";

public static final String START_AS_FOREGROUND_EXTRA = "start_as_foreground";

private static final Logger log = LoggerFactory.getLogger(BlockchainService.class);

public static void start(final Context context, final boolean cancelCoinsReceived) {
Expand All @@ -167,6 +174,74 @@ public static void start(final Context context, final boolean cancelCoinsReceive
context.startService(new Intent(context, BlockchainService.class));
}


public void startForeground() {
//Shows ongoing notification promoting service to foreground service and
//preventing it from being killed in Android 26 or later
//Credit to Sam Barbosa for this code
Notification notification = createNetworkSyncNotification(getBlockchainState());
if (notification != null) {
startForeground(Constants.NOTIFICATION_ID_BLOCKCHAIN_SYNC, notification);
}
}

private static final long BLOCKCHAIN_UPTODATE_THRESHOLD_MS = DateUtils.HOUR_IN_MILLIS;

@Nullable
public static String getSyncStateString(BlockchainState blockchainState, Context context) {
if(blockchainState == null)
return null;
final long blockchainLag = System.currentTimeMillis() - blockchainState.bestChainDate.getTime();
final boolean blockchainUptodate = blockchainLag < BLOCKCHAIN_UPTODATE_THRESHOLD_MS;
final boolean noImpediments = blockchainState.impediments.isEmpty();

if (!(blockchainUptodate || !blockchainState.replaying)) {
String progressMessage;
final String downloading = context.getString(noImpediments ? R.string.blockchain_state_progress_downloading
: R.string.blockchain_state_progress_stalled);

if (blockchainLag < 2 * DateUtils.DAY_IN_MILLIS) {
final long hours = blockchainLag / DateUtils.HOUR_IN_MILLIS;
progressMessage = context.getString(R.string.blockchain_state_progress_hours, downloading, hours);
} else if (blockchainLag < 2 * DateUtils.WEEK_IN_MILLIS) {
final long days = blockchainLag / DateUtils.DAY_IN_MILLIS;
progressMessage = context.getString(R.string.blockchain_state_progress_days, downloading, days);
} else if (blockchainLag < 90 * DateUtils.DAY_IN_MILLIS) {
final long weeks = blockchainLag / DateUtils.WEEK_IN_MILLIS;
progressMessage = context.getString(R.string.blockchain_state_progress_weeks, downloading, weeks);
} else {
final long months = blockchainLag / (30 * DateUtils.DAY_IN_MILLIS);
progressMessage = context.getString(R.string.blockchain_state_progress_months, downloading, months);
}

return progressMessage;
} else {
return null;
}
}


private Notification createNetworkSyncNotification(BlockchainState blockchainState) {
Intent notificationIntent = new Intent(this, WalletActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(this, 0,
notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);

String message = getSyncStateString(blockchainState, this);
if (message == null) {
message = getString(R.string.blockchain_state_progress_downloading);
}

return new NotificationCompat.Builder(this,
Constants.NOTIFICATION_CHANNEL_ID_ONGOING)
.setSmallIcon(R.drawable.stat_notify_received_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(message)
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.build();
}

public static void stop(final Context context) {
context.stopService(new Intent(context, BlockchainService.class));
}
Expand All @@ -178,6 +253,8 @@ public static void scheduleStart(final WalletApplication application) {
// apply some backoff
final long alarmInterval;
if (lastUsedAgo < Constants.LAST_USAGE_THRESHOLD_JUST_MS)
alarmInterval = AlarmManager.INTERVAL_FIFTEEN_MINUTES / 15;
else if (lastUsedAgo < Constants.LAST_USAGE_THRESHOLD_2HOURS_MS)
alarmInterval = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
else if (lastUsedAgo < Constants.LAST_USAGE_THRESHOLD_RECENTLY_MS)
alarmInterval = AlarmManager.INTERVAL_HALF_DAY;
Expand All @@ -187,9 +264,19 @@ else if (lastUsedAgo < Constants.LAST_USAGE_THRESHOLD_RECENTLY_MS)
log.info("last used {} minutes ago, rescheduling blockchain sync in roughly {} minutes",
lastUsedAgo / DateUtils.MINUTE_IN_MILLIS, alarmInterval / DateUtils.MINUTE_IN_MILLIS);

final AlarmManager alarmManager = (AlarmManager) application.getSystemService(Context.ALARM_SERVICE);
final PendingIntent alarmIntent = PendingIntent.getService(application, 0,
new Intent(application, BlockchainService.class), 0);
AlarmManager alarmManager = (AlarmManager) application.getSystemService(Context.ALARM_SERVICE);
PendingIntent alarmIntent;
//final PendingIntent alarmIntent = PendingIntent.getService(application, 0,
// new Intent(application, BlockchainService.class), 0);
Intent serviceIntent = new Intent(application, BlockchainService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
serviceIntent.putExtra(BlockchainService.START_AS_FOREGROUND_EXTRA, true);
alarmIntent = PendingIntent.getForegroundService(application, 0, serviceIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
} else {
alarmIntent = PendingIntent.getService(application, 0, serviceIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
alarmManager.cancel(alarmIntent);

// workaround for no inexact set() before KitKat
Expand Down Expand Up @@ -768,7 +855,7 @@ private void shutdown() {
peerGroup.removeDisconnectedEventListener(peerConnectivityListener);
peerGroup.removeConnectedEventListener(peerConnectivityListener);
peerGroup.removeWallet(wallet);
log.info("stopping {} asynchronously", peerGroup);
log.info("stopping {} asynchronously (shutdown)", peerGroup);
peerGroup.stopAsync();
peerGroup = null;

Expand All @@ -786,6 +873,11 @@ public int onStartCommand(final Intent intent, final int flags, final int startI
log.info("service start command: " + intent + (intent.hasExtra(Intent.EXTRA_ALARM_COUNT)
? " (alarm count: " + intent.getIntExtra(Intent.EXTRA_ALARM_COUNT, 0) + ")" : ""));

Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(START_AS_FOREGROUND_EXTRA)) {
startForeground();
}

final String action = intent.getAction();

if (BlockchainService.ACTION_CANCEL_COINS_RECEIVED.equals(action)) {
Expand Down Expand Up @@ -827,7 +919,7 @@ public void onDestroy() {
peerGroup.removeConnectedEventListener(peerConnectivityListener);
peerGroup.removeWallet(wallet.getValue());
peerGroup.stopAsync();
log.info("stopping {} asynchronously", peerGroup);
log.info("stopping {} asynchronously (onDestroy)", peerGroup);
}

peerConnectivityListener.stop();
Expand Down Expand Up @@ -926,5 +1018,20 @@ private void broadcastBlockchainState() {
if (blockchainState != null)
blockchainState.putExtras(broadcast);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//Handle Ongoing notification state
if (blockchainState.bestChainHeight == config.getBestChainHeightEver()) {
//Remove ongoing notification if blockchain sync finished
stopForeground(true);
nm.cancel(Constants.NOTIFICATION_ID_BLOCKCHAIN_SYNC);
} else if (blockchainState.replaying || blockchainState.bestChainDate.getTime() < (Utils.currentTimeSeconds() - 60* 60 * 24 * 2)) {
//Shows ongoing notification when synchronizing the blockchain
Notification notification = createNetworkSyncNotification(blockchainState);
if (notification != null) {
nm.notify(Constants.NOTIFICATION_ID_BLOCKCHAIN_SYNC, notification);
}
}
}
}
}

0 comments on commit f8e6d2a

Please # to comment.