package com.bitcoinandroid;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.util.Log;
import com.google.bitcoin.core.NetworkConnection;
import com.google.bitcoin.core.Peer;
import com.google.bitcoin.core.Transaction;
public class BackgroundTask extends AsyncTask<String, Integer, Boolean> {
ApplicationState appState = ApplicationState.current;
BitcoinWallet activity;
public ProgressDialog downloadingProgress;
private CountDownLatch progress;
private long remaining = 0;
public BackgroundTask(BitcoinWallet parent) {
this.activity = parent;
downloadingProgress = new ProgressDialog(activity);
downloadingProgress.setMessage(activity.getString(R.string.connecting_to_peers));
downloadingProgress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
downloadingProgress.setButton(activity.getString(R.string.run_in_background), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
if (activity.settings.getBoolean("firstRun", true)) {
SharedPreferences.Editor editor = activity.settings.edit();
editor.putBoolean("firstRun", false);
editor.commit();
}
downloadingProgress.dismiss();
}
});
if (activity.settings.getBoolean("firstRun", true)) {
downloadingProgress.show();
}
}
protected void onPreExecute() {
}
protected void onProgressUpdate(Integer... progress) {
int current = (int) progress[0];
int max = (int) progress[1];
downloadingProgress.setMessage(activity.getString((int) progress[2]));
if (current >= max) {
downloadingProgress.dismiss();
activity.hideSpinner();
} else {
downloadingProgress.setMax(max);
downloadingProgress.setProgress(max - current);
}
}
protected void onPostExecute(final Boolean success) {
}
protected Boolean doInBackground(final String... args) {
downloadBlockChain();
connectToLocalPeers();
resendPendingTransactions();
Log.d("Wallet", "Ending background task");
hideDialog();
return true;
}
private void downloadBlockChain() {
Log.d("Wallet", "Downloading block chain");
remaining = 0;
boolean done = false;
while (!done) {
ArrayList<InetSocketAddress> isas = appState.discoverPeers();
if (isas.size() == 0) {
// not connected to internet, could show a dialog to the user
// here?
done = true;
} else {
for (InetSocketAddress isa : isas) {
if (blockChainDownloadSuccessful(isa)) {
done = true;
break;
} else {
// remove and try next one
appState.removeBadPeer(isa);
}
}
}
}
hideDialog();
}
private boolean blockChainDownloadSuccessful(InetSocketAddress isa) {
NetworkConnection conn = createNetworkConnection(isa);
if (conn == null)
return false;
long current;
long last_current;
int no_change_count;
publishProgress(-1, 100, R.string.syncing_with_network);
Peer peer = new Peer(appState.params, conn, appState.blockChain, appState.wallet);
peer.start();
try {
Log.d("Wallet", "Starting download from new peer");
progress = peer.startBlockChainDownload();
current = progress.getCount();
last_current = current;
if (current > remaining)
remaining = current; // new max
no_change_count = 0;
while (true) {
double pct = 100.0 - (100.0 * (current / (double) remaining));
System.out.println(String.format("Chain download %d%% done", (int) pct));
progress.await(1, TimeUnit.SECONDS);
current = progress.getCount();
Log.d("Wallet", "no change count is " + no_change_count + " and current is " + current);
if (current == 0) {
// we're done!
return true;
} else if (current > (last_current - 10)) {
no_change_count++;
// if peer stopped talking to us for 8 seconds, or wasn't
// fast enough, lets break out and try next one
if (no_change_count >= 8)
return false;
} else {
// we're making progress, keep going!
last_current = current;
no_change_count = 0;
publishProgress((int) current, (int) remaining, R.string.syncing_with_network);
}
}
} catch (InterruptedException e) {
Log.d("Wallet", "InterruptedException in blockChainDownloadSuccessful");
e.printStackTrace();
} catch (IOException e) {
Log.d("Wallet", "IOException in blockChainDownloadSuccessful");
e.printStackTrace();
} finally {
// always calls this even if we return above
peer.disconnect();
}
return false;
}
private void hideDialog() {
publishProgress((int) remaining, (int) remaining, R.string.syncing_with_network);
}
/**
* Wrapped "new NetworkConnection" in a giant executor service since it
* would sometimes never return, even with the connectTimeout param Copied
* from
* http://stackoverflow.com/questions/1164301/how-do-i-call-some-blocking
* -method-with-a-timeout-in-java
*/
private NetworkConnection createNetworkConnection(final InetSocketAddress isa) {
ExecutorService executor = Executors.newCachedThreadPool();
Callable<NetworkConnection> task = new Callable<NetworkConnection>() {
public NetworkConnection call() {
NetworkConnection conn = null;
try {
conn = new NetworkConnection(isa.getAddress(), appState.params, appState.blockStore.getChainHead().getHeight(), 5000);
} catch (Exception e) {
}
return conn;
}
};
Future<NetworkConnection> future = executor.submit(task);
NetworkConnection result = null;
try {
result = future.get(10, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
} finally {
future.cancel(true);
}
return result;
}
/* connect to local peers (minimum of 3, maximum of 8) */
private synchronized void connectToLocalPeers() {
Log.d("Wallet", "Connecting to local peers");
synchronized (appState.connectedPeersLock) {
// clear out any which have disconnected
for (Peer peer : appState.connectedPeers) {
if (!peer.isRunning())
appState.connectedPeers.remove(peer);
}
if (appState.connectedPeers.size() < 3) {
for (InetSocketAddress isa : appState.discoverPeers()) {
publishProgress(8 - appState.connectedPeers.size(), 8, R.string.connecting_to_peers);
NetworkConnection conn = createNetworkConnection(isa);
if (conn == null) {
appState.removeBadPeer(isa);
} else {
Peer peer = new Peer(appState.params, conn, appState.blockChain, appState.wallet);
peer.start();
appState.connectedPeers.add(peer);
if (appState.connectedPeers.size() >= 8)
break;
}
}
}
}
}
private void resendPendingTransactions() {
Log.d("Wallet", "Resending pendings transactions");
for (Transaction tx : appState.wallet.pending.values()) {
if (tx.sent(appState.wallet)) {
Log.d("Wallet", "resending");
appState.sendTransaction(tx);
}
}
}
}