de.schildbach.wallet.ui.send.RequestWalletBalanceTask.java Source code

Java tutorial

Introduction

Here is the source code for de.schildbach.wallet.ui.send.RequestWalletBalanceTask.java

Source

/*
 * Copyright 2014-2015 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 de.schildbach.wallet.ui.send;

import static com.google.common.base.Preconditions.checkState;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence.ConfidenceType;
import org.bitcoinj.core.TransactionOutput;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.squareup.okhttp.CacheControl;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import de.schildbach.wallet.Constants;
import de.schildbach.wallet_test.R;

import android.os.Handler;
import android.os.Looper;

/**
 * @author Andreas Schildbach
 */
public final class RequestWalletBalanceTask {
    private final Handler backgroundHandler;
    private final Handler callbackHandler;
    private final ResultCallback resultCallback;
    @Nullable
    private final String userAgent;

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

    public interface ResultCallback {
        void onResult(Collection<Transaction> transactions);

        void onFail(int messageResId, Object... messageArgs);
    }

    public RequestWalletBalanceTask(final Handler backgroundHandler, final ResultCallback resultCallback,
            @Nullable final String userAgent) {
        this.backgroundHandler = backgroundHandler;
        this.callbackHandler = new Handler(Looper.myLooper());
        this.resultCallback = resultCallback;
        this.userAgent = userAgent;
    }

    public void requestWalletBalance(final Address... addresses) {
        backgroundHandler.post(new Runnable() {
            @Override
            public void run() {
                org.bitcoinj.core.Context.propagate(Constants.CONTEXT);

                final HttpUrl.Builder url = HttpUrl.parse(Constants.BITEASY_API_URL).newBuilder();
                url.addPathSegment("outputs");
                url.addQueryParameter("per_page", "MAX");
                url.addQueryParameter("operator", "AND");
                url.addQueryParameter("spent_state", "UNSPENT");
                for (final Address address : addresses)
                    url.addQueryParameter("address[]", address.toBase58());

                log.debug("trying to request wallet balance from {}", url.build());

                final Request.Builder request = new Request.Builder();
                request.url(url.build());
                request.cacheControl(new CacheControl.Builder().noCache().build());
                request.header("Accept-Charset", "utf-8");
                if (userAgent != null)
                    request.header("User-Agent", userAgent);

                final Call call = Constants.HTTP_CLIENT.newCall(request.build());
                try {
                    final Response response = call.execute();
                    if (response.isSuccessful()) {
                        final String content = response.body().string();
                        final JSONObject json = new JSONObject(content);

                        final int status = json.getInt("status");
                        if (status != 200)
                            throw new IOException("api status " + status + " when fetching unspent outputs");

                        final JSONObject jsonData = json.getJSONObject("data");

                        final JSONObject jsonPagination = jsonData.getJSONObject("pagination");

                        if (!"false".equals(jsonPagination.getString("next_page")))
                            throw new IOException("result set too big");

                        final JSONArray jsonOutputs = jsonData.getJSONArray("outputs");

                        final Map<Sha256Hash, Transaction> transactions = new HashMap<Sha256Hash, Transaction>(
                                jsonOutputs.length());

                        for (int i = 0; i < jsonOutputs.length(); i++) {
                            final JSONObject jsonOutput = jsonOutputs.getJSONObject(i);

                            final Sha256Hash uxtoHash = Sha256Hash.wrap(jsonOutput.getString("transaction_hash"));
                            final int uxtoIndex = jsonOutput.getInt("transaction_index");
                            final byte[] uxtoScriptBytes = Constants.HEX
                                    .decode(jsonOutput.getString("script_pub_key"));
                            final Coin uxtoValue = Coin.valueOf(Long.parseLong(jsonOutput.getString("value")));

                            Transaction tx = transactions.get(uxtoHash);
                            if (tx == null) {
                                tx = new FakeTransaction(Constants.NETWORK_PARAMETERS, uxtoHash);
                                tx.getConfidence().setConfidenceType(ConfidenceType.BUILDING);
                                transactions.put(uxtoHash, tx);
                            }

                            final TransactionOutput output = new TransactionOutput(Constants.NETWORK_PARAMETERS, tx,
                                    uxtoValue, uxtoScriptBytes);

                            if (tx.getOutputs().size() > uxtoIndex) {
                                // Work around not being able to replace outputs on transactions
                                final List<TransactionOutput> outputs = new ArrayList<TransactionOutput>(
                                        tx.getOutputs());
                                final TransactionOutput dummy = outputs.set(uxtoIndex, output);
                                checkState(dummy.getValue().equals(Coin.NEGATIVE_SATOSHI),
                                        "Index %s must be dummy output", uxtoIndex);
                                // Remove and re-add all outputs
                                tx.clearOutputs();
                                for (final TransactionOutput o : outputs)
                                    tx.addOutput(o);
                            } else {
                                // Fill with dummies as needed
                                while (tx.getOutputs().size() < uxtoIndex)
                                    tx.addOutput(new TransactionOutput(Constants.NETWORK_PARAMETERS, tx,
                                            Coin.NEGATIVE_SATOSHI, new byte[] {}));

                                // Add the real output
                                tx.addOutput(output);
                            }
                        }

                        log.info("fetched unspent outputs from {}", url);

                        onResult(transactions.values());
                    } else {
                        final int responseCode = response.code();
                        final String responseMessage = response.message();

                        log.info("got http error '{}: {}' from {}", responseCode, responseMessage, url);
                        onFail(R.string.error_http, responseCode, responseMessage);
                    }
                } catch (final JSONException x) {
                    log.info("problem parsing json from " + url, x);

                    onFail(R.string.error_parse, x.getMessage());
                } catch (final IOException x) {
                    log.info("problem querying unspent outputs from " + url, x);

                    onFail(R.string.error_io, x.getMessage());
                }
            }
        });
    }

    protected void onResult(final Collection<Transaction> transactions) {
        callbackHandler.post(new Runnable() {
            @Override
            public void run() {
                resultCallback.onResult(transactions);
            }
        });
    }

    protected void onFail(final int messageResId, final Object... messageArgs) {
        callbackHandler.post(new Runnable() {
            @Override
            public void run() {
                resultCallback.onFail(messageResId, messageArgs);
            }
        });
    }

    private static class FakeTransaction extends Transaction {
        private final Sha256Hash hash;

        public FakeTransaction(final NetworkParameters params, final Sha256Hash hash) {
            super(params);
            this.hash = hash;
        }

        @Override
        public Sha256Hash getHash() {
            return hash;
        }
    }
}