piuk.blockchain.android.MyRemoteWallet.java Source code

Java tutorial

Introduction

Here is the source code for piuk.blockchain.android.MyRemoteWallet.java

Source

/*
 * Copyright 2011-2012 the original author or authors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package piuk.blockchain.android;

import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.json.simple.parser.JSONParser;
import org.spongycastle.util.encoders.Hex;

import piuk.blockchain.android.Constants;
import piuk.blockchain.android.SuccessCallback;
import piuk.blockchain.android.util.WalletUtils;
import android.annotation.SuppressLint;
import android.util.Pair;
//import android.util.Log;

import com.google.bitcoin.core.Address;
import com.google.bitcoin.core.AddressFormatException;
import com.google.bitcoin.core.Base58;
import com.google.bitcoin.core.DumpedPrivateKey;
import com.google.bitcoin.core.ECKey;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Sha256Hash;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.Transaction.SigHash;
import com.google.bitcoin.core.TransactionInput;
import com.google.bitcoin.core.TransactionOutput;
import com.google.bitcoin.core.Utils;
import com.google.bitcoin.core.Wallet;
import com.google.bitcoin.params.MainNetParams;

@SuppressLint("DefaultLocale")
@SuppressWarnings("unchecked")
public class MyRemoteWallet extends MyWallet {
    private static final String WebROOT = "https://" + Constants.BLOCKCHAIN_DOMAIN + "/";
    private static final String ApiCode = "25a6ad13-1633-4dfb-b6ee-9b91cdf0b5c3";

    public static final String NotificationsTypeEmail = "1";
    public static final String NotificationsTypeSMS = "32";

    private String _checksum;
    private boolean _isNew = false;
    private MyBlock latestBlock;
    private long lastMultiAddress;
    private BigInteger final_balance = BigInteger.ZERO;
    private BigInteger total_received = BigInteger.ZERO;
    private BigInteger total_sent = BigInteger.ZERO;

    private String language = "en";
    private String btcCurrencyCode = "USD";
    private String localCurrencyCode = "BTC";
    private boolean sync_pubkeys = true;
    private double localCurrencyConversion;
    private double btcCurrencyConversion;
    private long serverTimeOffset = 0;
    private HashSet<String> notificationsTypeSet = new HashSet<String>();
    private String smsNumber = null;
    private String email = null;

    private Map<String, JSONObject> multiAddrBalancesRoot;
    private JSONObject multiAddrRoot;

    private double sharedFee;
    private List<MyTransaction> transactions = Collections.synchronizedList(new ArrayList<MyTransaction>());

    public static enum FeePolicy {
        FeeOnlyIfNeeded, FeeForce, FeeNever
    }

    private State state = State.INPUT;

    public static enum State {
        INPUT, SENDING, SENT
    }

    public void setState(State state) {
        this.state = state;
    }

    public State getState() {
        return this.state;
    }

    public MyBlock getLatestBlock() {
        return latestBlock;
    }

    public void setFinal_balance(BigInteger final_balance) {
        this.final_balance = final_balance;
    }

    public void setTotal_received(BigInteger total_received) {
        this.total_received = total_received;
    }

    public void setTotal_sent(BigInteger total_sent) {
        this.total_sent = total_sent;
    }

    public long getLastMultiAddress() {
        return lastMultiAddress;
    }

    public void setLatestBlock(MyBlock latestBlock) {
        this.latestBlock = latestBlock;
    }

    public BigInteger getFinal_balance() {
        return final_balance;
    }

    public BigInteger getTotal_received() {
        return total_received;
    }

    public BigInteger getTotal_sent() {
        return total_sent;
    }

    public String getLocalCurrencyCode() {
        return localCurrencyCode;
    }

    public double getLocalCurrencyConversion() {
        return localCurrencyConversion;
    }

    public Map<String, JSONObject> getMultiAddrBalancesRoot() {
        return multiAddrBalancesRoot;
    }

    public BigInteger getBalanceOfAddress(String address) {
        if (multiAddrBalancesRoot == null)
            return null;
        JSONObject addressRoot = multiAddrBalancesRoot.get(address);
        return BigInteger.valueOf(((Number) addressRoot.get("final_balance")).longValue());
    }

    public JSONObject getMultiAddrRoot() {
        return multiAddrRoot;
    }

    public void setMultiAddrRoot(JSONObject multiAddrRoot) {
        this.multiAddrRoot = multiAddrRoot;
    }

    public double getSharedFee() {
        return sharedFee;
    }

    public boolean isAddressMine(String address) {
        for (Map<String, Object> map : this.getKeysMap()) {
            String addr = (String) map.get("addr");

            if (address.equals(addr))
                return true;
        }

        return false;
    }

    public static class Latestblock {
        int height;
        int block_index;
        Hash hash;
        long time;
    }

    public synchronized BigInteger getBalance() {
        return final_balance;
    }

    public static String generateSharedAddress(String destination) throws Exception {
        StringBuilder args = new StringBuilder();

        args.append("address=" + destination);
        args.append("&shared=true");
        args.append("&format=plain");
        args.append("&method=create");

        final String response = postURL("https://" + Constants.BLOCKCHAIN_DOMAIN + "/api/receive", args.toString());

        JSONObject object = (JSONObject) new JSONParser().parse(response);

        return (String) object.get("input_address");
    }

    public synchronized BigInteger getBalance(String address) {
        if (this.multiAddrBalancesRoot != null && this.multiAddrBalancesRoot.containsKey(address)) {
            return BigInteger
                    .valueOf(((Number) this.multiAddrBalancesRoot.get(address).get("final_balance")).longValue());
        }

        return BigInteger.ZERO;
    }

    public boolean isNew() {
        return _isNew;
    }

    public MyRemoteWallet() throws Exception {
        super();

        this.temporyPassword = null;

        this._checksum = null;

        this._isNew = true;
    }

    public MyRemoteWallet(JSONObject walletJSONObj, String password) throws Exception {
        super(walletJSONObj.get("payload").toString(), password);

        handleWalletPayloadObj(walletJSONObj);

        this.temporyPassword = password;

        this._checksum = new String(Hex.encode(MessageDigest.getInstance("SHA-256")
                .digest(walletJSONObj.get("payload").toString().getBytes("UTF-8"))));

        this._isNew = false;
    }

    public MyRemoteWallet(String base64Payload, String password) throws Exception {
        super(base64Payload, password);

        this.temporyPassword = password;

        this._checksum = new String(
                Hex.encode(MessageDigest.getInstance("SHA-256").digest(base64Payload.getBytes("UTF-8"))));

        this._isNew = false;
    }

    private static String fetchURL(String URL) throws Exception {
        return WalletUtils.getURL(URL);
    }

    public static String postURL(String request, String urlParameters) throws Exception {
        if (urlParameters.length() > 0) {
            urlParameters += "&";
        }

        //Include the Android API Code in POST requests
        urlParameters += "api_code=" + ApiCode;

        return WalletUtils.postURL(request, urlParameters);
    }

    @Override
    public synchronized boolean addKey(ECKey key, String address, String label) throws Exception {
        boolean success = super.addKey(key, address, label);

        EventListeners.invokeWalletDidChange();

        return success;
    }

    @Override
    public synchronized boolean addKey(ECKey key, String address, String label, String device_name,
            String device_version) throws Exception {
        boolean success = super.addKey(key, address, label, device_name, device_version);

        EventListeners.invokeWalletDidChange();

        return success;
    }

    @Override
    public void setTag(String address, long tag) {
        super.setTag(address, tag);

        EventListeners.invokeWalletDidChange();
    }

    @Override
    public synchronized boolean addWatchOnly(String address, String source) throws Exception {
        boolean success = super.addWatchOnly(address, source);

        EventListeners.invokeWalletDidChange();

        return success;
    }

    public String[] getNotWatchOnlyActiveAddresses() {
        String[] from = getActiveAddresses();
        List<String> notWatchOnlyActiveAddresses = new ArrayList<String>(from.length);

        for (int i = 0; i < from.length; i++) {
            try {
                if (!isWatchOnly(from[i]))
                    notWatchOnlyActiveAddresses.add(from[i]);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return notWatchOnlyActiveAddresses.toArray(new String[notWatchOnlyActiveAddresses.size()]);
    }

    @Override
    public synchronized boolean removeKey(ECKey key) {
        boolean success = super.removeKey(key);

        EventListeners.invokeWalletDidChange();

        return success;
    }

    public List<MyTransaction> getMyTransactions() {
        return transactions;
    }

    public boolean addTransaction(MyTransaction tx) {

        for (MyTransaction existing_tx : transactions) {
            if (existing_tx.getTxIndex() == tx.getTxIndex())
                return false;
        }

        this.transactions.add(tx);

        return true;
    }

    public boolean prependTransaction(MyTransaction tx) {

        for (MyTransaction existing_tx : transactions) {
            if (existing_tx.getTxIndex() == tx.getTxIndex())
                return false;
        }

        this.transactions.add(0, tx);

        return true;
    }

    public BigInteger getBaseFee() {
        BigInteger baseFee = null;
        if (getFeePolicy() == -1) {
            baseFee = Utils.toNanoCoins("0.0001");
        } else if (getFeePolicy() == 1) {
            baseFee = Utils.toNanoCoins("0.0005");
        } else {
            baseFee = Utils.toNanoCoins("0.0001");
        }

        return baseFee;
    }

    public List<MyTransaction> getTransactions() {
        return this.transactions;
    }

    public MyTransaction getTransaction(String txHash) {
        if (this.transactions == null)
            return null;

        for (Iterator<MyTransaction> iterator = this.transactions.iterator(); iterator.hasNext();) {
            MyTransaction tx = iterator.next();
            if (tx.getHashAsString().equals(txHash))
                return tx;
        }
        return null;
    }

    public void parseMultiAddr(String response, boolean notifications) throws Exception {

        transactions.clear();

        BigInteger previousBalance = final_balance;

        Map<String, Object> top = (Map<String, Object>) JSONValue.parse(response);

        this.multiAddrRoot = (JSONObject) top;

        Map<String, Object> info_obj = (Map<String, Object>) top.get("info");

        Map<String, Object> block_obj = (Map<String, Object>) info_obj.get("latest_block");

        if (block_obj != null) {
            Sha256Hash hash = new Sha256Hash(Hex.decode((String) block_obj.get("hash")));
            int blockIndex = ((Number) block_obj.get("block_index")).intValue();
            int blockHeight = ((Number) block_obj.get("height")).intValue();
            long time = ((Number) block_obj.get("time")).longValue();

            MyBlock block = new MyBlock();

            block.height = blockHeight;
            block.hash = hash;
            block.blockIndex = blockIndex;
            block.time = time;

            this.latestBlock = block;
        }

        List<JSONObject> multiAddrBalances = (List<JSONObject>) top.get("addresses");

        Map<String, JSONObject> multiAddrBalancesRoot = new HashMap<String, JSONObject>();

        for (JSONObject obj : multiAddrBalances) {
            multiAddrBalancesRoot.put((String) obj.get("address"), obj);
        }

        this.multiAddrBalancesRoot = multiAddrBalancesRoot;

        Map<String, Object> symbol_local = (Map<String, Object>) info_obj.get("symbol_local");

        boolean didUpdateCurrency = false;

        if (symbol_local != null && symbol_local.containsKey("code")) {
            String currencyCode = (String) symbol_local.get("code");
            Double currencyConversion = (Double) symbol_local.get("conversion");

            if (currencyConversion == null)
                currencyConversion = 0d;

            if (this.localCurrencyCode == null || !this.localCurrencyCode.equals(currencyCode)
                    || this.localCurrencyConversion != currencyConversion) {
                this.localCurrencyCode = currencyCode;
                this.localCurrencyConversion = currencyConversion;
                didUpdateCurrency = true;
            }
        }

        Map<String, Object> symbol_btc = (Map<String, Object>) info_obj.get("symbol_btc");
        if (symbol_btc != null && symbol_btc.containsKey("code")) {
            String currencyCode = (String) symbol_local.get("code");
            Double currencyConversion = (Double) symbol_local.get("conversion");

            if (currencyConversion == null)
                currencyConversion = 0d;

            if (this.btcCurrencyCode == null || !this.btcCurrencyCode.equals(currencyCode)
                    || this.btcCurrencyConversion != currencyConversion) {
                this.btcCurrencyCode = currencyCode;
                this.btcCurrencyConversion = currencyConversion;
                //didUpdateCurrency = true;
            }
        }

        if (didUpdateCurrency) {
            EventListeners.invokeCurrencyDidChange();
        }

        if (top.containsKey("mixer_fee")) {
            sharedFee = ((Number) top.get("mixer_fee")).doubleValue();
        }

        Map<String, Object> wallet_obj = (Map<String, Object>) top.get("wallet");

        this.final_balance = BigInteger.valueOf(((Number) wallet_obj.get("final_balance")).longValue());
        this.total_sent = BigInteger.valueOf(((Number) wallet_obj.get("total_sent")).longValue());
        this.total_received = BigInteger.valueOf(((Number) wallet_obj.get("total_received")).longValue());

        List<Map<String, Object>> transactions = (List<Map<String, Object>>) top.get("txs");

        MyTransaction newestTransaction = null;
        if (transactions != null) {
            for (Map<String, Object> transactionDict : transactions) {
                MyTransaction tx = MyTransaction.fromJSONDict(transactionDict);

                if (tx == null)
                    continue;

                if (newestTransaction == null)
                    newestTransaction = tx;

                addTransaction(tx);
            }
        }

        if (notifications) {
            if (this.final_balance.compareTo(previousBalance) != 0 && newestTransaction != null) {
                if (newestTransaction.getResult().compareTo(BigInteger.ZERO) >= 0)
                    EventListeners.invokeOnCoinsReceived(newestTransaction,
                            newestTransaction.getResult().longValue());
                else
                    EventListeners.invokeOnCoinsSent(newestTransaction, newestTransaction.getResult().longValue());
            }
        } else {
            EventListeners.invokeOnTransactionsChanged();
        }
    }

    public boolean isUptoDate(long time) {
        long now = System.currentTimeMillis();

        if (lastMultiAddress < now - time) {
            return false;
        } else {
            return true;
        }
    }

    public static synchronized String getBalances(String[] addresses, boolean notifications) throws Exception {
        String url = WebROOT + "multiaddr?active=" + StringUtils.join(addresses, "|") + "&simple=true&format=json";

        String response = fetchURL(url);

        return response;
    }

    public synchronized String doMultiAddr(boolean notifications) throws Exception {
        String url = WebROOT + "multiaddr?active=" + StringUtils.join(getActiveAddresses(), "|") + "&symbol_btc="
                + btcCurrencyCode + "&symbol_local=" + localCurrencyCode;

        String response = fetchURL(url);

        parseMultiAddr(response, notifications);

        lastMultiAddress = System.currentTimeMillis();

        return response;
    }

    public synchronized void doMultiAddr(boolean notifications, SuccessCallback callback) {
        try {
            String url = WebROOT + "multiaddr?active=" + StringUtils.join(getActiveAddresses(), "|")
                    + "&symbol_btc=" + btcCurrencyCode + "&symbol_local=" + localCurrencyCode;

            String response = fetchURL(url);

            parseMultiAddr(response, notifications);

            lastMultiAddress = System.currentTimeMillis();

            callback.onSuccess();
        } catch (Exception e) {
            e.printStackTrace();

            callback.onFail();
        }
    }

    public interface SendProgress {
        public void onStart();

        //Return false to cancel
        public boolean onReady(Transaction tx, BigInteger fee, FeePolicy feePolicy, long priority);

        public void onSend(Transaction tx, String message);

        //Return true to cancel the transaction or false to continue without it
        public ECKey onPrivateKeyMissing(String address);

        public void onError(String message);

        public void onProgress(String message);
    }

    private List<MyTransactionOutPoint> filter(List<MyTransactionOutPoint> unspent, List<ECKey> tempKeys,
            boolean askForPrivateKeys, final SendProgress progress) throws Exception {
        List<MyTransactionOutPoint> filtered = new ArrayList<MyTransactionOutPoint>();

        Set<String> alreadyAskedFor = new HashSet<String>();

        for (MyTransactionOutPoint output : unspent) {
            BitcoinScript script = new BitcoinScript(output.getScriptBytes());

            String addr = script.getAddress().toString();

            Map<String, Object> keyMap = findKey(addr);

            if (keyMap.get("priv") == null) {
                if (askForPrivateKeys && alreadyAskedFor.add(addr)) {

                    ECKey key = progress.onPrivateKeyMissing(addr);

                    if (key != null) {
                        filtered.add(output);

                        tempKeys.add(key);
                    }
                }
            } else {
                filtered.add(output);
            }
        }

        return filtered;
    }

    private Pair<ECKey, String> generateNewMiniPrivateKey() {
        SecureRandom random = new SecureRandom();

        while (true) {
            //         Log.d("generateNewMiniPrivateKey", "generateNewMiniPrivateKey");

            //Make Candidate Mini Key
            byte randomBytes[] = new byte[16];
            random.nextBytes(randomBytes);
            String encodedBytes = Base58.encode(randomBytes);
            //TODO: Casascius Series 1 22-character variant, remember to notify Ben about updating to 30-character variant
            String minikey = 'S' + encodedBytes.substring(0, 21);

            try {
                //Append ? & hash it again
                byte[] bytes_appended = MessageDigest.getInstance("SHA-256").digest((minikey + '?').getBytes());

                //If zero byte then the key is valid
                if (bytes_appended[0] == 0) {

                    try {
                        //SHA256
                        byte[] bytes = MessageDigest.getInstance("SHA-256").digest(minikey.getBytes());

                        final ECKey eckey = new ECKey(bytes, null);

                        final DumpedPrivateKey dumpedPrivateKey1 = eckey.getPrivateKeyEncoded(getParams());
                        String privateKey1 = Base58.encode(dumpedPrivateKey1.bytes);

                        final String toAddress = eckey.toAddress(getParams()).toString();
                        /*
                        Log.d("sendCoinsToFriend", "generateNewMiniPrivateKey: minikey: " + minikey);
                        Log.d("sendCoinsToFriend", "generateNewMiniPrivateKey: privateKey: " + privateKey1);
                        Log.d("sendCoinsToFriend", "generateNewMiniPrivateKey: address: " + toAddress);
                        */

                        return new Pair<ECKey, String>(eckey, minikey);

                    } catch (NoSuchAlgorithmException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } catch (NoSuchAlgorithmException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public void sendCoinsEmail(final String email, final BigInteger amount, final FeePolicy feePolicy,
            final BigInteger fee, final SendProgress progress) throws Exception {
        sendCoinsToFriend("email", email, amount, feePolicy, fee, progress);
    }

    public void sendCoinsSMS(final String number, final BigInteger amount, final FeePolicy feePolicy,
            final BigInteger fee, final SendProgress progress) throws Exception {
        sendCoinsToFriend("sms", number, amount, feePolicy, fee, progress);
    }

    private void sendCoinsToFriend(final String sendType, final String emailOrNumber, final BigInteger amount,
            final FeePolicy feePolicy, final BigInteger fee, final SendProgress progress) throws Exception {

        new Thread() {
            @Override
            public void run() {
                progress.onStart();
                final List<ECKey> tempKeys = new ArrayList<ECKey>();

                try {
                    final String[] from = getActiveAddresses();
                    final ECKey key;
                    Pair<ECKey, String> keyAndMiniKey = null;
                    /*
                    if (sendType == "sms") {
                       keyAndMiniKey = generateNewMiniPrivateKey();
                       key = keyAndMiniKey.first;                     
                    } else {
                       key = generateECKey();
                    }
                    */
                    keyAndMiniKey = generateNewMiniPrivateKey();
                    key = keyAndMiniKey.first;

                    //Construct a new transaction
                    progress.onProgress("Getting Unspent Outputs");

                    List<MyTransactionOutPoint> allUnspent = getUnspentOutputPoints(from);

                    Pair<Transaction, Long> pair = null;

                    progress.onProgress("Constructing Transaction");

                    final DumpedPrivateKey dumpedPrivateKey = key.getPrivateKeyEncoded(getParams());
                    String privateKey = Base58.encode(dumpedPrivateKey.bytes);

                    final String toAddress = key.toAddress(getParams()).toString();
                    /*
                    Log.d("sendCoinsToFriend", "sendCoinsToFriend: privateKey: " + privateKey);
                    Log.d("sendCoinsToFriend", "sendCoinsToFriend: toAddress: " + toAddress);
                    */

                    try {
                        //Try without asking for watch only addresses
                        List<MyTransactionOutPoint> unspent = filter(allUnspent, tempKeys, false, progress);
                        HashMap<String, BigInteger> receivingAddresses = new HashMap<String, BigInteger>();
                        receivingAddresses.put(toAddress, amount);
                        pair = makeTransaction(false, unspent, receivingAddresses, fee, null);

                        //Transaction cancelled
                        if (pair == null)
                            return;
                    } catch (InsufficientFundsException e) {

                        //Try with asking for watch only
                        List<MyTransactionOutPoint> unspent = filter(allUnspent, tempKeys, true, progress);
                        HashMap<String, BigInteger> receivingAddresses = new HashMap<String, BigInteger>();
                        receivingAddresses.put(toAddress, amount);
                        pair = makeTransaction(false, unspent, receivingAddresses, fee, null);

                        //Transaction cancelled
                        if (pair == null)
                            return;
                    }

                    if (allUnspent != null) {
                        Transaction tx = pair.first;

                        progress.onProgress("Signing Inputs");

                        Wallet wallet = new Wallet(MainNetParams.get());
                        for (TransactionInput input : tx.getInputs()) {
                            byte[] scriptBytes = input.getOutpoint().getConnectedPubKeyScript();
                            String address = new BitcoinScript(scriptBytes).getAddress().toString();
                            final ECKey walletKey;
                            try {
                                walletKey = getECKey(address);
                            } catch (Exception e) {
                                // skip add Watch Only Bitcoin Address key because already accounted for  later with tempKeys
                                continue;
                            }
                            ECKey keyCompressed;
                            ECKey keyUnCompressed;
                            BigInteger priv = new BigInteger(walletKey.getPrivKeyBytes());
                            if (priv.compareTo(BigInteger.ZERO) >= 0) {
                                keyCompressed = new ECKey(priv, null, true);
                                keyUnCompressed = new ECKey(priv, null, false);
                            } else {
                                byte[] appendZeroByte = ArrayUtils.addAll(new byte[1], walletKey.getPrivKeyBytes());
                                BigInteger priv2 = new BigInteger(appendZeroByte);
                                keyCompressed = new ECKey(priv2, null, true);
                                keyUnCompressed = new ECKey(priv2, null, false);
                            }

                            if (keyCompressed != null) {
                                wallet.addKey(keyCompressed);
                            }

                            if (keyUnCompressed != null) {
                                wallet.addKey(keyUnCompressed);
                            }
                        }

                        wallet.addKeys(tempKeys);

                        //Now sign the inputs
                        tx.signInputs(SigHash.ALL, wallet);

                        progress.onProgress("Broadcasting Transaction");

                        final String txHash = tx.getHashAsString();

                        /*
                        Log.d("sendCoinsToFriend", "sendCoinsToFriend: txHash: " + txHash);                  
                        Log.d("sendCoinsToFriend", "sendCoinsToFriend: emailOrNumber: " + emailOrNumber);
                        */

                        Map<Object, Object> params = new HashMap<Object, Object>();
                        params.put("type", sendType);
                        //                  Log.d("sendCoinsToFriend", "type:" + sendType);
                        String privParameter = (sendType == "sms") ? keyAndMiniKey.second : privateKey;
                        params.put("priv", privParameter);
                        params.put("hash", txHash);
                        params.put("to", emailOrNumber);
                        //                  Log.d("sendCoinsToFriend", "to:" + emailOrNumber);
                        params.put("guid", getGUID());
                        params.put("sharedKey", getSharedKey());

                        try {
                            String response = WalletUtils.postURLWithParams(WebROOT + "send-via", params);
                            if (response != null && response.length() > 0) {
                                progress.onProgress("Send Transaction");
                                //                        Log.d("sendCoinsToFriend", "sendCoinsToFriend: send-via response: " + response);
                                String response2 = pushTx(tx);
                                if (response2 != null && response2.length() > 0) {
                                    //                           Log.d("sendCoinsToFriend", "sendCoinsToFriend: pushTx response: " + response2);
                                    progress.onSend(tx, response2);

                                    String label = sendType == "email" ? emailOrNumber + " Sent Via Email"
                                            : emailOrNumber + " Sent Via SMS";
                                    addKey(key, toAddress, label);
                                    setTag(toAddress, 2);
                                }
                            }
                        } catch (Exception e) {
                            progress.onError(e.getMessage());
                            e.printStackTrace();
                        }
                    }

                } catch (Exception e) {
                    progress.onError(e.getMessage());
                    e.printStackTrace();
                }

            }
        }.start();

    }

    public void simpleSendCoinsAsync(final String toAddress, final BigInteger amount, final FeePolicy feePolicy,
            final BigInteger fee, final SendProgress progress) {
        HashMap<String, BigInteger> receivingAddresses = new HashMap<String, BigInteger>();
        receivingAddresses.put(toAddress, amount);

        final String[] from = getActiveAddresses();
        HashMap<String, BigInteger> sendingAddresses = new HashMap<String, BigInteger>();
        for (int i = 0; i < from.length; i++)
            sendingAddresses.put(from[i], null);

        sendCoinsAsync(true, sendingAddresses, receivingAddresses, feePolicy, fee, null, progress);
    }

    public void sendCoinsAsync(final String[] from, final String toAddress, final BigInteger amount,
            final FeePolicy feePolicy, final BigInteger fee, final String changeAddress,
            final SendProgress progress) {
        HashMap<String, BigInteger> receivingAddresses = new HashMap<String, BigInteger>();
        receivingAddresses.put(toAddress, amount);

        HashMap<String, BigInteger> sendingAddresses = new HashMap<String, BigInteger>();
        for (int i = 0; i < from.length; i++)
            sendingAddresses.put(from[i], null);

        sendCoinsAsync(false, sendingAddresses, receivingAddresses, feePolicy, fee, changeAddress, progress);
    }

    public void sendCoinsAsync(final HashMap<String, BigInteger> sendingAddresses, final String toAddress,
            final BigInteger amount, final FeePolicy feePolicy, final BigInteger fee, final String changeAddress,
            final SendProgress progress) {
        BigInteger sum = BigInteger.ZERO;
        for (Iterator<Entry<String, BigInteger>> iterator = sendingAddresses.entrySet().iterator(); iterator
                .hasNext();) {
            Entry<String, BigInteger> entry = iterator.next();
            sum = sum.add(entry.getValue());
        }

        if (sum.compareTo(amount) != 0) {
            progress.onError("Internal error input amounts not validating correctly");
            return;
        }

        HashMap<String, BigInteger> receivingAddresses = new HashMap<String, BigInteger>();
        receivingAddresses.put(toAddress, amount);

        sendCoinsAsync(false, sendingAddresses, receivingAddresses, feePolicy, fee, changeAddress, progress);
    }

    private void sendCoinsAsync(final boolean isSimpleSend, final HashMap<String, BigInteger> sendingAddresses,
            final HashMap<String, BigInteger> receivingAddresses, final FeePolicy feePolicy, final BigInteger fee,
            final String changeAddress, final SendProgress progress) {

        new Thread() {
            @Override
            public void run() {
                progress.onStart();

                final BigInteger feeAmount;
                if (fee == null)
                    feeAmount = BigInteger.ZERO;
                else
                    feeAmount = fee;

                final List<ECKey> tempKeys = new ArrayList<ECKey>();

                try {

                    //Construct a new transaction
                    progress.onProgress("Getting Unspent Outputs");

                    List<String> from = new ArrayList<String>(sendingAddresses.keySet());
                    List<MyTransactionOutPoint> allUnspent = getUnspentOutputPoints(
                            from.toArray(new String[from.size()]));

                    Pair<Transaction, Long> pair = null;

                    progress.onProgress("Constructing Transaction");

                    try {
                        //Try without asking for watch only addresses
                        List<MyTransactionOutPoint> unspent = filter(allUnspent, tempKeys, false, progress);

                        if (isSimpleSend) {
                            pair = makeTransaction(isSimpleSend, unspent, receivingAddresses, feeAmount,
                                    changeAddress);
                        } else {
                            pair = makeTransactionCustom(sendingAddresses, unspent, receivingAddresses, feeAmount,
                                    changeAddress);
                        }

                        //Transaction cancelled
                        if (pair == null)
                            return;
                    } catch (InsufficientFundsException e) {

                        //Try with asking for watch only
                        List<MyTransactionOutPoint> unspent = filter(allUnspent, tempKeys, true, progress);

                        if (isSimpleSend) {
                            pair = makeTransaction(isSimpleSend, unspent, receivingAddresses, feeAmount,
                                    changeAddress);
                        } else {
                            pair = makeTransactionCustom(sendingAddresses, unspent, receivingAddresses, feeAmount,
                                    changeAddress);
                        }

                        //Transaction cancelled
                        if (pair == null)
                            return;
                    }

                    Transaction tx = pair.first;
                    Long priority = pair.second;

                    if (isSimpleSend) {
                        //If returns false user cancelled
                        //Probably because they want to recreate the transaction with different fees
                        if (!progress.onReady(tx, feeAmount, feePolicy, priority))
                            return;
                    }

                    progress.onProgress("Signing Inputs");

                    Wallet wallet = new Wallet(MainNetParams.get());
                    for (TransactionInput input : tx.getInputs()) {
                        byte[] scriptBytes = input.getOutpoint().getConnectedPubKeyScript();
                        String address = new BitcoinScript(scriptBytes).getAddress().toString();
                        final ECKey walletKey;
                        try {
                            walletKey = getECKey(address);
                        } catch (Exception e) {
                            // skip add Watch Only Bitcoin Address key because already accounted for  later with tempKeys
                            continue;
                        }
                        ECKey keyCompressed;
                        ECKey keyUnCompressed;
                        BigInteger priv = new BigInteger(walletKey.getPrivKeyBytes());
                        if (priv.compareTo(BigInteger.ZERO) >= 0) {
                            keyCompressed = new ECKey(priv, null, true);
                            keyUnCompressed = new ECKey(priv, null, false);
                        } else {
                            byte[] appendZeroByte = ArrayUtils.addAll(new byte[1], walletKey.getPrivKeyBytes());
                            BigInteger priv2 = new BigInteger(appendZeroByte);
                            keyCompressed = new ECKey(priv2, null, true);
                            keyUnCompressed = new ECKey(priv2, null, false);
                        }

                        if (keyCompressed != null) {
                            wallet.addKey(keyCompressed);
                        }

                        if (keyUnCompressed != null) {
                            wallet.addKey(keyUnCompressed);
                        }
                    }

                    wallet.addKeys(tempKeys);

                    //Now sign the inputs
                    tx.signInputs(SigHash.ALL, wallet);

                    progress.onProgress("Broadcasting Transaction");

                    String response = pushTx(tx);

                    progress.onSend(tx, response);

                } catch (Exception e) {
                    e.printStackTrace();

                    progress.onError(e.getLocalizedMessage());

                }
            }
        }.start();
    }

    //Returns response message
    public String pushTx(Transaction tx) throws Exception {

        String hexString = new String(Hex.encode(tx.bitcoinSerialize()));

        if (hexString.length() > 16384)
            throw new Exception(
                    "Blockchain wallet's cannot handle transactions over 16kb in size. Please try splitting your transaction");

        String response = postURL(WebROOT + "pushtx", "tx=" + hexString);

        return response;
    }

    public static class InsufficientFundsException extends Exception {
        private static final long serialVersionUID = 1L;

        public InsufficientFundsException(String string) {
            super(string);
        }
    }

    //You must sign the inputs
    public Pair<Transaction, Long> makeTransaction(boolean isSimpleSend, List<MyTransactionOutPoint> unspent,
            HashMap<String, BigInteger> receivingAddresses, BigInteger fee, final String changeAddress)
            throws Exception {

        long priority = 0;

        if (unspent == null || unspent.size() == 0)
            throw new InsufficientFundsException("No free outputs to spend.");

        if (fee == null)
            fee = BigInteger.ZERO;

        //Construct a new transaction
        Transaction tx = new Transaction(getParams());

        BigInteger outputValueSum = BigInteger.ZERO;

        for (Iterator<Entry<String, BigInteger>> iterator = receivingAddresses.entrySet().iterator(); iterator
                .hasNext();) {
            Map.Entry<String, BigInteger> mapEntry = iterator.next();
            String toAddress = mapEntry.getKey();
            BigInteger amount = mapEntry.getValue();

            if (amount == null || amount.compareTo(BigInteger.ZERO) <= 0)
                throw new Exception("You must provide an amount");

            outputValueSum = outputValueSum.add(amount);
            //Add the output
            BitcoinScript toOutputScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(toAddress));
            //         Log.d("MyRemoteWallet", "MyRemoteWallet makeTransaction toAddress: " + toAddress + "amount: " + amount);

            TransactionOutput output = new TransactionOutput(getParams(), null, amount,
                    toOutputScript.getProgram());

            tx.addOutput(output);
        }

        //Now select the appropriate inputs
        BigInteger valueSelected = BigInteger.ZERO;
        BigInteger valueNeeded = outputValueSum.add(fee);
        BigInteger minFreeOutputSize = BigInteger.valueOf(1000000);

        MyTransactionOutPoint changeOutPoint = null;

        for (MyTransactionOutPoint outPoint : unspent) {

            BitcoinScript script = new BitcoinScript(outPoint.getScriptBytes());

            if (script.getOutType() == BitcoinScript.ScriptOutTypeStrange)
                continue;

            BitcoinScript inputScript = new BitcoinScript(outPoint.getConnectedPubKeyScript());
            String address = inputScript.getAddress().toString();

            //if isSimpleSend don't use address as input if is output 
            if (isSimpleSend && receivingAddresses.get(address) != null)
                continue;

            MyTransactionInput input = new MyTransactionInput(getParams(), null, new byte[0], outPoint);

            input.outpoint = outPoint;
            //         Log.d("MyRemoteWallet", "MyRemoteWallet makeTransaction fromAddress: " + address + "amount: " + outPoint.value);

            tx.addInput(input);

            valueSelected = valueSelected.add(outPoint.value);

            priority += outPoint.value.longValue() * outPoint.confirmations;

            if (changeAddress == null)
                changeOutPoint = outPoint;

            if (valueSelected.compareTo(valueNeeded) == 0
                    || valueSelected.compareTo(valueNeeded.add(minFreeOutputSize)) >= 0)
                break;
        }

        //Check the amount we have selected is greater than the amount we need
        if (valueSelected.compareTo(valueNeeded) < 0) {
            throw new InsufficientFundsException("Insufficient Funds");
        }

        BigInteger change = valueSelected.subtract(outputValueSum).subtract(fee);

        //Now add the change if there is any
        if (change.compareTo(BigInteger.ZERO) > 0) {
            BitcoinScript change_script;
            if (changeAddress != null) {
                change_script = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(changeAddress));
                //            Log.d("MyRemoteWallet", "MyRemoteWallet makeTransaction changeAddress != null: " + changeAddress + "change: " + change);
            } else if (changeOutPoint != null) {
                BitcoinScript inputScript = new BitcoinScript(changeOutPoint.getConnectedPubKeyScript());
                //             Log.d("MyRemoteWallet", "MyRemoteWallet makeTransaction changeAddress == null: " + inputScript.getAddress() + "change: " + change);

                //Return change to the first address
                change_script = BitcoinScript.createSimpleOutBitoinScript(inputScript.getAddress());
            } else {
                throw new Exception("Invalid transaction attempt");
            }
            TransactionOutput change_output = new TransactionOutput(getParams(), null, change,
                    change_script.getProgram());

            tx.addOutput(change_output);
        }

        long estimatedSize = tx.bitcoinSerialize().length + (114 * tx.getInputs().size());

        priority /= estimatedSize;

        return new Pair<Transaction, Long>(tx, priority);
    }

    //You must sign the inputs
    public Pair<Transaction, Long> makeTransactionCustom(final HashMap<String, BigInteger> sendingAddresses,
            List<MyTransactionOutPoint> unspent, HashMap<String, BigInteger> receivingAddresses, BigInteger fee,
            final String changeAddress) throws Exception {

        long priority = 0;

        if (unspent == null || unspent.size() == 0)
            throw new InsufficientFundsException("No free outputs to spend.");

        if (fee == null)
            fee = BigInteger.ZERO;

        //Construct a new transaction
        Transaction tx = new Transaction(getParams());

        BigInteger outputValueSum = BigInteger.ZERO;

        for (Iterator<Entry<String, BigInteger>> iterator = receivingAddresses.entrySet().iterator(); iterator
                .hasNext();) {
            Map.Entry<String, BigInteger> mapEntry = iterator.next();
            String toAddress = mapEntry.getKey();
            BigInteger amount = mapEntry.getValue();

            if (amount == null || amount.compareTo(BigInteger.ZERO) <= 0)
                throw new Exception("You must provide an amount");

            outputValueSum = outputValueSum.add(amount);
            //Add the output
            BitcoinScript toOutputScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(toAddress));
            //         Log.d("MyRemoteWallet", "MyRemoteWallet makeTransactionCustom toAddress: " + toAddress + "amount: " + amount);

            TransactionOutput output = new TransactionOutput(getParams(), null, amount,
                    toOutputScript.getProgram());
            tx.addOutput(output);
        }

        //Now select the appropriate inputs
        BigInteger valueSelected = BigInteger.ZERO;
        BigInteger valueNeeded = outputValueSum.add(fee);

        Map<String, BigInteger> addressTotalUnspentValues = new HashMap<String, BigInteger>();

        for (MyTransactionOutPoint outPoint : unspent) {

            BitcoinScript script = new BitcoinScript(outPoint.getScriptBytes());

            if (script.getOutType() == BitcoinScript.ScriptOutTypeStrange)
                continue;

            BitcoinScript inputScript = new BitcoinScript(outPoint.getConnectedPubKeyScript());
            String address = inputScript.getAddress().toString();

            BigInteger addressSendAmount = sendingAddresses.get(address);
            if (addressSendAmount == null) {
                throw new Exception("Invalid transaction address send amount is null");
            }

            final BigInteger addressTotalUnspentValue = addressTotalUnspentValues.get(address);

            if (addressTotalUnspentValue == null) {
                addressTotalUnspentValues.put(address, outPoint.value);
            } else {
                addressTotalUnspentValues.put(address, addressTotalUnspentValue.add(outPoint.value));
            }

            MyTransactionInput input = new MyTransactionInput(getParams(), null, new byte[0], outPoint);

            input.outpoint = outPoint;
            //         Log.d("MyRemoteWallet", "MyRemoteWallet makeTransactionCustom fromAddress: " + address + "amount: " + outPoint.value);

            tx.addInput(input);

            valueSelected = valueSelected.add(outPoint.value);

            priority += outPoint.value.longValue() * outPoint.confirmations;

            //if (valueSelected.compareTo(valueNeeded) == 0 || valueSelected.compareTo(valueNeeded.add(minFreeOutputSize)) >= 0)
            //   break;
        }

        //Check the amount we have selected is greater than the amount we need
        if (valueSelected.compareTo(valueNeeded) < 0) {
            throw new InsufficientFundsException("Insufficient Funds");
        }

        //decide change
        if (changeAddress == null) {
            BigInteger feeAmountLeftToAccountedFor = fee;

            for (Iterator<Entry<String, BigInteger>> iterator = addressTotalUnspentValues.entrySet()
                    .iterator(); iterator.hasNext();) {
                final Entry<String, BigInteger> entry = iterator.next();
                final String address = entry.getKey();
                final BigInteger addressTotalUnspentValue = entry.getValue();
                final BigInteger addressSendAmount = sendingAddresses.get(address);
                BigInteger addressChangeAmount = addressTotalUnspentValue.subtract(addressSendAmount);

                if (feeAmountLeftToAccountedFor.compareTo(BigInteger.ZERO) > 0) {

                    if (addressChangeAmount.compareTo(feeAmountLeftToAccountedFor) >= 0) {
                        //have enough to fill fee
                        addressChangeAmount = addressChangeAmount.subtract(feeAmountLeftToAccountedFor);
                        feeAmountLeftToAccountedFor = BigInteger.ZERO;
                    } else {
                        // do not have enough to fill fee
                        feeAmountLeftToAccountedFor = feeAmountLeftToAccountedFor.subtract(addressChangeAmount);
                        addressChangeAmount = BigInteger.ZERO;
                    }
                }

                if (addressChangeAmount.compareTo(BigInteger.ZERO) > 0) {
                    //Add the output
                    BitcoinScript toOutputScript = BitcoinScript
                            .createSimpleOutBitoinScript(new BitcoinAddress(address));
                    //                Log.d("MyRemoteWallet", "MyRemoteWallet makeTransactionCustom changeAddress == null: " + address + "addressChangeAmount: " + addressChangeAmount);

                    TransactionOutput output = new TransactionOutput(getParams(), null, addressChangeAmount,
                            toOutputScript.getProgram());

                    tx.addOutput(output);
                }
            }
        } else {
            BigInteger addressChangeAmountSum = BigInteger.ZERO;
            for (Iterator<Entry<String, BigInteger>> iterator = addressTotalUnspentValues.entrySet()
                    .iterator(); iterator.hasNext();) {
                final Entry<String, BigInteger> entry = iterator.next();
                final String address = entry.getKey();
                final BigInteger addressTotalUnspentValue = entry.getValue();
                final BigInteger addressSendAmount = sendingAddresses.get(address);
                final BigInteger addressChangeAmount = addressTotalUnspentValue.subtract(addressSendAmount);
                addressChangeAmountSum = addressChangeAmountSum.add(addressChangeAmount);
            }

            if (addressChangeAmountSum.compareTo(BigInteger.ZERO) > 0) {
                //Add the output
                BitcoinScript toOutputScript = BitcoinScript
                        .createSimpleOutBitoinScript(new BitcoinAddress(changeAddress));

                TransactionOutput output = new TransactionOutput(getParams(), null,
                        addressChangeAmountSum.subtract(fee), toOutputScript.getProgram());
                //             Log.d("MyRemoteWallet", "MyRemoteWallet makeTransactionCustom changeAddress != null: " + changeAddress + "addressChangeAmount: " + output.getValue());
                tx.addOutput(output);
            }
        }

        long estimatedSize = tx.bitcoinSerialize().length + (114 * tx.getInputs().size());

        priority /= estimatedSize;

        return new Pair<Transaction, Long>(tx, priority);
    }

    public static List<MyTransactionOutPoint> getUnspentOutputPoints(String[] from) throws Exception {

        StringBuffer buffer = new StringBuffer(WebROOT + "unspent?active=");

        int ii = 0;
        for (String address : from) {
            buffer.append(address);

            if (ii < from.length - 1)
                buffer.append("|");

            ++ii;
        }

        List<MyTransactionOutPoint> outputs = new ArrayList<MyTransactionOutPoint>();

        String response = fetchURL(buffer.toString());

        Map<String, Object> root = (Map<String, Object>) JSONValue.parse(response);

        List<Map<String, Object>> outputsRoot = (List<Map<String, Object>>) root.get("unspent_outputs");

        for (Map<String, Object> outDict : outputsRoot) {

            byte[] hashBytes = Hex.decode((String) outDict.get("tx_hash"));

            ArrayUtils.reverse(hashBytes);

            Sha256Hash txHash = new Sha256Hash(hashBytes);

            int txOutputN = ((Number) outDict.get("tx_output_n")).intValue();
            BigInteger value = BigInteger.valueOf(((Number) outDict.get("value")).longValue());
            byte[] scriptBytes = Hex.decode((String) outDict.get("script"));
            int confirmations = ((Number) outDict.get("confirmations")).intValue();

            //Contrstuct the output
            MyTransactionOutPoint outPoint = new MyTransactionOutPoint(txHash, txOutputN, value, scriptBytes);

            outPoint.setConfirmations(confirmations);

            outputs.add(outPoint);
        }

        return outputs;
    }

    /**
     * Register this account/device pair within the server.
     * @throws Exception 
     *
     */
    public boolean registerNotifications(final String regId) throws Exception {
        if (_isNew)
            return false;

        StringBuilder args = new StringBuilder();

        args.append("guid=" + getGUID());
        args.append("&sharedKey=" + getSharedKey());
        args.append("&method=register-android-device");
        args.append("&payload=" + URLEncoder.encode(regId));
        args.append("&length=" + regId.length());

        String response = postURL(WebROOT + "wallet", args.toString());

        return response != null && response.length() > 0;
    }

    /** k
     * Unregister this account/device pair within the server.
     * @throws Exception 
     */
    public boolean unregisterNotifications(final String regId) throws Exception {
        if (_isNew)
            return false;

        StringBuilder args = new StringBuilder();

        args.append("guid=" + getGUID());
        args.append("&sharedKey=" + getSharedKey());
        args.append("&method=unregister-android-device");
        args.append("&payload=" + URLEncoder.encode(regId));
        args.append("&length=" + regId.length());

        String response = postURL(WebROOT + "wallet", args.toString());

        return response != null && response.length() > 0;
    }

    public boolean getIsEmailNotificationEnabled() {
        return notificationsTypeSet.contains(NotificationsTypeEmail);
    }

    public boolean getIsSMSNotificationEnabled() {
        return notificationsTypeSet.contains(NotificationsTypeSMS);
    }

    public String getSmsNumber() {
        return smsNumber;
    }

    public void setSmsNumber(String smsNumber) {
        this.smsNumber = smsNumber;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void getAccountInformation() throws Exception {
        Map<Object, Object> params = new HashMap<Object, Object>();
        params.put("method", "get-info");
        params.put("format", "json");

        String response = securePost(WebROOT + "wallet", params);
        JSONObject obj = (JSONObject) new JSONParser().parse(response);
        setEmail((String) obj.get("email"));
        setSmsNumber((String) obj.get("sms_number"));

        List<Long> notificationsType = (List<Long>) obj.get("notifications_type");
        notificationsTypeSet = new HashSet<String>();

        for (Long value : notificationsType)
            notificationsTypeSet.add(value.toString());
    }

    public String updateEmail(String email) throws Exception {
        if (email == null) {
            throw new Exception("Email cannot be null");
        }

        Map<Object, Object> params = new HashMap<Object, Object>();
        String length = Integer.toString(email.length());

        params.put("length", length);
        params.put("payload", email);
        params.put("method", "update-email");

        String response = securePost(WebROOT + "wallet", params);
        return response;
    }

    public String updateSMS(String smsNumber) throws Exception {
        if (smsNumber == null) {
            throw new Exception("smsNumber cannot be null");
        }

        Map<Object, Object> params = new HashMap<Object, Object>();
        String length = Integer.toString(smsNumber.length());

        params.put("length", length);
        params.put("payload", smsNumber);
        params.put("method", "update-sms");

        String response = securePost(WebROOT + "wallet", params);
        return response;
    }

    public String enableEmailNotification(boolean enable) throws Exception {
        if (enable)
            notificationsTypeSet.add(NotificationsTypeEmail);
        else
            notificationsTypeSet.remove(NotificationsTypeEmail);

        List<String> list = new ArrayList<String>(notificationsTypeSet);

        return updateNotificationsType(list.toArray(new String[list.size()]));
    }

    public String enableSMSNotification(boolean enable) throws Exception {
        if (enable)
            notificationsTypeSet.add(NotificationsTypeSMS);
        else
            notificationsTypeSet.remove(NotificationsTypeSMS);

        List<String> list = new ArrayList<String>(notificationsTypeSet);

        return updateNotificationsType(list.toArray(new String[list.size()]));
    }

    public boolean isEnableEmailNotification() {
        return notificationsTypeSet.contains(NotificationsTypeEmail);
    }

    public boolean isEnableSMSNotification() {
        return notificationsTypeSet.contains(NotificationsTypeSMS);
    }

    public String updateNotificationsType(boolean enableEmailNotification, boolean enableSMSNotification)
            throws Exception {
        if (enableSMSNotification)
            notificationsTypeSet.add(NotificationsTypeSMS);
        else
            notificationsTypeSet.remove(NotificationsTypeSMS);

        if (enableEmailNotification)
            notificationsTypeSet.add(NotificationsTypeEmail);
        else
            notificationsTypeSet.remove(NotificationsTypeEmail);

        List<String> list = new ArrayList<String>(notificationsTypeSet);
        return updateNotificationsType(list.toArray(new String[list.size()]));
    }

    private String updateNotificationsType(String[] values) throws Exception {

        String payload = StringUtils.join(values, "|");
        String length = Integer.toString(payload.length());

        Map<Object, Object> params = new HashMap<Object, Object>();
        params.put("length", length);
        params.put("payload", payload);
        params.put("method", "update-notifications-type");

        String response = securePost(WebROOT + "wallet", params);
        return response;
    }

    public String securePost(String url, Map<Object, Object> data) throws Exception {
        Map<Object, Object> params = new HashMap<Object, Object>(data);

        if (!data.containsKey("sharedKey")) {
            serverTimeOffset = 500; //TODO dont hard code serverTimeOffset

            String sharedKey = getSharedKey().toLowerCase();
            long now = new Date().getTime();

            long timestamp = (now - serverTimeOffset) / 10000;

            String text = sharedKey + Long.toString(timestamp);
            String SKHashHex = new String(
                    Hex.encode(MessageDigest.getInstance("SHA-256").digest(text.getBytes("UTF-8"))));
            int i = 0;
            String tSKUID = SKHashHex.substring(i, i += 8) + "-" + SKHashHex.substring(i, i += 4) + "-"
                    + SKHashHex.substring(i, i += 4) + "-" + SKHashHex.substring(i, i += 4) + "-"
                    + SKHashHex.substring(i, i += 12);

            params.put("sharedKey", tSKUID);
            params.put("sKTimestamp", Long.toString(timestamp));
            params.put("sKDebugHexHash", SKHashHex);
            params.put("sKDebugTimeOffset", Long.toString(serverTimeOffset));
            params.put("sKDebugOriginalClientTime", Long.toString(now));
            params.put("sKDebugOriginalSharedKey", sharedKey);

            if (!params.containsKey("guid"))
                params.put("guid", this.getGUID());

            if (!params.containsKey("format"))
                params.put("format", "plain");
        }

        String response = WalletUtils.postURLWithParams(url, params);
        return response;
    }

    public boolean updateRemoteLocalCurrency(String currency_code) throws Exception {
        if (_isNew)
            return false;

        localCurrencyCode = currency_code;

        StringBuilder args = new StringBuilder();

        args.append("guid=" + getGUID());
        args.append("&sharedKey=" + getSharedKey());
        args.append("&payload=" + currency_code);
        args.append("&length=" + currency_code.length());
        args.append("&method=update-currency");
        ;

        String response = postURL(WebROOT + "wallet", args.toString());

        return response != null;
    }

    /**
     * Get the tempoary paring encryption password
     * @throws Exception 
     *
     */
    public static String getPairingEncryptionPassword(final String guid) throws Exception {
        StringBuilder args = new StringBuilder();

        args.append("guid=" + guid);
        args.append("&method=pairing-encryption-password");

        return postURL(WebROOT + "wallet", args.toString());
    }

    public static BigInteger getAddressBalance(final String address) throws Exception {
        return new BigInteger(fetchURL(WebROOT + "q/addressbalance/" + address));
    }

    public static String getWalletManualPairing(final String guid) throws Exception {
        StringBuilder args = new StringBuilder();

        args.append("guid=" + guid);
        args.append("&method=pairing-encryption-password");

        String response = fetchURL(WebROOT + "wallet/" + guid + "?format=json&resend_code=false");

        JSONObject object = (JSONObject) new JSONParser().parse(response);

        String payload = (String) object.get("payload");
        if (payload == null || payload.length() == 0) {
            throw new Exception("Error Fetching Wallet Payload");
        }

        return payload;
    }

    public synchronized boolean remoteSave() throws Exception {
        return remoteSave(null);
    }

    public synchronized boolean remoteSave(String email) throws Exception {

        String payload = this.getPayload();

        String old_checksum = this._checksum;
        this._checksum = new String(
                Hex.encode(MessageDigest.getInstance("SHA-256").digest(payload.getBytes("UTF-8"))));

        String method = _isNew ? "insert" : "update";

        String urlEncodedPayload = URLEncoder.encode(payload);

        StringBuilder args = new StringBuilder();
        args.append("guid=");
        args.append(URLEncoder.encode(this.getGUID(), "utf-8"));
        args.append("&sharedKey=");
        args.append(URLEncoder.encode(this.getSharedKey(), "utf-8"));
        args.append("&payload=");
        args.append(urlEncodedPayload);
        args.append("&method=");
        args.append(method);
        args.append("&length=");
        args.append(payload.length());
        args.append("&checksum=");
        args.append(URLEncoder.encode(_checksum, "utf-8"));

        if (sync_pubkeys) {
            args.append("&active=");
            args.append(StringUtils.join(getActiveAddresses(), "|"));
        }

        if (email != null && email.length() > 0) {
            args.append("&email=");
            args.append(URLEncoder.encode(email, "utf-8"));
        }

        args.append("&device=");
        args.append("android");

        if (old_checksum != null && old_checksum.length() > 0) {
            args.append("&old_checksum=");
            args.append(old_checksum);
        }

        postURL(WebROOT + "wallet", args.toString());

        _isNew = false;

        return true;
    }

    public void remoteDownload() {

    }

    public String getChecksum() {
        return _checksum;
    }

    public synchronized String setPayload(JSONObject walletJSONObj) throws Exception {
        handleWalletPayloadObj(walletJSONObj);

        return setPayload(walletJSONObj.get("payload").toString());
    }

    public synchronized String setPayload(String payload) throws Exception {
        MyRemoteWallet tempWallet = new MyRemoteWallet(payload, temporyPassword);

        this.root = tempWallet.root;
        this.rootContainer = tempWallet.rootContainer;

        if (this.temporySecondPassword != null && !this.validateSecondPassword(temporySecondPassword)) {
            this.temporySecondPassword = null;
        }

        this._checksum = tempWallet._checksum;

        _isNew = false;

        return payload;
    }

    public static class NotModfiedException extends Exception {
        private static final long serialVersionUID = 1L;
    }

    public void handleWalletPayloadObj(JSONObject obj) {
        Map<String, Object> symbol_local = (Map<String, Object>) obj.get("symbol_local");

        boolean didUpdateCurrency = false;

        if (symbol_local != null && symbol_local.containsKey("code")) {
            String currencyCode = (String) symbol_local.get("code");
            Double currencyConversion = (Double) symbol_local.get("conversion");

            if (currencyConversion == null)
                currencyConversion = 0d;

            if (this.localCurrencyCode == null || !this.localCurrencyCode.equals(currencyCode)
                    || this.localCurrencyConversion != currencyConversion) {
                this.localCurrencyCode = currencyCode;
                this.localCurrencyConversion = currencyConversion;
                didUpdateCurrency = true;
            }
        }

        Map<String, Object> symbol_btc = (Map<String, Object>) obj.get("symbol_btc");
        if (symbol_btc != null && symbol_btc.containsKey("code")) {
            String currencyCode = (String) symbol_local.get("code");
            Double currencyConversion = (Double) symbol_local.get("conversion");

            if (currencyConversion == null)
                currencyConversion = 0d;

            if (this.btcCurrencyCode == null || !this.btcCurrencyCode.equals(currencyCode)
                    || this.btcCurrencyConversion != currencyConversion) {
                this.btcCurrencyCode = currencyCode;
                this.btcCurrencyConversion = currencyConversion;
                //didUpdateCurrency = true;
            }
        }

        if (didUpdateCurrency) {
            EventListeners.invokeCurrencyDidChange();
        }

        if (obj.containsKey("sync_pubkeys")) {
            sync_pubkeys = Boolean.valueOf(obj.get("sync_pubkeys").toString());
        }
    }

    public static JSONObject getWalletPayload(String guid, String sharedKey, String checkSumString)
            throws Exception {
        String response = postURL(WebROOT + "wallet", "method=wallet.aes.json&guid=" + guid + "&sharedKey="
                + sharedKey + "&checksum=" + checkSumString + "&format=json");

        if (response == null) {
            throw new Exception("Error downloading wallet");
        }

        JSONObject obj = (JSONObject) new JSONParser().parse(response);

        String payload = obj.get("payload").toString();

        if (payload == null) {
            throw new Exception("Error downloading wallet");
        }

        if (payload.equals("Not modified")) {
            throw new NotModfiedException();
        }

        return obj;
    }

    public static JSONObject getWalletPayload(String guid, String sharedKey) throws Exception {
        String response = postURL(WebROOT + "wallet",
                "method=wallet.aes.json&guid=" + guid + "&sharedKey=" + sharedKey + "&format=json");

        if (response == null) {
            throw new Exception("Error downloading wallet");
        }

        JSONObject obj = (JSONObject) new JSONParser().parse(response);

        String payload = obj.get("payload").toString();

        if (payload == null) {
            throw new Exception("Error downloading wallet");
        }

        return obj;
    }

    public List<Pair<String, String>> getLabelList() {
        List<Pair<String, String>> array = new ArrayList<Pair<String, String>>();

        Map<String, String> labelMap = this.getLabelMap();

        synchronized (labelMap) {
            for (Map.Entry<String, String> entry : labelMap.entrySet()) {
                array.add(new Pair<String, String>(entry.getValue(), entry.getKey()) {
                    public String toString() {
                        return first.toString();
                    }
                });
            }
        }

        return array;
    }

    public String getToAddress(String inputAddress) {
        final String userEntered = inputAddress;
        if (userEntered.length() > 0) {
            try {
                new Address(Constants.NETWORK_PARAMETERS, userEntered);

                return userEntered;
            } catch (AddressFormatException e) {
                List<Pair<String, String>> labels = this.getLabelList();

                for (Pair<String, String> label : labels) {
                    if (label.first.toLowerCase(Locale.ENGLISH).equals(userEntered.toLowerCase(Locale.ENGLISH))) {
                        try {
                            new Address(Constants.NETWORK_PARAMETERS, label.second);

                            return label.second;
                        } catch (AddressFormatException e1) {
                        }
                    }
                }
            }
        }

        return null;
    }
}