package com.bitcoinandroid;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import org.apache.commons.io.IOUtils;
import android.app.Application;
import android.app.backup.BackupManager;
import android.util.Log;
import com.google.bitcoin.core.BlockChain;
import com.google.bitcoin.core.BlockStore;
import com.google.bitcoin.core.BlockStoreException;
import com.google.bitcoin.core.BoundedOverheadBlockStore;
import com.google.bitcoin.core.DnsDiscovery;
import com.google.bitcoin.core.ECKey;
import com.google.bitcoin.core.IrcDiscovery;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Peer;
import com.google.bitcoin.core.PeerDiscovery;
import com.google.bitcoin.core.PeerDiscoveryException;
import com.google.bitcoin.core.Sha256Hash;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.Wallet;
public class ApplicationState extends Application {
// convenient place to keep global app variables
private boolean TEST_MODE = true;
Wallet wallet;
private String filePrefix = TEST_MODE ? "testnet" : "prodnet";
/**
* Every access to walletFile must be synch'ed with this lock. Backup agent
* can be run at any time.
*/
static final Object[] walletFileLock = new Object[0];
File walletFile;
File keychainFile;
boolean walletShouldBeRebuilt = false;
private BackupManager backupManager;
NetworkParameters params =
TEST_MODE ? NetworkParameters.testNet() : NetworkParameters.prodNet();
private PeerDiscovery peerDiscovery;
private ArrayList<InetSocketAddress> isas = new ArrayList<InetSocketAddress>();
static final Object[] connectedPeersLock = new Object[0];
ArrayList<Peer> connectedPeers = new ArrayList<Peer>(); // protected by above lock
BlockStore blockStore = null;
BlockChain blockChain;
//tracks which transactions we've shown a notification for so we couldn't duplicate them
//could probably persist this between start/top of the app, but fine for now
ArrayList<Sha256Hash> notifiedUserOfTheseTransactions = new ArrayList<Sha256Hash>();
public static ApplicationState current;
@Override
public void onCreate() {
Log.d("Wallet", "Starting app");
ApplicationState.current = (ApplicationState) this;
backupManager = new BackupManager(this);
// read or create wallet
synchronized (ApplicationState.walletFileLock) {
keychainFile = new File(getFilesDir(), filePrefix+".keychain");
walletFile = new File(getFilesDir(), filePrefix + ".wallet");
try {
//throw new RuntimeException("asd");
wallet = Wallet.loadFromFile(walletFile);
Log.d("Wallet", "Found wallet file to load");
} catch (Exception e) {
e.printStackTrace();
wallet = new Wallet(params);
Log.d("Wallet", "Created new wallet...now attempting to reset prior keys");
//load any previous keys in case this IOException was due to a serialization change of a previous wallet
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(keychainFile));
@SuppressWarnings("unchecked")
ArrayList<ECKey> keys = (ArrayList<ECKey>) ois.readObject();
for (ECKey key : keys) {
wallet.keychain.add(key);
}
walletShouldBeRebuilt = true;
} catch (Exception e2) {
Log.d("Wallet", "No prior keys found, a brand new wallet!");
wallet.keychain.add(new ECKey());
}
saveWallet();
} catch (StackOverflowError e) {
//couldn't deserialize the wallet - maybe it was saved in a previous version of bitcoinj?
e.printStackTrace();
}
}
if (TEST_MODE) {
peerDiscovery = new IrcDiscovery("#bitcoinTEST");
} else {
peerDiscovery = new DnsDiscovery(params);
}
Log.d("Wallet", "Reading block store from disk");
try {
File file = new File(getExternalFilesDir(null), filePrefix
+ ".blockchain");
if (!file.exists()) {
Log.d("Wallet", "Copying initial blockchain from assets folder");
InputStream is = null;
try {
is = getAssets().open(
filePrefix + ".blockchain");
IOUtils.copy(is, new FileOutputStream(file));
} catch (IOException e) {
Log.d("Wallet",
"Couldn't find initial blockchain in assets folder...starting from scratch");
}
finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
blockStore = new BoundedOverheadBlockStore(params, file);
blockChain = new BlockChain(params, wallet, blockStore);
} catch (BlockStoreException bse) {
throw new Error("Couldn't store block.");
}
}
public void saveWallet() {
synchronized (ApplicationState.walletFileLock) {
Log.d("Wallet", "Saving wallet");
try {
wallet.saveToFile(walletFile);
} catch (IOException e) {
throw new Error("Can't save wallet file.");
}
// save keys also in case we need to rebuild wallet later (serialization changes)
ObjectOutputStream keychain;
try {
keychain = new ObjectOutputStream(new FileOutputStream(keychainFile));
keychain.writeObject(wallet.keychain);
keychain.flush();
keychain.close();
} catch (IOException e) {
e.printStackTrace();
throw new Error("Can't save keychain file.");
}
}
Log.d("Wallet", "Notifying BackupManager that data has changed. Should backup soon.");
backupManager.dataChanged();
}
public synchronized void removeBadPeer(InetSocketAddress isa) {
Log.d("Wallet", "removing bad peer");
isas.remove(isa);
}
@SuppressWarnings("unchecked")
public ArrayList<InetSocketAddress> discoverPeers() {
if (isas.size() == 0) {
try {
isas.addAll(Arrays.asList(peerDiscovery.getPeers()));
Collections.shuffle(isas); // try different order each time
} catch (PeerDiscoveryException e) {
Log.d("Wallet", "Couldn't discover peers.");
}
}
Log.d("Wallet", "discoverPeers returning "+isas.size()+" peers");
// shallow clone to prevent concurrent modification exceptions
return (ArrayList<InetSocketAddress>) isas.clone();
}
/* rebroadcast pending transactions to all connected peers */
public void sendTransaction(Transaction tx) {
boolean success = false;
synchronized (connectedPeersLock) {
for (Peer peer : connectedPeers) {
try {
peer.broadcastTransaction(tx);
success = true;
Log.d("Wallet", "Broadcast succeeded");
} catch (IOException e) {
peer.disconnect();
connectedPeers.remove(peer);
Log.d("Wallet", "Broadcast failed");
}
}
}
if (success) {
wallet.confirmSend(tx);
saveWallet();
}
}
}