diff --git a/README.md b/README.md index b57d35fd81..e19fda353c 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,7 @@ Authors - Dmitry Murashchik - Constantin Vennekel - Leo Wandersleb + - Daniel Krawisz Credits ======= diff --git a/public/bitlib/src/main/java/com/megiontechnologies/Bitcoins.java b/public/bitlib/src/main/java/com/megiontechnologies/Bitcoins.java index d8f766c109..99959ced1e 100644 --- a/public/bitlib/src/main/java/com/megiontechnologies/Bitcoins.java +++ b/public/bitlib/src/main/java/com/megiontechnologies/Bitcoins.java @@ -123,9 +123,9 @@ public BigDecimal multiply(BigDecimal pricePerBtc) { return toBigDecimal().multiply(BigDecimal.valueOf(satoshis)); } - protected Bitcoins parse(String input) { - return Bitcoins.valueOf(input); - } + protected Bitcoins parse(String input) { + return Bitcoins.valueOf(input); + } @Override public String toString() { @@ -201,4 +201,4 @@ private static long roundToSignificantFigures(long num, int n) { return ret; } -} +} \ No newline at end of file diff --git a/public/bitlib/src/main/java/com/mrd/bitlib/StandardTransactionBuilder.java b/public/bitlib/src/main/java/com/mrd/bitlib/StandardTransactionBuilder.java index 0dc7262148..24d573ee92 100644 --- a/public/bitlib/src/main/java/com/mrd/bitlib/StandardTransactionBuilder.java +++ b/public/bitlib/src/main/java/com/mrd/bitlib/StandardTransactionBuilder.java @@ -111,8 +111,8 @@ public UnspentTransactionOutput[] getFundingOutputs() { return _funding; } - protected UnsignedTransaction(List outputs, List funding, - IPublicKeyRing keyRing, NetworkParameters network) { + public UnsignedTransaction(List outputs, List funding, + IPublicKeyRing keyRing, NetworkParameters network) { _network = network; _outputs = outputs.toArray(new TransactionOutput[]{}); _funding = funding.toArray(new UnspentTransactionOutput[]{}); @@ -251,7 +251,7 @@ public void addOutputs(OutputList outputs) throws OutputTooSmallException { } } - private static TransactionOutput createOutput(Address sendTo, long value, NetworkParameters network) { + public static TransactionOutput createOutput(Address sendTo, long value, NetworkParameters network) { ScriptOutput script; if (sendTo.isMultisig(network)) { script = new ScriptOutputP2SH(sendTo.getTypeSpecificBytes()); @@ -299,11 +299,11 @@ public UnsignedTransaction createUnsignedTransaction(Collection unspent = new LinkedList(inventory); - OldOutputs oldOutputs = new OldOutputs(minerFeeToUse, unspent); - long fee = oldOutputs.getFee(); - long outputSum = oldOutputs.getOutputSum(); - //todo extract coinselector interface with 2 implementations, oldest and pruning - List funding = pruneRedundantOutputs(oldOutputs.getAllFunding(), fee + outputSum); + OldestOutputsFirst oldestOutputsFirst = new OldestOutputsFirst(minerFeeToUse, unspent); + long fee = oldestOutputsFirst.getFee(); + long outputSum = oldestOutputsFirst.getOutputSum(); + List funding = pruneRedundantOutputs(oldestOutputsFirst.getAllNeededFundings(), fee + outputSum); + // the number of inputs might have changed - recalculate the fee fee = estimateFee(funding.size(), _outputs.size() + 1, minerFeeToUse); long found = 0; @@ -501,26 +501,36 @@ private static int estimateTransactionSize(int inputs, int outputs) { return estimate; } - private static long estimateFee(int inputs, int outputs, long minerFeeToUse) { + /** + * Returns the estimate needed fee in satoshis for a default P2PKH transaction with a certain number + * of inputs and outputs and the specified per-kB-fee + * + * @param inputs number of inputs + * @param outputs number of outputs + * @param minerFeePerKb miner fee in satoshis per kB + **/ + public static long estimateFee(int inputs, int outputs, long minerFeePerKb) { + // fee is based on the size of the transaction, we have to pay for + // every 1000 bytes + int txSize = estimateTransactionSize(inputs, outputs); + long requiredFee = (long) (((float) txSize / 1000.0) * minerFeePerKb); + // check if our estimation leads to a small fee that's below the default bitcoind-MIN_RELAY_FEE // if so, use the MIN_RELAY_FEE - if (minerFeeToUse < MIN_RELAY_FEE) { - minerFeeToUse = MIN_RELAY_FEE; + if (requiredFee < MIN_RELAY_FEE) { + requiredFee = MIN_RELAY_FEE; } - // fee is based on the size of the transaction, we have to pay for - // every 1000 bytes - int txSize = estimateTransactionSize(inputs, outputs); - long requiredFee = (long) (((float) txSize / 1000.0) * minerFeeToUse); return requiredFee; } - private class OldOutputs { + // todo: generalize this into a interface and provide different coin-selectors + private class OldestOutputsFirst { private List allFunding; private long fee; private long outputSum; - public OldOutputs(long minerFeeToUse, List unspent) throws InsufficientFundsException { + public OldestOutputsFirst(long minerFeeToUse, List unspent) throws InsufficientFundsException { // Find the funding for this transaction allFunding = new LinkedList(); fee = minerFeeToUse; @@ -540,7 +550,7 @@ public OldOutputs(long minerFeeToUse, List unspent) th } } - public List getAllFunding() { + public List getAllNeededFundings() { return allFunding; } diff --git a/public/bitlib/src/main/java/com/mrd/bitlib/crypto/PrivateKeyRing.java b/public/bitlib/src/main/java/com/mrd/bitlib/crypto/PrivateKeyRing.java index 788aa0903d..03eae45f84 100644 --- a/public/bitlib/src/main/java/com/mrd/bitlib/crypto/PrivateKeyRing.java +++ b/public/bitlib/src/main/java/com/mrd/bitlib/crypto/PrivateKeyRing.java @@ -50,6 +50,7 @@ public void addPrivateKey(PrivateKey privateKey, PublicKey publicKey, Address ad /** * Find a Bitcoin signer by public key */ + @Override public BitcoinSigner findSignerByPublicKey(PublicKey publicKey) { return _privateKeys.get(publicKey); } diff --git a/public/bitlib/src/main/java/com/mrd/bitlib/model/OutPoint.java b/public/bitlib/src/main/java/com/mrd/bitlib/model/OutPoint.java index 1cc44abe7d..0aa4368fa6 100644 --- a/public/bitlib/src/main/java/com/mrd/bitlib/model/OutPoint.java +++ b/public/bitlib/src/main/java/com/mrd/bitlib/model/OutPoint.java @@ -24,6 +24,7 @@ import com.mrd.bitlib.util.Sha256Hash; import com.mrd.bitlib.util.ByteReader.InsufficientBytesException; +// OutPoint denotes a particular output of a given transaction. public class OutPoint implements Serializable { private static final long serialVersionUID = 1L; diff --git a/public/bitlib/src/main/java/com/mrd/bitlib/model/Transaction.java b/public/bitlib/src/main/java/com/mrd/bitlib/model/Transaction.java index 00acc4a6d7..28c459ca58 100644 --- a/public/bitlib/src/main/java/com/mrd/bitlib/model/Transaction.java +++ b/public/bitlib/src/main/java/com/mrd/bitlib/model/Transaction.java @@ -27,6 +27,14 @@ import java.io.Serializable; +/** + * Transaction represents a raw Bitcoin transaction. In other words, it contains only the information found in the + * byte string representing a Bitcoin transaction. It contains no contextual information, such as the height + * of the transaction in the block chain or the outputs that its inputs redeem. + * + * Implements Serializable and is inserted directly in and out of the database. Therefore it cannot be changed + * without messing with the database. + */ public class Transaction implements Serializable { private static final long serialVersionUID = 1L; public static class TransactionParsingException extends Exception { diff --git a/public/btchip/src/main/java/com/btchip/BTChipDongle.java b/public/btchip/src/main/java/com/btchip/BTChipDongle.java index 373765d0d7..510d9cd384 100644 --- a/public/btchip/src/main/java/com/btchip/BTChipDongle.java +++ b/public/btchip/src/main/java/com/btchip/BTChipDongle.java @@ -20,11 +20,7 @@ package com.btchip; import com.btchip.comm.BTChipTransport; -import com.btchip.utils.BIP32Utils; -import com.btchip.utils.BufferUtils; -import com.btchip.utils.CoinFormatUtils; -import com.btchip.utils.Dump; -import com.btchip.utils.VarintUtils; +import com.btchip.utils.*; import java.io.ByteArrayOutputStream; import java.util.Arrays; @@ -366,7 +362,7 @@ private byte[] exchange(byte[] apdu) throws BTChipException { throw new BTChipException("Truncated response"); } lastSW = ((int) (response[response.length - 2] & 0xff) << 8) | - (int) (response[response.length - 1] & 0xff); + (int) (response[response.length - 1] & 0xff); byte[] result = new byte[response.length - 2]; System.arraycopy(response, 0, result, 0, response.length - 2); return result; diff --git a/public/coinapult/build.gradle b/public/coinapult/build.gradle index f0a8bdcc53..4571b309b5 100644 --- a/public/coinapult/build.gradle +++ b/public/coinapult/build.gradle @@ -18,7 +18,10 @@ dependencies { compile 'com.fasterxml.jackson.core:jackson-core:2.1.0' compile 'com.fasterxml.jackson.core:jackson-annotations:2.1.0' compile 'com.fasterxml.jackson.core:jackson-databind:2.1.0' - compile 'com.google.http-client:google-http-client-jackson2:1.19.0' + compile ('com.google.http-client:google-http-client-jackson2:1.19.0') { + // jackson comes with an older version of findbugs than required elsewhere + exclude group: 'com.google.code.findbugs' + } compile 'com.madgag.spongycastle:core:1.51.0.0' compile 'com.madgag.spongycastle:prov:1.51.0.0' diff --git a/public/lt-api/src/main/java/com/mycelium/lt/api/model/ChatEntry.java b/public/lt-api/src/main/java/com/mycelium/lt/api/model/ChatEntry.java index f27b5bc58f..dda905b68d 100644 --- a/public/lt-api/src/main/java/com/mycelium/lt/api/model/ChatEntry.java +++ b/public/lt-api/src/main/java/com/mycelium/lt/api/model/ChatEntry.java @@ -42,6 +42,7 @@ public class ChatEntry implements Serializable { public static final int EVENT_SUBTYPE_OWNER_STOPPED = 12; public static final int EVENT_SUBTYPE_PEER_STOPPED = 13; public static final int EVENT_SUBTYPE_TRADE_LOCATION = 14; + public static final int EVENT_SUBTYPE_CASH_ONLY_WARNING = 15; @JsonProperty public long time; diff --git a/public/mbw/build.gradle b/public/mbw/build.gradle index 3094f0c460..59526cd045 100644 --- a/public/mbw/build.gradle +++ b/public/mbw/build.gradle @@ -39,6 +39,13 @@ dependencies { compile 'com.squareup.okhttp:okhttp:2.2.0' compile 'com.android.support:multidex:1.0.1' + compile ('com.google.code.findbugs:annotations:3.0.1') { + //transitive = false + //exclude group: 'javax/annotation/**' + // with these excludes stuff works. not sure which can be removed. + exclude module: 'jsr305' + exclude module: 'jcip-annotations' + } } def commonDebugKeystore = file("../../keystore_debug") @@ -64,8 +71,8 @@ android { buildToolsVersion androidSdkBuildVersion defaultConfig { - versionCode 25700 - versionName '2.5.7' + versionCode 25805 + versionName '2.5.8' multiDexEnabled true } diff --git a/public/mbw/res-sources/licenses.md b/public/mbw/res-sources/licenses.md new file mode 100644 index 0000000000..e3c51eef0c --- /dev/null +++ b/public/mbw/res-sources/licenses.md @@ -0,0 +1,5 @@ +Files used for the localTraderLocalOnly.xcf: + +* thumbs-up: [public domain](https://pixabay.com/en/hand-thumb-sign-ok-yes-positive-311121/) +* guys: [public domain](https://openclipart.org/detail/201468/men-shacking-hand-4-differents-versions) +* earth: [public domanin](https://openclipart.org/detail/3320/earth) diff --git a/public/mbw/res-sources/localTraderLocalOnly.png b/public/mbw/res-sources/localTraderLocalOnly.png new file mode 100644 index 0000000000..bbe6910903 Binary files /dev/null and b/public/mbw/res-sources/localTraderLocalOnly.png differ diff --git a/public/mbw/res-sources/localTraderLocalOnly.xcf b/public/mbw/res-sources/localTraderLocalOnly.xcf new file mode 100644 index 0000000000..4a9fe2504b Binary files /dev/null and b/public/mbw/res-sources/localTraderLocalOnly.xcf differ diff --git a/public/mbw/res-sources/updateDrawables.sh b/public/mbw/res-sources/updateDrawables.sh new file mode 100755 index 0000000000..b750228772 --- /dev/null +++ b/public/mbw/res-sources/updateDrawables.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +sizeNames=(ldpi mdpi hdpi xhdpi xxhdpi) +sizes=(360 480 720 960 1800) +fileIn=localTraderLocalOnly.png +fileOut=lt_local_only_warning.png + +for ((n=0; n <= 4; n++)) +do + fOut="../src/main/res/drawable-"${sizeNames[n]}"/"$fileOut + convert -background none -resize ${sizes[n]}"x" $fileIn - | \ + pngquant --force 64 > $fOut +done diff --git a/public/mbw/src/main/AndroidManifest.xml b/public/mbw/src/main/AndroidManifest.xml index b57455f63c..7379416a06 100644 --- a/public/mbw/src/main/AndroidManifest.xml +++ b/public/mbw/src/main/AndroidManifest.xml @@ -59,6 +59,7 @@ + diff --git a/public/mbw/src/main/java/com/mycelium/wallet/AddressBookManager.java b/public/mbw/src/main/java/com/mycelium/wallet/AddressBookManager.java index c88e7a8065..e9609902ce 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/AddressBookManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/AddressBookManager.java @@ -269,9 +269,10 @@ private static String decode(String value) { sb.append('('); } else if (c == ')') { sb.append(')'); - } else { - // decode error, ignore this character } + // else { + // decode error, ignore this character + // } } else { if (c == '/') { slash = true; diff --git a/public/mbw/src/main/java/com/mycelium/wallet/BitidKeyDerivation.java b/public/mbw/src/main/java/com/mycelium/wallet/BitIdKeyDerivation.java similarity index 98% rename from public/mbw/src/main/java/com/mycelium/wallet/BitidKeyDerivation.java rename to public/mbw/src/main/java/com/mycelium/wallet/BitIdKeyDerivation.java index dcc4666a6f..42456ec5ef 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/BitidKeyDerivation.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/BitIdKeyDerivation.java @@ -39,6 +39,6 @@ /** * Created by Andreas on 20.06.2015. */ -public interface BitidKeyDerivation { +public interface BitIdKeyDerivation { InMemoryPrivateKey deriveKey(int accountIndex, String site); } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/BitcoinUri.java b/public/mbw/src/main/java/com/mycelium/wallet/BitcoinUri.java index ff641ae837..841cb21aa6 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/BitcoinUri.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/BitcoinUri.java @@ -34,15 +34,14 @@ package com.mycelium.wallet; -import java.io.Serializable; -import java.math.BigDecimal; - import android.net.Uri; - import com.google.common.base.Optional; import com.mrd.bitlib.model.Address; import com.mrd.bitlib.model.NetworkParameters; +import java.io.Serializable; +import java.math.BigDecimal; + /** * This is a crude implementation of a Bitcoin URI, but for now it works for our * purpose. @@ -56,8 +55,8 @@ public class BitcoinUri implements Serializable { public final String callbackURL; // returns a BitcoinUriWithAddress if address != null - public static BitcoinUri from(Address address, Long amount, String label, String callbackURL){ - if (address != null){ + public static BitcoinUri from(Address address, Long amount, String label, String callbackURL) { + if (address != null) { return new BitcoinUriWithAddress(address, amount, label, callbackURL); } else { return new BitcoinUri(null, amount, label, callbackURL); @@ -108,7 +107,12 @@ public static Optional parse(String uri, NetworkParameters network) } // Label + // Bip21 defines "?label" and "?message" - lets try "label" first and if it does not + // exist, lets use "message" String label = u.getQueryParameter("label"); + if (label == null) { + label = u.getQueryParameter("message"); + } // Payment Uri String paymentUri = u.getQueryParameter("r"); @@ -125,15 +129,21 @@ public static BitcoinUri fromAddress(Address address) { return new BitcoinUri(address, null, null); } - public String toString() { - Uri.Builder builder = new Uri.Builder() - .scheme("bitcoin") - .authority(address == null ? "" : address.toString()); - if (amount != null) builder.appendQueryParameter("amount", amount.toString()); - if (label != null) builder.appendQueryParameter("label", label); - if (callbackURL != null) builder.appendQueryParameter("r", callbackURL); + public String toString() { + Uri.Builder builder = new Uri.Builder() + .scheme("bitcoin") + .authority(address == null ? "" : address.toString()); + if (amount != null) { + builder.appendQueryParameter("amount", amount.toString()); + } + if (label != null) { + builder.appendQueryParameter("label", label); + } + if (callbackURL != null) { + builder.appendQueryParameter("r", callbackURL); + } //todo: this can probably be solved nicer with some opaque flags or something - return builder.toString().replace("/", ""); - } + return builder.toString().replace("/", ""); + } } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/ClearPinDialog.java b/public/mbw/src/main/java/com/mycelium/wallet/ClearPinDialog.java index 4079917d52..c3155dcd54 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/ClearPinDialog.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/ClearPinDialog.java @@ -86,7 +86,7 @@ private View.OnClickListener startResetListener(final Context context, final Mbw return new View.OnClickListener() { @Override public void onClick(View v) { - AlertDialog d = new AlertDialog.Builder(ClearPinDialog.this.getContext()) + new AlertDialog.Builder(ClearPinDialog.this.getContext()) .setPositiveButton(context.getString(R.string.yes), new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { diff --git a/public/mbw/src/main/java/com/mycelium/wallet/Constants.java b/public/mbw/src/main/java/com/mycelium/wallet/Constants.java index b3a034d4bc..c480445ff7 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/Constants.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/Constants.java @@ -131,6 +131,4 @@ public class Constants { public static final int MIN_PIN_BLOCKHEIGHT_AGE_RESET_PIN = 7 * BITCOIN_BLOCKS_PER_DAY; // Force user to read the warnings about additional backups public static final int WAIT_SECONDS_BEFORE_ADDITIONAL_BACKUP = 60; - public static final BigDecimal COINAPULT_MINIMUM_AMOUNT = BigDecimal.ONE; - } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/CurrencySwitcher.java b/public/mbw/src/main/java/com/mycelium/wallet/CurrencySwitcher.java index f2601c3560..369f1dfb93 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/CurrencySwitcher.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/CurrencySwitcher.java @@ -34,20 +34,18 @@ package com.mycelium.wallet; -import android.content.Context; +import com.google.api.client.util.Lists; import com.google.common.base.Strings; import com.mrd.bitlib.util.CoinUtil; import com.mycelium.wapi.model.ExchangeRate; +import com.mycelium.wapi.wallet.currency.CurrencySum; +import com.mycelium.wapi.wallet.currency.CurrencyValue; -import java.util.ArrayList; -import java.util.List; +import java.util.*; public class CurrencySwitcher { - public static final String BTC="BTC"; - private final ExchangeRateManager exchangeRateManager; - private Context applicationContext; private List fiatCurrencies; private CoinUtil.Denomination bitcoinDenomination; @@ -57,69 +55,71 @@ public class CurrencySwitcher { // the last shown currency (usually same as fiat currency, but in some spots we cycle through all currencies including Bitcoin) private String currentCurrency; - public CurrencySwitcher(final Context applicationContext, final ExchangeRateManager exchangeRateManager, final List fiatCurrencies, final String currentCurrency, final CoinUtil.Denomination bitcoinDenomination) { - this.applicationContext = applicationContext; + public CurrencySwitcher(final ExchangeRateManager exchangeRateManager, final Set fiatCurrencies, final String currentCurrency, final CoinUtil.Denomination bitcoinDenomination) { this.exchangeRateManager = exchangeRateManager; - this.fiatCurrencies = fiatCurrencies; + ArrayList currencies = Lists.newArrayList(fiatCurrencies); + Collections.sort(currencies); + this.fiatCurrencies = currencies; this.bitcoinDenomination = bitcoinDenomination; this.currentCurrency = currentCurrency; // if BTC is selected or current currency is not in list of available currencies (e.g. after update) // select a default one or none - if (currentCurrency.equals(BTC) || !fiatCurrencies.contains(currentCurrency) ){ - if (fiatCurrencies.size()==0){ + if (currentCurrency.equals(CurrencyValue.BTC) || !fiatCurrencies.contains(currentCurrency)) { + if (fiatCurrencies.size() == 0) { this.currentFiatCurrency = ""; // no fiat currency selected - }else { - this.currentFiatCurrency = fiatCurrencies.get(0); + } else { + this.currentFiatCurrency = currencies.get(0); } - }else { + } else { this.currentFiatCurrency = currentCurrency; } } - public void setCurrency(final String fiatCurrency){ - if (!fiatCurrency.equals(CurrencySwitcher.BTC)) { - currentFiatCurrency = fiatCurrency; + public void setCurrency(final String setToCurrency) { + if (!setToCurrency.equals(CurrencyValue.BTC)) { + currentFiatCurrency = setToCurrency; } - currentCurrency = fiatCurrency; + currentCurrency = setToCurrency; } - public String getCurrentFiatCurrency(){ + public String getCurrentFiatCurrency() { return currentFiatCurrency; } - public String getCurrentCurrency(){ + public String getCurrentCurrency() { return currentCurrency; } - public String getCurrentCurrencyIncludingDenomination(){ - - if (currentCurrency.equals(BTC)){ + public String getCurrentCurrencyIncludingDenomination() { + if (currentCurrency.equals(CurrencyValue.BTC)) { // use denomination only for btc return bitcoinDenomination.getUnicodeName(); - }else { + } else { return currentCurrency; } } - - public List getCurrencyList() { //make a copy to prevent others from changing our internal list return new ArrayList(fiatCurrencies); } - public int getFiatCurrenciesCount(){ + public int getFiatCurrenciesCount() { return fiatCurrencies.size(); } - public int getCurrenciesCount(){ + public int getCurrenciesCount() { return fiatCurrencies.size() + 1; // BTC is always available } - public void setCurrencyList(final List currencies) { + public void setCurrencyList(final Set fiatCurrencies) { + // convert the set to a list and sort it + ArrayList currencies = Lists.newArrayList(fiatCurrencies); + Collections.sort(currencies); + //if we de-selected our current active currency, we switch it if (!currencies.contains(currentFiatCurrency)) { if (currencies.isEmpty()) { @@ -130,7 +130,7 @@ public void setCurrencyList(final List currencies) { } } //copy to prevent changes by caller - fiatCurrencies = new ArrayList(currencies); + this.fiatCurrencies = new ArrayList(currencies); } public String getNextCurrency(boolean includeBitcoin) { @@ -146,15 +146,15 @@ public String getNextCurrency(boolean includeBitcoin) { if (index >= currencies.size()) { // we are at the end of the fiat-list. return BTC if we should include Bitcoin, otherwise wrap around - if (includeBitcoin){ + if (includeBitcoin) { // only set currentCurrency, but leave currentFiat currency as it was - currentCurrency = CurrencySwitcher.BTC; - }else { + currentCurrency = CurrencyValue.BTC; + } else { index -= currencies.size(); //wrap around currentCurrency = currencies.get(index); currentFiatCurrency = currentCurrency; } - }else{ + } else { currentCurrency = currencies.get(index); currentFiatCurrency = currentCurrency; } @@ -181,7 +181,7 @@ public String getBtcValueString(long satoshis, boolean includeUnit) { String valueString = CoinUtil.valueString(satoshis, d, true); if (includeUnit) { return valueString + " " + d.getUnicodeName(); - }else{ + } else { return valueString; } } @@ -191,14 +191,14 @@ public String getBtcValueString(long satoshis, boolean includeUnit, int precisio String valueString = CoinUtil.valueString(satoshis, d, precision); if (includeUnit) { return valueString + " " + d.getUnicodeName(); - }else{ + } else { return valueString; } } - public boolean isFiatExchangeRateAvailable(){ - if (Strings.isNullOrEmpty(currentFiatCurrency)){ + public boolean isFiatExchangeRateAvailable() { + if (Strings.isNullOrEmpty(currentFiatCurrency)) { // we dont even have a fiat currency... return false; } @@ -208,75 +208,91 @@ public boolean isFiatExchangeRateAvailable(){ return rate != null && rate.price != null; } - public String getFormattedFiatValue(long satoshis, boolean includeCurrencyCode){ + public String getFormattedFiatValue(CurrencyValue value, boolean includeCurrencyCode) { + if (value == null){ + return ""; + } - if (Strings.isNullOrEmpty(currentFiatCurrency)){ + CurrencyValue targetCurrency = getAsFiatValue(value); + + if (Strings.isNullOrEmpty(currentFiatCurrency)) { return ""; } - ExchangeRate rate = exchangeRateManager.getExchangeRate(getCurrentFiatCurrency()); - if (rate == null){ + if (targetCurrency == null) { //todo return ""; - }else { - if (rate.price != null) { - String fiatValueAsString = Utils.getFiatValueAsString(satoshis, rate.price); - if (includeCurrencyCode) { - return fiatValueAsString + " " + currentFiatCurrency; - } else { - return fiatValueAsString; - } + } else { + if (includeCurrencyCode) { + return Utils.getFormattedValueWithUnit(targetCurrency, getBitcoinDenomination()); } else { - // no valid price available - return ""; + return Utils.getFormattedValue(targetCurrency, getBitcoinDenomination()); } } } - public String getFormattedFiatValue(long satoshis, boolean includeCurrencyCode, int precision){ - - if (Strings.isNullOrEmpty(currentFiatCurrency)){ + public String getFormattedFiatValue(CurrencyValue value, boolean includeCurrencyCode, int precision) { + if (Strings.isNullOrEmpty(currentFiatCurrency)) { return ""; } - ExchangeRate rate = exchangeRateManager.getExchangeRate(getCurrentFiatCurrency()); - if (rate == null){ - //todo + CurrencyValue targetCurrency = getAsFiatValue(value); + + if (targetCurrency == null) { return ""; - }else { - if (rate.price != null) { - String fiatValueAsString = Utils.getFiatValueAsString(satoshis, rate.price, precision); - if (includeCurrencyCode) { - return fiatValueAsString + " " + currentFiatCurrency; - } else { - return fiatValueAsString; - } + } else { + if (includeCurrencyCode) { + return Utils.getFormattedValueWithUnit(targetCurrency, getBitcoinDenomination(), precision); } else { - // no valid price available - return ""; + return Utils.getFormattedValue(targetCurrency, getBitcoinDenomination(), precision); } } } - public String getFormattedValue(long satoshis, boolean includeCurrencyCode){ - if (currentCurrency.equals(BTC)){ - return getBtcValueString(satoshis, includeCurrencyCode); - }else{ - return getFormattedFiatValue(satoshis, includeCurrencyCode); + public String getFormattedValue(CurrencyValue currencyValue, boolean includeCurrencyCode) { + if (currencyValue == null){ + return ""; + } + CurrencyValue targetCurrency = getAsValue(currencyValue); + if (includeCurrencyCode) { + return Utils.getFormattedValueWithUnit(targetCurrency, getBitcoinDenomination()); + } else { + return Utils.getFormattedValue(targetCurrency, getBitcoinDenomination()); + } + } + + public String getFormattedValue(CurrencyValue currencyValue, boolean includeCurrencyCode, int precision) { + if (currencyValue == null){ + return ""; + } + CurrencyValue targetCurrency = getAsValue(currencyValue); + if (includeCurrencyCode) { + return Utils.getFormattedValueWithUnit(targetCurrency, getBitcoinDenomination(), precision); + } else { + return Utils.getFormattedValue(targetCurrency, getBitcoinDenomination(), precision); + } + } + + public CurrencyValue getAsFiatValue(CurrencyValue value){ + if (value == null){ + return null; } + if (Strings.isNullOrEmpty(currentFiatCurrency)) { + return null; + } + return CurrencyValue.fromValue(value, getCurrentFiatCurrency(), exchangeRateManager); } - public String getFormattedValue(long satoshis, boolean includeCurrencyCode, int precision){ - if (currentCurrency.equals(BTC)){ - return getBtcValueString(satoshis, includeCurrencyCode, precision); - }else{ - return getFormattedFiatValue(satoshis, includeCurrencyCode, precision); + public CurrencyValue getAsValue(CurrencyValue value){ + if (value == null){ + return null; } + return CurrencyValue.fromValue(value, getCurrentCurrency(), exchangeRateManager); } /** * Get the exchange rate price for the currently selected currency. - *

+ *

* Returns null if the current rate is too old or for a different currency. * In that the case the caller could choose to call refreshRates() and supply a handler to get a callback. */ @@ -284,4 +300,8 @@ public synchronized Double getExchangeRatePrice() { ExchangeRate rate = exchangeRateManager.getExchangeRate(currentFiatCurrency); return rate == null ? null : rate.price; } + + public CurrencyValue getValueFromSum(CurrencySum sum) { + return sum.getSumAsCurrency(currentCurrency, exchangeRateManager); + } } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/DataExport.java b/public/mbw/src/main/java/com/mycelium/wallet/DataExport.java index 2bf1bad2cb..4878aa5c7a 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/DataExport.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/DataExport.java @@ -39,6 +39,7 @@ import com.mycelium.wapi.model.TransactionSummary; import com.mycelium.wapi.wallet.WalletAccount; +import java.math.BigDecimal; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -67,10 +68,16 @@ private static String getTxLine(String accountLabel, String txLabel, Transaction DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); df.setTimeZone(tz); String date = df.format(new Date(summary.time * 1000)); //summary holds time in seconds, date expects milli-seconds - //todo: is this correct, using the isQueuedOutgoing (formerly isOutgoing)? seems like its actually just for not-confirmed tx - long value = (summary.isQueuedOutgoing ? summary.value * -1 : summary.value); //show outgoing as negative amount + BigDecimal value = (summary.isIncoming ? summary.value.getValue() : summary.value.getValue().negate()); //show outgoing as negative amount String destination = summary.destinationAddress.isPresent() ? summary.destinationAddress.get().toString() : ""; - return escape(accountLabel) + "," + summary.txid + "," + destination + "," + date + "," + value + "," + escape(txLabel) + "\n"; + return + escape(accountLabel) + "," + + summary.txid + "," + + destination + "," + + date + "," + + value + "," + + summary.value.getCurrency() + "," + + escape(txLabel) + "\n"; } private static String escape(String input) { diff --git a/public/mbw/src/main/java/com/mycelium/wallet/ExchangeRateManager.java b/public/mbw/src/main/java/com/mycelium/wallet/ExchangeRateManager.java index 96ae3fe668..acf7aa681c 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/ExchangeRateManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/ExchangeRateManager.java @@ -49,7 +49,7 @@ public class ExchangeRateManager implements ExchangeRateProvider { - private static final int MAX_RATE_AGE_MS = 1000 * 60; + private static final int MAX_RATE_AGE_MS = 5 * 1000 * 60; private static final String EXCHANGE_DATA = "wapi_exchange_rates"; public interface Observer { @@ -68,11 +68,9 @@ public interface Observer { private final List _subscribers; private String _currentExchangeSourceName; - public ExchangeRateManager(Context applicationContext, Wapi api, List fiatCurrencies) { + public ExchangeRateManager(Context applicationContext, Wapi api) { _applicationContext = applicationContext; _api = api; - setCurrencyList(fiatCurrencies); - _latestRates = null; _latestRatesTime = 0; _currentExchangeSourceName = getPreferences().getString("currentRateName", null); @@ -174,7 +172,7 @@ public String getCurrentExchangeSourceName() { * Get the names of the currently available exchange rates. May be empty the * first time the app is running */ - public List getExchangeSourceNames() { + public synchronized List getExchangeSourceNames() { List result = new LinkedList(); //check whether we have any rates if (_latestRates.isEmpty()) return result; @@ -238,16 +236,11 @@ private SharedPreferences getPreferences() { } // set for which fiat currencies we should get fx rates for - public void setCurrencyList(List currencies) { + public void setCurrencyList(Set currencies) { synchronized (_requestLock) { // copy list to prevent changes from outside ImmutableList.Builder listBuilder = new ImmutableList.Builder().addAll(currencies); - // always also fetch USD, even if not asked by the user, as some external services depend on a valid USD - // exchange rate - if (!currencies.contains("USD")){ - listBuilder.add("USD"); - } _fiatCurrencies = listBuilder.build(); } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/HttpErrorCollector.java b/public/mbw/src/main/java/com/mycelium/wallet/HttpErrorCollector.java index cf279f5440..9c9e319340 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/HttpErrorCollector.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/HttpErrorCollector.java @@ -60,7 +60,8 @@ public HttpErrorCollector(Thread.UncaughtExceptionHandler orig, Wapi api, String //todo make sure proxy is set before this. require as dependency? public static HttpErrorCollector registerInVM(Context applicationContext, WapiClient wapi) { - MbwEnvironment env = MbwEnvironment.determineEnvironment(applicationContext); + // just call this function to ensure all settings are correct + MbwEnvironment.verifyEnvironment(applicationContext); String version = VersionManager.determineVersion(applicationContext); return registerInVM(applicationContext, version, wapi); } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/LedgerPin2FADialog.java b/public/mbw/src/main/java/com/mycelium/wallet/LedgerPin2FADialog.java index 7a6c442d0e..772c68088d 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/LedgerPin2FADialog.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/LedgerPin2FADialog.java @@ -44,7 +44,7 @@ public void setDialogTitle(int titleRes) { public LedgerPin2FADialog(Context context, String address, byte[] keycardIndexes) { super(context); this.address = address; - this.keycardIndexes = keycardIndexes; + this.keycardIndexes = keycardIndexes.clone(); this.setCanceledOnTouchOutside(false); enteredPin = ""; loadLayout(); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/MbwEnvironment.java b/public/mbw/src/main/java/com/mycelium/wallet/MbwEnvironment.java index d9f1499245..8b488036ab 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/MbwEnvironment.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/MbwEnvironment.java @@ -46,7 +46,7 @@ public abstract class MbwEnvironment { private String _brand; - public static MbwEnvironment determineEnvironment(Context applicationContext) { + public static MbwEnvironment verifyEnvironment(Context applicationContext) { // Set up environment String network = applicationContext.getResources().getString(R.string.network); String brand = applicationContext.getResources().getString(R.string.brand); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/MbwManager.java b/public/mbw/src/main/java/com/mycelium/wallet/MbwManager.java index 35345bc826..afff3870c6 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/MbwManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/MbwManager.java @@ -45,6 +45,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Vibrator; +import android.support.annotation.Nullable; import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowManager; @@ -56,6 +57,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.collect.EvictingQueue; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import com.mrd.bitlib.crypto.*; import com.mrd.bitlib.model.Address; @@ -64,6 +66,7 @@ import com.mrd.bitlib.util.CoinUtil; import com.mrd.bitlib.util.CoinUtil.Denomination; import com.mrd.bitlib.util.HashUtils; +import com.mycelium.WapiLogger; import com.mycelium.lt.api.LtApiClient; import com.mycelium.net.ServerEndpointType; import com.mycelium.net.TorManager; @@ -74,6 +77,7 @@ import com.mycelium.wallet.activity.util.Pin; import com.mycelium.wallet.api.AndroidAsyncApi; import com.mycelium.wallet.bitid.ExternalService; +import com.mycelium.wallet.coinapult.CoinapultManager; import com.mycelium.wallet.event.*; import com.mycelium.wallet.ledger.LedgerManager; import com.mycelium.wallet.lt.LocalTraderManager; @@ -82,7 +86,6 @@ import com.mycelium.wallet.trezor.TrezorManager; import com.mycelium.wallet.wapi.SqliteWalletManagerBackingWrapper; import com.mycelium.wapi.api.WapiClient; -import com.mycelium.WapiLogger; import com.mycelium.wapi.wallet.*; import com.mycelium.wapi.wallet.bip44.Bip44Account; import com.mycelium.wapi.wallet.bip44.Bip44AccountContext; @@ -166,7 +169,7 @@ public static synchronized MbwManager getInstance(Context context) { private MbwManager(Context evilContext) { _applicationContext = Preconditions.checkNotNull(evilContext.getApplicationContext()); - _environment = MbwEnvironment.determineEnvironment(_applicationContext); + _environment = MbwEnvironment.verifyEnvironment(_applicationContext); String version = VersionManager.determineVersion(_applicationContext); // Preferences @@ -226,7 +229,7 @@ private MbwManager(Context evilContext) { _versionManager = new VersionManager(_applicationContext, _language, new AndroidAsyncApi(_wapi, _eventBus), version, _eventBus); Set currencyList = getPreferences().getStringSet(Constants.SELECTED_CURRENCIES, null); - ArrayList fiatCurrencies = new ArrayList(); + Set fiatCurrencies = new HashSet(); if (currencyList == null) { //if there is no list take the default currency fiatCurrencies.add(Constants.DEFAULT_CURRENCY); @@ -235,16 +238,15 @@ private MbwManager(Context evilContext) { fiatCurrencies.addAll(currencyList); } - _exchangeRateManager = new ExchangeRateManager(_applicationContext, _wapi, fiatCurrencies); - + _exchangeRateManager = new ExchangeRateManager(_applicationContext, _wapi); _currencySwitcher = new CurrencySwitcher( - _applicationContext, _exchangeRateManager, fiatCurrencies, getPreferences().getString(Constants.FIAT_CURRENCY_SETTING, Constants.DEFAULT_CURRENCY), Denomination.fromString(preferences.getString(Constants.BITCOIN_DENOMINATION_SETTING, Denomination.BTC.toString())) ); + // Check the device MemoryClass and set the scrypt-parameters for the PDF backup ActivityManager am = (ActivityManager) _applicationContext.getSystemService(Context.ACTIVITY_SERVICE); int memoryClass = am.getMemoryClass(); @@ -256,11 +258,20 @@ private MbwManager(Context evilContext) { _trezorManager = new TrezorManager(_applicationContext, getNetwork(), getEventBus()); _ledgerManager = new LedgerManager(_applicationContext, getNetwork(), getEventBus()); _walletManager = createWalletManager(_applicationContext, _environment); + _eventTranslator = new EventTranslator(new Handler(), _eventBus); - _walletManager.addObserver(_eventTranslator); _exchangeRateManager.subscribe(_eventTranslator); + + _walletManager.addObserver(_eventTranslator); _coinapultManager = createCoinapultManager(); - setExtraAccount(_coinapultManager); + if (_coinapultManager.isPresent()) { + addExtraAccounts(_coinapultManager.get()); + } + + // set the currency-list after we added all extra accounts, they may provide + // additional needed fiat currencies + setCurrencyList(fiatCurrencies); + migrateOldKeys(); createTempWalletManager(); @@ -269,16 +280,23 @@ private MbwManager(Context evilContext) { _environment.getBlockExplorerList(), getPreferences().getString(Constants.BLOCK_EXPLORER, _environment.getBlockExplorerList().get(0).getIdentifier())); + + } + + public void addExtraAccounts(AccountProvider accounts) { + _walletManager.addExtraAccounts(accounts); + _hasCoinapultAccounts = null; // invalidate cache } - public void setExtraAccount(Optional coinapultManager) { - _walletManager.setExtraAccount(coinapultManager); - _hasUsdAccount = null; // invalidate cache + @Subscribe() + public void onExtraAccountsChanged(ExtraAccountsChanged event) { + _walletManager.refreshExtraAccounts(); + _hasCoinapultAccounts = null; // invalidate cache } private Optional createCoinapultManager() { if (_walletManager.hasBip32MasterSeed() && _storage.isPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT)) { - BitidKeyDerivation derivation = new BitidKeyDerivation() { + BitIdKeyDerivation derivation = new BitIdKeyDerivation() { @Override public InMemoryPrivateKey deriveKey(int accountIndex, String site) { try { @@ -520,8 +538,15 @@ private WalletManager createWalletManager(final Context context, MbwEnvironment ); // Create and return wallet manager - return new WalletManager(secureKeyValueStore, + WalletManager walletManager = new WalletManager(secureKeyValueStore, backing, environment.getNetwork(), _wapi, externalSignatureProviderProxy); + + // notify the walletManager about the current selected account + UUID lastSelectedAccountId = getLastSelectedAccountId(); + if (lastSelectedAccountId != null) { + walletManager.setActiveAccount(lastSelectedAccountId); + } + return walletManager; } /** @@ -563,8 +588,12 @@ public List getCurrencyList() { return _currencySwitcher.getCurrencyList(); } - public void setCurrencyList(List currencies) { - _exchangeRateManager.setCurrencyList(currencies); + public void setCurrencyList(Set currencies) { + Set allActiveFiatCurrencies = _walletManager.getAllActiveFiatCurrencies(); + // let the exchange-rate manager fetch all currencies, that we might need + _exchangeRateManager.setCurrencyList(Sets.union(currencies, allActiveFiatCurrencies)); + + // but tell the currency-switcher only to switch over the user selected currencies _currencySwitcher.setCurrencyList(currencies); SharedPreferences.Editor editor = getEditor(); @@ -1038,6 +1067,19 @@ public void forgetColdStorageWalletManager() { } public WalletAccount getSelectedAccount() { + UUID uuid = getLastSelectedAccountId(); + + // If nothing is selected, or selected is archived, pick the first one + if (uuid == null || !_walletManager.hasAccount(uuid) || _walletManager.getAccount(uuid).isArchived()) { + uuid = _walletManager.getActiveAccounts().get(0).getId(); + setSelectedAccount(uuid); + } + + return _walletManager.getAccount(uuid); + } + + @Nullable + private UUID getLastSelectedAccountId() { // Get the selected account ID String uuidStr = getPreferences().getString(SELECTED_ACCOUNT, ""); UUID uuid = null; @@ -1048,18 +1090,7 @@ public WalletAccount getSelectedAccount() { // default to null and select another account below } } - - if (_coinapultManager.isPresent() && _coinapultManager.get().getId().equals(uuid)) { - return _coinapultManager.get(); - } - - // If nothing is selected, or selected is archived, pick the first one - if (uuid == null || !_walletManager.hasAccount(uuid) || _walletManager.getAccount(uuid).isArchived()) { - uuid = _walletManager.getActiveAccounts().get(0).getId(); - setSelectedAccount(uuid); - } - - return _walletManager.getAccount(uuid); + return uuid; } public void setSelectedAccount(UUID uuid) { @@ -1113,7 +1144,7 @@ private InMemoryPrivateKey createBip32WebsitePrivateKey(byte[] masterSeed, int a // Create the private key for the specified account InMemoryPrivateKey accountPriv = bidNode.createChildPrivateKey(accountIndex); // Concatenate the private key bytes with the site name - byte[] sitePrivateKeySeed = new byte[0]; + byte[] sitePrivateKeySeed; try { sitePrivateKeySeed = BitUtils.concatenate(accountPriv.getPrivateKeyBytes(), site.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { @@ -1140,9 +1171,9 @@ public RandomSource getRandomSource() { public TrezorManager getTrezorManager() { return _trezorManager; } - + public LedgerManager getLedgerManager() { - return _ledgerManager; + return _ledgerManager; } public WapiClient getWapi() { @@ -1217,11 +1248,12 @@ public void switchServer() { _environment.getWapiEndpoints().switchToNextEndpoint(); } - Boolean _hasUsdAccount = null; - public boolean hasUsdAccount() { - if (_hasUsdAccount == null){ - _hasUsdAccount = getMetadataStorage().isPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT); + private Boolean _hasCoinapultAccounts = null; + + public boolean hasCoinapultAccount() { + if (_hasCoinapultAccounts == null) { + _hasCoinapultAccounts = getMetadataStorage().isPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT); } - return _hasUsdAccount; + return _hasCoinapultAccounts; } } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/StringHandleConfig.java b/public/mbw/src/main/java/com/mycelium/wallet/StringHandleConfig.java index b8e0c633fc..c1ca51b6be 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/StringHandleConfig.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/StringHandleConfig.java @@ -54,11 +54,8 @@ import com.mycelium.wallet.activity.send.SendMainActivity; import com.mycelium.wallet.bitid.BitIDAuthenticationActivity; import com.mycelium.wallet.bitid.BitIDSignRequest; -import com.mycelium.wallet.external.cashila.activity.BcdCodedSepaData; -import com.mycelium.wallet.external.cashila.activity.CashilaPaymentsActivity; import com.mycelium.wallet.persistence.MetadataStorage; import com.mycelium.wallet.pop.PopRequest; -import com.mycelium.wapi.api.response.Feature; import com.mycelium.wapi.wallet.AesKeyCipher; import com.mycelium.wapi.wallet.KeyCipher; import com.mycelium.wapi.wallet.WalletManager; @@ -134,9 +131,6 @@ public static StringHandleConfig genericScanRequest() { request.hdNodeAction = HdNodeAction.SEND_PUB_SPEND_PRIV; request.popAction = PopAction.SEND; - //not supported so far, as this data lacks address informations - //request.bcdSepaCodeAction = SepaAction.INIT_SEND; - //at the moment, we just support wordlist backups //request.masterSeedAction = MasterSeedAction.IMPORT; return request; @@ -281,41 +275,6 @@ static private boolean isPrivKey(NetworkParameters network, String content) { } } - public enum SepaAction implements Action { - INIT_SEND { - @Override - public boolean handle(final StringHandlerActivity handlerActivity, final String content) { - try { - final BcdCodedSepaData bcdCode = BcdCodedSepaData.fromString(content); - if (bcdCode == null){ - return false; - } - - MbwManager mbwManager = MbwManager.getInstance(handlerActivity); - mbwManager.getVersionManager().showFeatureWarningIfNeeded(handlerActivity, Feature.CASHILA, true, new Runnable() { - @Override - public void run() { - Intent intent = CashilaPaymentsActivity.getIntent(handlerActivity, bcdCode); - intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); - handlerActivity.startActivity(intent); - handlerActivity.finishOk(); - } - }); - - - return true; - } catch (HdKeyNode.KeyGenerationException ex) { - return false; - } - } - - @Override - public boolean canHandle(NetworkParameters network, String content) { - return BcdCodedSepaData.fromString(content) != null; - } - } - } - public enum HdNodeAction implements Action { RETURN { @Override @@ -913,7 +872,6 @@ private boolean isBtcpopURI(String content) { public Action sssShareAction = Action.NONE; public Action hdNodeAction = Action.NONE; public Action wordListAction = Action.NONE; - public Action bcdSepaCodeAction = Action.NONE; public Action popAction = Action.NONE; public List getAllActions() { diff --git a/public/mbw/src/main/java/com/mycelium/wallet/Utils.java b/public/mbw/src/main/java/com/mycelium/wallet/Utils.java index 2171eefebb..2d115a1817 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/Utils.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/Utils.java @@ -84,15 +84,18 @@ import com.mycelium.wallet.activity.BackupWordListActivity; import com.mycelium.wallet.activity.export.BackupToPdfActivity; import com.mycelium.wallet.activity.export.ExportAsQrCodeActivity; -import com.mycelium.wapi.wallet.AesKeyCipher; -import com.mycelium.wapi.wallet.currency.CurrencyValue; +import com.mycelium.wallet.coinapult.CoinapultAccount; import com.mycelium.wallet.persistence.MetadataStorage; +import com.mycelium.wapi.wallet.AesKeyCipher; import com.mycelium.wapi.wallet.ExportableAccount; import com.mycelium.wapi.wallet.WalletAccount; import com.mycelium.wapi.wallet.bip44.Bip44Account; import com.mycelium.wapi.wallet.bip44.Bip44AccountContext; import com.mycelium.wapi.wallet.bip44.Bip44AccountExternalSignature; import com.mycelium.wapi.wallet.bip44.Bip44PubOnlyAccount; +import com.mycelium.wapi.wallet.currency.BitcoinValue; +import com.mycelium.wapi.wallet.currency.CurrencyValue; +import com.mycelium.wapi.wallet.currency.ExactBitcoinValue; import com.mycelium.wapi.wallet.single.SingleAddressAccount; import org.ocpsoft.prettytime.PrettyTime; @@ -101,12 +104,7 @@ import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.List; -import java.util.Locale; +import java.util.*; @SuppressWarnings("deprecation") public class Utils { @@ -238,7 +236,7 @@ public static void showSimpleMessageDialog(final Context context, int messageRes public static String formatBlockcountAsApproxDuration(final Context context, final int blocks) { MbwManager mbwManager = MbwManager.getInstance(context); PrettyTime p = new PrettyTime(mbwManager.getLocale()); - String ret = p.formatApproximateDuration(new Date((new Date()).getTime() + Math.max(blocks, 1) * 10 * 60 * 1000)); + String ret = p.formatApproximateDuration(new Date((new Date()).getTime() + Math.max((long)blocks, 1L) * 10 * 60 * 1000)); return ret; } @@ -270,7 +268,8 @@ public static void showSimpleMessageDialog(final Context context, String message * Show a dialog with a buttons that displays a message. Click the message * or the back button to make it disappear. */ - public static void showSimpleMessageDialog(final Context context, String message, final Runnable okayRunner, @StringRes int okayButtonText, final Runnable postRunner) { + public static void showSimpleMessageDialog(final Context context, String message, final Runnable okayRunner, + @StringRes int okayButtonText, final Runnable postRunner) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); final View layout = inflater.inflate(R.layout.simple_message_dialog, null); AlertDialog.Builder builder = new AlertDialog.Builder(context).setView(layout); @@ -304,7 +303,7 @@ public void onDismiss(DialogInterface dialog) { /** * Show an optional message/ - *

+ *

* The user can check a "never shot this again" check box and the message * will never get displayed again. * @@ -472,7 +471,7 @@ public static Optional

addressFromString(String someString, NetworkPara /** * Truncate and transform a decimal string to a maximum number of digits - *

+ *

* The string will be truncated and verified to be a valid decimal number * with one comma or dot separator. A comma separator will be converted to a * dot. The resulting string will have at most the number of decimals @@ -690,9 +689,15 @@ public static List sortAccounts(List accounts, fin @Nullable @Override public Integer apply(@Nullable WalletAccount input) { - if (input instanceof Bip44Account) return 0; - if (input instanceof SingleAddressAccount) return 1; - if (input instanceof CoinapultManager) return 3; //coinapult last + if (input instanceof Bip44Account) { + return 0; + } + if (input instanceof SingleAddressAccount) { + return 1; + } + if (input instanceof CoinapultAccount) { + return 3; //coinapult last + } return 2; } }); @@ -734,20 +739,19 @@ public static Drawable getDrawableForAccount(WalletAccount walletAccount, boolea //trezor account if (walletAccount instanceof Bip44AccountExternalSignature) { - int accountType = ((Bip44AccountExternalSignature)walletAccount).getAccountType(); - if (accountType == Bip44AccountContext.ACCOUNT_TYPE_UNRELATED_X_PUB_EXTERNAL_SIG_LEDGER) { - return resources.getDrawable(R.drawable.ledger_icon); - } - else { - return resources.getDrawable(R.drawable.trezor_icon_only); - } - + int accountType = ((Bip44AccountExternalSignature) walletAccount).getAccountType(); + if (accountType == Bip44AccountContext.ACCOUNT_TYPE_UNRELATED_X_PUB_EXTERNAL_SIG_LEDGER) { + return resources.getDrawable(R.drawable.ledger_icon); + } else { + return resources.getDrawable(R.drawable.trezor_icon_only); + } + } //regular HD account if (walletAccount instanceof Bip44Account) { return resources.getDrawable(R.drawable.multikeys_grey); } - if (walletAccount instanceof CoinapultManager) { + if (walletAccount instanceof CoinapultAccount) { if (isSelectedAccount) { return resources.getDrawable(R.drawable.coinapult); } else { @@ -761,14 +765,13 @@ public static Drawable getDrawableForAccount(WalletAccount walletAccount, boolea public static String getNameForNewAccount(WalletAccount account, Context context) { if (account instanceof Bip44AccountExternalSignature) { - String baseName; - int accountType = ((Bip44AccountExternalSignature)account).getAccountType(); - if (accountType == Bip44AccountContext.ACCOUNT_TYPE_UNRELATED_X_PUB_EXTERNAL_SIG_LEDGER) { - baseName = MbwManager.getInstance(context).getLedgerManager().getLabelOrDefault(); - } - else { - baseName = MbwManager.getInstance(context).getTrezorManager().getLabelOrDefault(); - } + String baseName; + int accountType = ((Bip44AccountExternalSignature) account).getAccountType(); + if (accountType == Bip44AccountContext.ACCOUNT_TYPE_UNRELATED_X_PUB_EXTERNAL_SIG_LEDGER) { + baseName = MbwManager.getInstance(context).getLedgerManager().getLabelOrDefault(); + } else { + baseName = MbwManager.getInstance(context).getTrezorManager().getLabelOrDefault(); + } return baseName + " #" + (((Bip44AccountExternalSignature) account).getAccountIndex() + 1); } else if (account instanceof Bip44PubOnlyAccount) { return context.getString(R.string.account_prefix_imported); @@ -779,9 +782,13 @@ public static String getNameForNewAccount(WalletAccount account, Context context } } - public static boolean isAllowedForLocalTrader(WalletAccount account) { - if (account instanceof CoinapultManager) return false; //we do not support coinapult accs in lt (yet) - if (!account.getReceivingAddress().isPresent()) return false; // the account has no valid receiving address (should not happen) - dont use it + public static boolean isAllowedForLocalTrader(WalletAccount account) { + if (account instanceof CoinapultAccount) { + return false; //we do not support coinapult accs in lt (yet) + } + if (!account.getReceivingAddress().isPresent()) { + return false; // the account has no valid receiving address (should not happen) - dont use it + } return true; //all other account types including trezor accs are fine } @@ -801,31 +808,101 @@ public static String getFormattedDate(Context context, Date date) { return format.format(date); } - public static String getFormattedValue(CurrencyValue value, MbwManager mbw){ + public static String getFormattedValue(CurrencyValue value, CoinUtil.Denomination denomination) { + if (value == null) { + return ""; + } + BigDecimal val = value.getValue(); - if (val == null){ + if (val == null) { return ""; } if (value.isBtc()) { - return CoinUtil.valueString(val, mbw.getBitcoinDenomination(), false); + return CoinUtil.valueString(val, denomination, false); } else { + return FIAT_FORMAT.format(val); } } - public static String getFormattedValueWithUnit(CurrencyValue value, MbwManager mbw){ + public static String getFormattedValue(CurrencyValue value, CoinUtil.Denomination denomination, int precision) { + if (value == null) { + return ""; + } + BigDecimal val = value.getValue(); - if (val == null){ + if (val == null) { + return ""; + } + if (value.isBtc()) { + return CoinUtil.valueString( + ((BitcoinValue) value).getLongValue(), + denomination, precision + ); + } else { + if (!formatCache.containsKey(precision)) { + DecimalFormat fiatFormat = (DecimalFormat) FIAT_FORMAT.clone(); + fiatFormat.setMaximumFractionDigits(precision); + formatCache.put(precision, fiatFormat); + } + return formatCache.get(precision).format(val); + } + } + + public static String getFormattedValueWithUnit(CurrencyValue value, CoinUtil.Denomination denomination) { + if (value == null) { return ""; } if (value.isBtc()) { - return String.format("%s %s", CoinUtil.valueString(val, mbw.getBitcoinDenomination(), false), mbw.getBitcoinDenomination().getUnicodeName()); + return getFormattedValueWithUnit((BitcoinValue) value, denomination); } else { + BigDecimal val = value.getValue(); + if (val == null) { + return ""; + } return String.format("%s %s", FIAT_FORMAT.format(val), value.getCurrency()); } } + // prevent ambiguous call for ExactBitcoinValue + public static String getFormattedValueWithUnit(ExactBitcoinValue value, CoinUtil.Denomination denomination) { + return getFormattedValueWithUnit((BitcoinValue)value, denomination); + } + + public static String getFormattedValueWithUnit(BitcoinValue value, CoinUtil.Denomination denomination) { + BigDecimal val = value.getValue(); + if (val == null) { + return ""; + } + return String.format("%s %s", CoinUtil.valueString(val, denomination, false), denomination.getUnicodeName()); + } + + + public static String getFormattedValueWithUnit(CurrencyValue value, CoinUtil.Denomination denomination, int precision) { + if (value == null) { + return ""; + } + + BigDecimal val = value.getValue(); + if (val == null) { + return ""; + } + + if (value.isBtc()) { + return String.format("%s %s", CoinUtil.valueString(((BitcoinValue) value).getLongValue(), + denomination, precision), denomination.getUnicodeName() + ); + } else { + if (!formatCache.containsKey(precision)) { + DecimalFormat fiatFormat = (DecimalFormat) FIAT_FORMAT.clone(); + fiatFormat.setMaximumFractionDigits(precision); + formatCache.put(precision, fiatFormat); + } + return String.format("%s %s", formatCache.get(precision).format(val), value.getCurrency()); + } + } + public static boolean isValidEmailAddress(String value) { return android.util.Patterns.EMAIL_ADDRESS.matcher(value).matches(); } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/VersionManager.java b/public/mbw/src/main/java/com/mycelium/wallet/VersionManager.java index df4df96b57..8735ad2700 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/VersionManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/VersionManager.java @@ -315,7 +315,7 @@ public boolean showFeatureWarningIfNeeded(final Context context, final Feature f text.setText(msg); text.setMovementMethod(LinkMovementMethod.getInstance()); - int padding = (int) (context.getResources().getDisplayMetrics().density * 10f + 0.5f); + int padding = (int) (context.getResources().getDisplayMetrics().density * 10.0 + 0.5); text.setPadding(padding, padding, padding, padding); AlertDialog.Builder dialog = new AlertDialog.Builder(context); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/AddAccountActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/AddAccountActivity.java index 6adcc0ab68..70a8c5b566 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/AddAccountActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/AddAccountActivity.java @@ -52,7 +52,7 @@ import com.coinapult.api.httpclient.CoinapultClient; import com.google.common.base.Optional; import com.google.common.base.Preconditions; -import com.mycelium.wallet.CoinapultManager; +import com.mycelium.wallet.coinapult.CoinapultManager; import com.mycelium.wallet.MbwManager; import com.mycelium.wallet.R; import com.mycelium.wallet.activity.modern.Toaster; @@ -69,15 +69,8 @@ public class AddAccountActivity extends Activity { - public static final int RESULT_COINAPULT = 2; - public static void callMe(Fragment fragment, int requestCode) { - callMe(fragment, requestCode, false); - } - - public static void callMe(Fragment fragment, int requestCode, boolean addCoinapult) { Intent intent = new Intent(fragment.getActivity(), AddAccountActivity.class); - intent.putExtra("coinapult", addCoinapult); fragment.startActivityForResult(intent, requestCode); } @@ -93,7 +86,6 @@ public void onCreate(Bundle savedInstanceState) { this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); super.onCreate(savedInstanceState); setContentView(R.layout.add_account_activity); - final Activity activity = AddAccountActivity.this; _mbwManager = MbwManager.getInstance(this); _toaster = new Toaster(this); @@ -101,7 +93,7 @@ public void onCreate(Bundle savedInstanceState) { findViewById(R.id.btHdCreate).setOnClickListener(createHdAccount); final View coinapultUSD = findViewById(R.id.btCoinapultCreate); coinapultUSD.setOnClickListener(createCoinapultAccount); - coinapultUSD.setEnabled(!_mbwManager.getMetadataStorage().isPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT)); + //coinapultUSD.setEnabled(!_mbwManager.getMetadataStorage().isPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT)); if (_mbwManager.getMetadataStorage().getMasterSeedBackupState() == MetadataStorage.BackupState.VERIFIED) { findViewById(R.id.tvWarningNoBackup).setVisibility(View.GONE); } else { @@ -109,9 +101,6 @@ public void onCreate(Bundle savedInstanceState) { } _progress = new ProgressDialog(this); - if (getIntent().getBooleanExtra("coinapult", false)){ - createCoinapultAccountProtected(); - } } View.OnClickListener advancedClickListener = new View.OnClickListener() { @@ -142,25 +131,14 @@ public void run() { View.OnClickListener createCoinapultAccount = new View.OnClickListener() { @Override public void onClick(View view) { - createCoinapultAccountProtected(); + Intent intent = AddCoinapultAccountActivity.getIntent(AddAccountActivity.this); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + AddAccountActivity.this.startActivity(intent); + AddAccountActivity.this.finish(); + } }; - private void createCoinapultAccountProtected() { - _mbwManager.getVersionManager().showFeatureWarningIfNeeded( - AddAccountActivity.this, Feature.COINAPULT_NEW_ACCOUNT, true, new Runnable() { - @Override - public void run() { - _mbwManager.runPinProtectedFunction(AddAccountActivity.this, new Runnable() { - @Override - public void run() { - createCoinapultAccount(); - } - }); - } - } - ); - } private void createNewHdAccount() { final WalletManager wallet = _mbwManager.getWalletManager(false); @@ -177,34 +155,6 @@ private void createNewHdAccount() { new HdCreationAsyncTask(_mbwManager.getEventBus()).execute(); } - private void createCoinapultAccount() { - - AlertDialog.Builder b = new AlertDialog.Builder(this); - b.setTitle(getString(R.string.coinapult_tos_question)); - View diaView = getLayoutInflater().inflate(R.layout.ext_coinapult_tos, null); - b.setView(diaView); - b.setPositiveButton(getString(R.string.agree), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // Create the account initially without set email address - // if needed, the user can later set and verify it via account menu. - new AddCoinapultAsyncTask(_mbwManager.getEventBus(), Optional.absent()).execute(); - } - }); - b.setNegativeButton(getString(R.string.dontagree), null); - - AlertDialog dialog = b.create(); - - TextView link = (TextView) diaView.findViewById(R.id.tosLink); - link.setClickable(true); - link.setMovementMethod(LinkMovementMethod.getInstance()); - String linkUrl = getString(R.string.coinapult_tos_link_url); - String text = " " + link.getText() + ""; - link.setText(Html.fromHtml(text)); - - dialog.show(); - } - private class HdCreationAsyncTask extends AsyncTask { private Bus bus; @@ -229,54 +179,6 @@ protected void onPostExecute(UUID account) { } } - private class AddCoinapultAsyncTask extends AsyncTask { - private Bus bus; - private Optional mail; - private CoinapultManager coinapultManager; - private final ProgressDialog progressDialog; - - public AddCoinapultAsyncTask(Bus bus, Optional mail) { - this.bus = bus; - this.mail = mail; - progressDialog = ProgressDialog.show(AddAccountActivity.this, getString(R.string.coinapult), getString(R.string.createCoinapult)); - progressDialog.setCancelable(false); - progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - progressDialog.show(); - } - - @Override - protected UUID doInBackground(Void... params) { - _mbwManager.getMetadataStorage().setPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT, true); - coinapultManager = _mbwManager.getCoinapultManager(); - try { - coinapultManager.addUSD(mail); - // save the mail address locally for later verification - if (mail.isPresent()) { - _mbwManager.getMetadataStorage().setCoinapultMail(mail.get()); - } - } catch (CoinapultClient.CoinapultBackendException e) { - return null; - } - return coinapultManager.getId(); - } - - @Override - protected void onPostExecute(UUID account) { - progressDialog.dismiss(); - if (account != null) { - _mbwManager.setExtraAccount(Optional.of(coinapultManager)); - bus.post(new AccountChanged(account)); - Intent result = new Intent(); - result.putExtra(RESULT_KEY, coinapultManager.getId()); - setResult(RESULT_COINAPULT, result); - finish(); - } else { - // something went wrong - clean up the half ready coinapultManager - Toast.makeText(AddAccountActivity.this, R.string.coinapult_unable_to_create_account, Toast.LENGTH_SHORT).show(); - _mbwManager.getMetadataStorage().setPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT, false); - } - } - } @com.squareup.otto.Subscribe public void hdAccountCreated(HdAccountCreated event) { diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/AddCoinapultAccountActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/AddCoinapultAccountActivity.java new file mode 100644 index 0000000000..744abc2fea --- /dev/null +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/AddCoinapultAccountActivity.java @@ -0,0 +1,250 @@ +/* + * Copyright 2013, 2014 Megion Research and Development GmbH + * + * Licensed under the Microsoft Reference Source License (MS-RSL) + * + * This license governs use of the accompanying software. If you use the software, you accept this license. + * If you do not accept the license, do not use the software. + * + * 1. Definitions + * The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law. + * "You" means the licensee of the software. + * "Your company" means the company you worked for when you downloaded the software. + * "Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes + * of debugging your products, maintaining your products, or enhancing the interoperability of your products with the + * software, and specifically excludes the right to distribute the software outside of your company. + * "Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor + * under this license. + * + * 2. Grant of Rights + * (A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, + * worldwide, royalty-free copyright license to reproduce the software for reference use. + * (B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, + * worldwide, royalty-free patent license under licensed patents for reference use. + * + * 3. Limitations + * (A) No Trademark License- This license does not grant you any rights to use the Licensor’s name, logo, or trademarks. + * (B) If you begin patent litigation against the Licensor over patents that you think may apply to the software + * (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically. + * (C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, + * guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot + * change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, + * fitness for a particular purpose and non-infringement. + */ + +package com.mycelium.wallet.activity; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; +import butterknife.ButterKnife; +import butterknife.InjectView; +import butterknife.OnClick; +import com.coinapult.api.httpclient.CoinapultClient; +import com.google.common.base.Optional; +import com.mycelium.wallet.MbwManager; +import com.mycelium.wallet.R; +import com.mycelium.wallet.coinapult.CoinapultAccount; +import com.mycelium.wallet.coinapult.CoinapultManager; +import com.mycelium.wallet.event.AccountChanged; +import com.mycelium.wallet.persistence.MetadataStorage; +import com.mycelium.wapi.api.response.Feature; +import com.squareup.otto.Bus; + +import java.util.UUID; + +public class AddCoinapultAccountActivity extends Activity { + public static final int RESULT_COINAPULT = 2; + + @InjectView(R.id.btCoinapultAddGBP) Button btCoinapultAddGBP; + @InjectView(R.id.btCoinapultAddUSD) Button btCoinapultAddUSD; + @InjectView(R.id.btCoinapultAddEUR) Button btCoinapultAddEUR; + @InjectView(R.id.tvTosLink) TextView tvTosLink; + + public static Intent getIntent(Context context) { + Intent intent = new Intent(context, AddCoinapultAccountActivity.class); + //intent.putExtra("coinapult", addCoinapult); + return intent; + } + + public static final String RESULT_KEY = "account"; + private MbwManager _mbwManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + super.onCreate(savedInstanceState); + setContentView(R.layout.add_coinapult_account_activity); + _mbwManager = MbwManager.getInstance(this); + ButterKnife.inject(this); + + btCoinapultAddUSD.setText(getString(R.string.coinapult_currency_account, CoinapultAccount.Currency.USD.name)); + btCoinapultAddEUR.setText(getString(R.string.coinapult_currency_account, CoinapultAccount.Currency.EUR.name)); + btCoinapultAddGBP.setText(getString(R.string.coinapult_currency_account, CoinapultAccount.Currency.GBP.name)); + + setTosLink(tvTosLink); + } + + void setButtonEnabled(){ + // only enable buttons for which we dont have an account already + if (_mbwManager.hasCoinapultAccount()){ + CoinapultManager coinapultManager = _mbwManager.getCoinapultManager(); + btCoinapultAddUSD.setEnabled(!coinapultManager.hasCurrencyEnabled(CoinapultAccount.Currency.USD)); + btCoinapultAddEUR.setEnabled(!coinapultManager.hasCurrencyEnabled(CoinapultAccount.Currency.EUR)); + btCoinapultAddGBP.setEnabled(!coinapultManager.hasCurrencyEnabled(CoinapultAccount.Currency.GBP)); + + } else { + btCoinapultAddUSD.setEnabled(true); + btCoinapultAddEUR.setEnabled(true); + btCoinapultAddGBP.setEnabled(true); + } + } + + @OnClick(R.id.btCoinapultAddUSD) + void onUsdClick() { + createCoinapultAccountProtected(CoinapultAccount.Currency.USD); + } + + @OnClick(R.id.btCoinapultAddEUR) + void onEurClick() { + createCoinapultAccountProtected(CoinapultAccount.Currency.EUR); + } + + @OnClick(R.id.btCoinapultAddGBP) + void onGbpClick() { + createCoinapultAccountProtected(CoinapultAccount.Currency.GBP); + } + + + private void createCoinapultAccountProtected(final CoinapultAccount.Currency currency) { + _mbwManager.getVersionManager().showFeatureWarningIfNeeded( + AddCoinapultAccountActivity.this, Feature.COINAPULT_NEW_ACCOUNT, true, new Runnable() { + @Override + public void run() { + _mbwManager.runPinProtectedFunction(AddCoinapultAccountActivity.this, new Runnable() { + @Override + public void run() { + createCoinapultAccount(currency); + } + }); + } + } + ); + } + + private void createCoinapultAccount(final CoinapultAccount.Currency currency) { + + AlertDialog.Builder b = new AlertDialog.Builder(this); + b.setTitle(getString(R.string.coinapult)); + View diaView = getLayoutInflater().inflate(R.layout.ext_coinapult_tos, null); + b.setView(diaView); + b.setPositiveButton(getString(R.string.agree), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Create the account initially without set email address + // if needed, the user can later set and verify it via account menu. + new AddCoinapultAsyncTask(_mbwManager.getEventBus(), Optional.absent(), currency).execute(); + } + }); + b.setNegativeButton(getString(R.string.dontagree), null); + + AlertDialog dialog = b.create(); + + TextView link = (TextView) diaView.findViewById(R.id.tvTosLink); + TextView tvThreshold = (TextView) diaView.findViewById(R.id.tvThreshold); + tvThreshold.setText(getString(R.string.coinapult_threshold_warning, currency.getMinimumConversationString())); + setTosLink(link); + + dialog.show(); + } + + private void setTosLink(TextView link) { + link.setClickable(true); + link.setMovementMethod(LinkMovementMethod.getInstance()); + String linkUrl = getString(R.string.coinapult_tos_link_url); + String text = " " + link.getText() + ""; + link.setText(Html.fromHtml(text)); + } + + private class AddCoinapultAsyncTask extends AsyncTask { + private final boolean alreadyHadCoinapultAccount; + private Bus bus; + private Optional mail; + private final CoinapultAccount.Currency currency; + private CoinapultManager coinapultManager; + private final ProgressDialog progressDialog; + + public AddCoinapultAsyncTask(Bus bus, Optional mail, CoinapultAccount.Currency currency) { + this.bus = bus; + this.mail = mail; + this.currency = currency; + this.alreadyHadCoinapultAccount = _mbwManager.getMetadataStorage().isPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT); + progressDialog = ProgressDialog.show(AddCoinapultAccountActivity.this, getString(R.string.coinapult), getString(R.string.coinapult_create_account)); + progressDialog.setCancelable(false); + progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + progressDialog.show(); + } + + @Override + protected UUID doInBackground(Void... params) { + _mbwManager.getMetadataStorage().setPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT, true); + coinapultManager = _mbwManager.getCoinapultManager(); + try { + coinapultManager.activateAccount(mail); + // save the mail address locally for later verification + if (mail.isPresent()) { + _mbwManager.getMetadataStorage().setCoinapultMail(mail.get()); + } + UUID uuid = coinapultManager.enableCurrency(currency); + coinapultManager.scanForAccounts(); + return uuid; + } catch (CoinapultClient.CoinapultBackendException e) { + return null; + } + } + + @Override + protected void onPostExecute(UUID account) { + progressDialog.dismiss(); + if (account != null) { + _mbwManager.addExtraAccounts(coinapultManager); + bus.post(new AccountChanged(account)); + Intent result = new Intent(); + result.putExtra(RESULT_KEY, account); + setResult(RESULT_COINAPULT, result); + finish(); + } else { + // something went wrong - clean up the half ready coinapultManager + Toast.makeText(AddCoinapultAccountActivity.this, R.string.coinapult_unable_to_create_account, Toast.LENGTH_SHORT).show(); + _mbwManager.getMetadataStorage().setPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT, alreadyHadCoinapultAccount); + } + setButtonEnabled(); + } + } + + @Override + public void onResume() { + _mbwManager.getEventBus().register(this); + setButtonEnabled(); + super.onResume(); + } + + @Override + public void onPause() { + _mbwManager.getEventBus().unregister(this); + _mbwManager.getVersionManager().closeDialog(); + super.onPause(); + } +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/GetAmountActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/GetAmountActivity.java index a6f8e3da58..45431fe820 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/GetAmountActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/GetAmountActivity.java @@ -40,11 +40,13 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.view.View; -import android.view.View.OnClickListener; import android.view.Window; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; +import butterknife.ButterKnife; +import butterknife.InjectView; +import butterknife.OnClick; import com.google.common.base.Preconditions; import com.megiontechnologies.Bitcoins; import com.mrd.bitlib.StandardTransactionBuilder; @@ -57,10 +59,7 @@ import com.mycelium.wallet.event.ExchangeRatesRefreshed; import com.mycelium.wallet.event.SelectedCurrencyChanged; import com.mycelium.wapi.wallet.WalletAccount; -import com.mycelium.wapi.wallet.currency.CurrencyValue; -import com.mycelium.wapi.wallet.currency.ExactBitcoinValue; -import com.mycelium.wapi.wallet.currency.ExactCurrencyValue; -import com.mycelium.wapi.wallet.currency.ExchangeBasedCurrencyValue; +import com.mycelium.wapi.wallet.currency.*; import com.squareup.otto.Subscribe; import java.math.BigDecimal; @@ -68,14 +67,20 @@ public class GetAmountActivity extends Activity implements NumberEntryListener { - public static final String AMOUNT = "amount"; - public static final String ENTEREDAMOUNT = "enteredamount"; + public static final String ENTERED_AMOUNT = "enteredamount"; public static final String ACCOUNT = "account"; public static final String KB_MINER_FEE = "kbMinerFee"; public static final String IS_COLD_STORAGE = "isColdStorage"; - public static final String SENDMODE = "sendmode"; - public static final String AMOUNT_SATOSHI = "amountSatoshi"; + public static final String SEND_MODE = "sendmode"; + + @InjectView(R.id.btCurrency) Button btCurrency; + @InjectView(R.id.btPaste) Button btPaste; + @InjectView(R.id.btMax) Button btMax; + @InjectView(R.id.btOk) Button btOk; + @InjectView(R.id.tvMaxAmount) TextView tvMaxAmount; + @InjectView(R.id.tvAmount) TextView tvAmount; + @InjectView(R.id.tvAlternateAmount) TextView tvAlternateAmount; private boolean isSendMode; @@ -89,17 +94,17 @@ public class GetAmountActivity extends Activity implements NumberEntryListener { public static void callMe(Activity currentActivity, int requestCode, UUID account, CurrencyValue amountToSend, Long kbMinerFee, boolean isColdStorage) { Intent intent = new Intent(currentActivity, GetAmountActivity.class); intent.putExtra(ACCOUNT, account); - intent.putExtra(ENTEREDAMOUNT, amountToSend); + intent.putExtra(ENTERED_AMOUNT, amountToSend); intent.putExtra(KB_MINER_FEE, kbMinerFee); intent.putExtra(IS_COLD_STORAGE, isColdStorage); - intent.putExtra(SENDMODE, true); + intent.putExtra(SEND_MODE, true); currentActivity.startActivityForResult(intent, requestCode); } public static void callMe(Activity currentActivity, CurrencyValue amountToSend, int requestCode) { Intent intent = new Intent(currentActivity, GetAmountActivity.class); - intent.putExtra(ENTEREDAMOUNT, amountToSend); - intent.putExtra(SENDMODE, false); + intent.putExtra(ENTERED_AMOUNT, amountToSend); + intent.putExtra(SEND_MODE, false); currentActivity.startActivityForResult(intent, requestCode); } @@ -109,11 +114,13 @@ public void onCreate(Bundle savedInstanceState) { this.requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); setContentView(R.layout.get_amount_activity); + ButterKnife.inject(this); + _mbwManager = MbwManager.getInstance(getApplication()); initNumberEntry(savedInstanceState); - isSendMode = getIntent().getBooleanExtra(SENDMODE, false); + isSendMode = getIntent().getBooleanExtra(SEND_MODE, false); if (isSendMode) { initSendMode(); } @@ -135,105 +142,96 @@ private void initSendMode() { showMaxAmount(); // if no amount is set, create an null amount with the correct currency - if (_amount == null || _amount.getValue() == null){ + if (_amount == null || _amount.getValue() == null) { _amount = ExactCurrencyValue.from(null, _maxSpendableAmount.getCurrency()); updateUI(); } // Max Button - findViewById(R.id.btMax).setOnClickListener(maxClickListener); - - findViewById(R.id.tvMaxAmount).setVisibility(View.VISIBLE); - findViewById(R.id.btMax).setVisibility(View.VISIBLE); + tvMaxAmount.setVisibility(View.VISIBLE); + btMax.setVisibility(View.VISIBLE); } private void initListeners() { - // Make both currency button and invisible right button at top a listener - // switch currency - Button btCurrency = (Button) findViewById(R.id.btCurrency); + // set the text for the currency button btCurrency.setText(_mbwManager.getBitcoinDenomination().getUnicodeName()); btCurrency.setEnabled(_mbwManager.getCurrencySwitcher().getExchangeRatePrice() != null); - btCurrency.setOnClickListener(switchCurrencyListener); - findViewById(R.id.btRight).setOnClickListener(switchCurrencyListener); - - // Make both paste button and invisible left button at top a listener to - // paste from clipboard - Button btPaste = (Button) findViewById(R.id.btPaste); - btPaste.setOnClickListener(pasteListener); - findViewById(R.id.btLeft).setOnClickListener(pasteListener); - - // Ok Button - findViewById(R.id.btOk).setOnClickListener(onClickOkButton); } private void initNumberEntry(Bundle savedInstanceState) { - _amount = (CurrencyValue) getIntent().getSerializableExtra(ENTEREDAMOUNT); + _amount = (CurrencyValue) getIntent().getSerializableExtra(ENTERED_AMOUNT); // Load saved state if (savedInstanceState != null) { - _amount = (CurrencyValue) savedInstanceState.getSerializable(ENTEREDAMOUNT); + _amount = (CurrencyValue) savedInstanceState.getSerializable(ENTERED_AMOUNT); } - // Set amount + + // Init the number pad String amountString; if (_amount != null) { - amountString = Utils.getFormattedValue(_amount, _mbwManager); + amountString = Utils.getFormattedValue(_amount, _mbwManager.getBitcoinDenomination()); + _mbwManager.getCurrencySwitcher().setCurrency(_amount.getCurrency()); } else { amountString = ""; } - TextView tvAmount = (TextView) findViewById(R.id.tvAmount); - tvAmount.setText(amountString); - _numberEntry = new NumberEntry(_mbwManager.getBitcoinDenomination().getDecimalPlaces(), this, this, amountString); + } - private OnClickListener onClickOkButton = new OnClickListener() { + @OnClick(R.id.btOk) + void onOkClick() { + if (_amount == null) { + return; + } - @Override - public void onClick(View arg0) { - if (_amount == null){ - return; - } + // Return the entered value and set a positive result code + Intent result = new Intent(); + result.putExtra(AMOUNT, _amount); + setResult(RESULT_OK, result); + GetAmountActivity.this.finish(); + } - // Return the number of satoshis - Intent result = new Intent(); - result.putExtra(AMOUNT, _amount); - Bitcoins amountBtc = _amount.getAsBitcoin(_mbwManager.getExchangeRateManager()); - if (amountBtc != null) { - long longValue = amountBtc.getLongValue(); - result.putExtra(AMOUNT_SATOSHI, longValue); - } - setResult(RESULT_OK, result); - GetAmountActivity.this.finish(); + @OnClick(R.id.btMax) + void onMaxButtonClick() { + if (CurrencyValue.isNullOrZero(_maxSpendableAmount)) { + String msg = getResources().getString(R.string.insufficient_funds); + Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); + } else { + _amount = _maxSpendableAmount; + // set the current shown currency to the amounts currency + _mbwManager.getCurrencySwitcher().setCurrency(_amount.getCurrency()); + updateUI(); + checkEntry(); } - }; + } + - private OnClickListener maxClickListener = new OnClickListener() { + @OnClick({R.id.btRight, R.id.btCurrency}) + void onSwitchCurrencyClick() { + String targetCurrency = _mbwManager.getNextCurrency(true); + CurrencySwitcher currencySwitcher = _mbwManager.getCurrencySwitcher(); - @Override - public void onClick(View arg0) { - maximizeAmount(); + // if we have a fiat currency selected and the price is not available, switch on -> no point in showing it + // if there is no exchange rate at all available, we will get to BTC and stay there + while (!targetCurrency.equals(CurrencyValue.BTC) && !currencySwitcher.isFiatExchangeRateAvailable()) { + targetCurrency = _mbwManager.getNextCurrency(true); } - }; - private final OnClickListener switchCurrencyListener = new OnClickListener() { + _amount = CurrencyValue.fromValue(_amount, targetCurrency, _mbwManager.getExchangeRateManager()); - @Override - public void onClick(View arg0) { - switchCurrency(); - } - }; + updateUI(); + } - private final OnClickListener pasteListener = new OnClickListener() { - @Override - public void onClick(View arg0) { - BigDecimal clipboardValue = getAmountFromClipboard(); - if (clipboardValue == null) { - return; - } - setEnteredAmount(clipboardValue); - _numberEntry.setEntry(clipboardValue, _mbwManager.getBitcoinDenomination().getDecimalPlaces()); + @OnClick({R.id.btLeft, R.id.btPaste}) + void onPasteButtonClick() { + BigDecimal clipboardValue = getAmountFromClipboard(); + if (clipboardValue == null) { + return; } - }; + setEnteredAmount(clipboardValue); + _numberEntry.setEntry(clipboardValue, _mbwManager.getBitcoinDenomination().getDecimalPlaces()); + } + private boolean enablePaste() { return getAmountFromClipboard() != null; @@ -245,7 +243,7 @@ private BigDecimal getAmountFromClipboard() { return null; } String number = content.trim(); - if (_amount.isBtc()) { + if (CurrencyValue.BTC.equals(_mbwManager.getCurrencySwitcher().getCurrentCurrency())) { number = Utils .truncateAndConvertDecimalString(number, _mbwManager.getBitcoinDenomination().getDecimalPlaces()); if (number == null) { @@ -269,74 +267,53 @@ private BigDecimal getAmountFromClipboard() { } } - private void switchCurrency() { - String targetCurrency = _mbwManager.getNextCurrency(true); - CurrencySwitcher currencySwitcher = _mbwManager.getCurrencySwitcher(); - - // if we have a fiat currency selected and the price is not available, switch on -> no point in showing it - // if there is no exchange rate at all available, we will get to BTC and stay there - while (!targetCurrency.equals(CurrencySwitcher.BTC) && !currencySwitcher.isFiatExchangeRateAvailable()) { - targetCurrency = _mbwManager.getNextCurrency(true); - } - - _amount = CurrencyValue.fromValue(_amount, targetCurrency, _mbwManager.getExchangeRateManager()); - - updateUI(); - } - private void updateUI() { //update buttons and views - Button btCurrency = (Button) findViewById(R.id.btCurrency); // Show maximum spendable amount if (isSendMode) { showMaxAmount(); } + // Set current currency name button + btCurrency.setText(_mbwManager.getCurrencySwitcher().getCurrentCurrencyIncludingDenomination()); - if (_amount.isBtc()) { - // Set BTC button - btCurrency.setText(_mbwManager.getBitcoinDenomination().getUnicodeName()); - } else { - // Set Fiat button - btCurrency.setText(_amount.getCurrency()); - } - - //update amount - int showDecimalPlaces; - BigDecimal newAmount = null; - if (_amount.isBtc()) { - //just good ol bitcoins - showDecimalPlaces = _mbwManager.getBitcoinDenomination().getDecimalPlaces(); - if (_amount.getValue() != null) { - int btcToTargetUnit = CoinUtil.Denomination.BTC.getDecimalPlaces() - _mbwManager.getBitcoinDenomination().getDecimalPlaces(); - newAmount = _amount.getValue().multiply(BigDecimal.TEN.pow(btcToTargetUnit)); + if (_amount != null) { + //update amount + int showDecimalPlaces; + BigDecimal newAmount = null; + if ( _mbwManager.getCurrencySwitcher().getCurrentCurrency().equals(CurrencyValue.BTC)) { + //just good ol bitcoins + showDecimalPlaces = _mbwManager.getBitcoinDenomination().getDecimalPlaces(); + if (_amount.getValue() != null) { + int btcToTargetUnit = CoinUtil.Denomination.BTC.getDecimalPlaces() - _mbwManager.getBitcoinDenomination().getDecimalPlaces(); + newAmount = _amount.getValue().multiply(BigDecimal.TEN.pow(btcToTargetUnit)); + } + } else { + //take what was typed in + showDecimalPlaces = 2; + newAmount = _amount.getValue(); } + _numberEntry.setEntry(newAmount, showDecimalPlaces); } else { - //take what was typed in - showDecimalPlaces = 2; - newAmount = _amount.getValue(); + tvAmount.setText(""); } - _numberEntry.setEntry(newAmount, showDecimalPlaces); - // Check whether we can show the paste button - findViewById(R.id.btPaste).setVisibility(enablePaste() ? View.VISIBLE : View.GONE); + btPaste.setVisibility(enablePaste() ? View.VISIBLE : View.GONE); } private void showMaxAmount() { CurrencyValue maxSpendable = CurrencyValue.fromValue(_maxSpendableAmount, _amount.getCurrency(), _mbwManager.getExchangeRateManager()); - String maxBalanceString = getResources().getString(R.string.max_btc, - Utils.getFormattedValueWithUnit(maxSpendable, _mbwManager)); - - ((TextView) findViewById(R.id.tvMaxAmount)).setText(maxBalanceString); + Utils.getFormattedValueWithUnit(maxSpendable, _mbwManager.getBitcoinDenomination())); + tvMaxAmount.setText(maxBalanceString); } @Override public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); - savedInstanceState.putSerializable(ENTEREDAMOUNT, _amount); + savedInstanceState.putSerializable(ENTERED_AMOUNT, _amount); } @Override @@ -345,11 +322,11 @@ protected void onResume() { _mbwManager.getExchangeRateManager().requestOptionalRefresh(); - findViewById(R.id.btCurrency).setEnabled( - _mbwManager.hasFiatCurrency() - && _mbwManager.getCurrencySwitcher().isFiatExchangeRateAvailable()); + btCurrency.setEnabled(_mbwManager.hasFiatCurrency() + && _mbwManager.getCurrencySwitcher().isFiatExchangeRateAvailable() + ); - findViewById(R.id.btPaste).setVisibility(enablePaste() ? View.VISIBLE : View.GONE); + btPaste.setVisibility(enablePaste() ? View.VISIBLE : View.GONE); super.onResume(); } @@ -372,34 +349,34 @@ public void onEntryChanged(String entry, boolean wasSet) { private void setEnteredAmount(BigDecimal value) { // handle denomination - if (_amount.isBtc()) { + String currentCurrency = _mbwManager.getCurrencySwitcher().getCurrentCurrency(); + if (currentCurrency.equals(CurrencyValue.BTC)) { Long satoshis; int decimals = _mbwManager.getBitcoinDenomination().getDecimalPlaces(); satoshis = value.movePointRight(decimals).longValue(); - if (satoshis >= Bitcoins.MAX_VALUE){ + if (satoshis >= Bitcoins.MAX_VALUE) { // entered value is equal or larger then total amount of bitcoins ever existing return; } _amount = ExactBitcoinValue.from(satoshis); } else { - _amount = ExactCurrencyValue.from(value, _amount.getCurrency()); + _amount = ExactCurrencyValue.from(value, currentCurrency); } if (isSendMode) { // enable/disable Max button - findViewById(R.id.btMax).setEnabled(_maxSpendableAmount.getExactValue() != _amount.getExactValue()); + btMax.setEnabled(_maxSpendableAmount.getExactValue() != _amount.getExactValue()); } } private void updateAmountsDisplay(String amountText) { // update main-currency display - ((TextView) findViewById(R.id.tvAmount)).setText(amountText); + tvAmount.setText(amountText); // Set alternate amount if we can - TextView tvAlternateAmount = ((TextView) findViewById(R.id.tvAlternateAmount)); if (!_mbwManager.hasFiatCurrency() || !_mbwManager.getCurrencySwitcher().isFiatExchangeRateAvailable()) { tvAlternateAmount.setText(""); } else { @@ -411,38 +388,31 @@ private void updateAmountsDisplay(String amountText) { _amount, currency, _mbwManager.getExchangeRateManager()); } else { // Show BTC as alternate amount - convertedAmount = ExchangeBasedCurrencyValue.fromValue( - _amount, "BTC", _mbwManager.getExchangeRateManager()); + try { + convertedAmount = ExchangeBasedCurrencyValue.fromValue( + _amount, "BTC", _mbwManager.getExchangeRateManager()); + } catch (IllegalArgumentException ex){ + // something failed while calculating the bitcoin amount + convertedAmount = ExactBitcoinValue.ZERO; + } } - tvAlternateAmount.setText(Utils.getFormattedValueWithUnit(convertedAmount, _mbwManager)); - } - } - - private void maximizeAmount() { - if (_maxSpendableAmount.isZero()) { - String msg = getResources().getString(R.string.insufficient_funds); - Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); - } else { - _amount = _maxSpendableAmount; - updateUI(); - checkEntry(); + tvAlternateAmount.setText(Utils.getFormattedValueWithUnit(convertedAmount, _mbwManager.getBitcoinDenomination())); } } private void checkEntry() { - if (_amount.getValue() == null || _amount.isZero()) { + if (CurrencyValue.isNullOrZero(_amount)) { // Nothing entered - ((TextView) findViewById(R.id.tvAmount)).setTextColor(getResources().getColor(R.color.white)); - findViewById(R.id.btOk).setEnabled(false); + tvAmount.setTextColor(getResources().getColor(R.color.white)); + btOk.setEnabled(false); return; } if (isSendMode && _amount.getValue() != null) { AmountValidation result = checkTransaction(); // Enable/disable Ok button - findViewById(R.id.btOk).setEnabled(result == AmountValidation.Ok - && !_amount.isZero()); + btOk.setEnabled(result == AmountValidation.Ok && !_amount.isZero()); } else { - findViewById(R.id.btOk).setEnabled(true); + btOk.setEnabled(true); } } @@ -451,7 +421,9 @@ private void checkEntry() { * that we have enough funds to send it. */ private AmountValidation checkSendAmount(Bitcoins satoshis) { - if (satoshis == null) return AmountValidation.Ok; //entering a fiat value + exchange is not availible + if (satoshis == null) { + return AmountValidation.Ok; //entering a fiat value + exchange is not availible + } try { WalletAccount.Receiver receiver = new WalletAccount.Receiver(Address.getNullAddress(_mbwManager.getNetwork()), satoshis); _account.checkAmount(receiver, _kbMinerFee, _amount); @@ -473,14 +445,20 @@ private enum AmountValidation { } private AmountValidation checkTransaction() { - Bitcoins satoshis = _amount.getAsBitcoin(_mbwManager.getExchangeRateManager()); + Bitcoins satoshis; + try { + satoshis = _amount.getAsBitcoin(_mbwManager.getExchangeRateManager()); + } catch (IllegalArgumentException ex){ + // something failed while calculating the bitcoin amount + return AmountValidation.Invalid; + } // Check whether we have sufficient funds, and whether the output is too small AmountValidation result = checkSendAmount(satoshis); if (result == AmountValidation.Ok) { - ((TextView) findViewById(R.id.tvAmount)).setTextColor(getResources().getColor(R.color.white)); + tvAmount.setTextColor(getResources().getColor(R.color.white)); } else { - ((TextView) findViewById(R.id.tvAmount)).setTextColor(getResources().getColor(R.color.red)); + tvAmount.setTextColor(getResources().getColor(R.color.red)); if (result == AmountValidation.NotEnoughFunds) { // We do not have enough funds if (_account.getBalance().getSpendableBalance() < satoshis.getLongValue()) { @@ -493,10 +471,11 @@ private AmountValidation checkTransaction() { String msg = getResources().getString(R.string.insufficient_funds_for_fee); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } - } else { + } + // else { // The amount we want to send is not large enough for the network to // accept it. Don't Toast about it, it's just annoying - } + // } } return result; } @@ -513,9 +492,9 @@ public void selectedCurrencyChanged(SelectedCurrencyChanged event) { private void updateExchangeRateDisplay() { Double exchangeRatePrice = _mbwManager.getCurrencySwitcher().getExchangeRatePrice(); - findViewById(R.id.btCurrency).setEnabled(exchangeRatePrice != null); + btCurrency.setEnabled(exchangeRatePrice != null); if (exchangeRatePrice != null) { updateAmountsDisplay(_numberEntry.getEntry()); } } -} \ No newline at end of file +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/WordAutoCompleterFragment.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/WordAutoCompleterFragment.java index b6774080aa..e3c6d6a6e3 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/WordAutoCompleterFragment.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/WordAutoCompleterFragment.java @@ -126,7 +126,7 @@ private boolean exactMatch(String entered){ } public void setCompletions(String[] completions) { - _completions = completions; + _completions = completions.clone(); showCompletionButtons(); } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/export/ExportAsQrCodeActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/export/ExportAsQrCodeActivity.java index 605df6ea73..7fc6bd9cee 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/export/ExportAsQrCodeActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/export/ExportAsQrCodeActivity.java @@ -46,14 +46,11 @@ import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; - import com.mycelium.wallet.MbwManager; import com.mycelium.wallet.R; import com.mycelium.wallet.Utils; import com.mycelium.wallet.activity.util.QrImageView; -import com.mycelium.wapi.wallet.AesKeyCipher; import com.mycelium.wapi.wallet.ExportableAccount; -import com.mycelium.wapi.wallet.KeyCipher; public class ExportAsQrCodeActivity extends Activity { @@ -64,13 +61,15 @@ public class ExportAsQrCodeActivity extends Activity { private Switch swSelectData; private boolean hasWarningAccepted = false; - public static Intent getIntent(Activity activity, ExportableAccount.Data accountData){ + public static Intent getIntent(Activity activity, ExportableAccount.Data accountData) { Intent intent = new Intent(activity, ExportAsQrCodeActivity.class); intent.putExtra(ACCOUNT, accountData); return intent; } - /** Called when the activity is first created. */ + /** + * Called when the activity is first created. + */ @Override public void onCreate(Bundle savedInstanceState) { this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); @@ -84,7 +83,7 @@ public void onCreate(Bundle savedInstanceState) { accountData = (ExportableAccount.Data) getIntent().getSerializableExtra(ACCOUNT); if (accountData == null || (!accountData.publicData.isPresent() && !accountData.privateData.isPresent()) - ){ + ) { finish(); return; } @@ -99,7 +98,7 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) { updateData(); } }); - }else{ + } else { swSelectData.setVisibility(View.GONE); findViewById(R.id.tvShow).setVisibility(View.GONE); } @@ -118,9 +117,9 @@ public boolean onLongClick(View view) { } private void updateData() { - if (isPrivateDataSelected()){ + if (isPrivateDataSelected()) { showPrivateData(); - }else{ + } else { showPublicData(); } } @@ -128,12 +127,12 @@ private void updateData() { private boolean isPrivateDataSelected() { if (accountData.privateData.isPresent()) { return swSelectData.isChecked(); - }else { + } else { return false; } } - private void setWarningVisibility(boolean showWarning){ + private void setWarningVisibility(boolean showWarning) { if (showWarning) { findViewById(R.id.llPrivKeyWarning).setVisibility(View.VISIBLE); findViewById(R.id.ivQrCode).setVisibility(View.GONE); @@ -151,41 +150,43 @@ private void setWarningVisibility(boolean showWarning){ } } - private void showPrivateData(){ + private void showPrivateData() { if (hasWarningAccepted) { setWarningVisibility(false); String privateData = accountData.privateData.get(); showData(privateData); - }else{ + } else { setWarningVisibility(true); } - ((TextView)findViewById(R.id.tvWarning)).setText(this.getString(R.string.export_warning_privkey)); + ((TextView) findViewById(R.id.tvWarning)).setText(this.getString(R.string.export_warning_privkey)); } - private void showPublicData(){ + private void showPublicData() { setWarningVisibility(false); String publicData = accountData.publicData.get(); showData(publicData); - ((TextView)findViewById(R.id.tvWarning)).setText(this.getString(R.string.export_warning_pubkey)); + ((TextView) findViewById(R.id.tvWarning)).setText(this.getString(R.string.export_warning_pubkey)); } - private void showData(final String data){ + private void showData(final String data) { // Set QR code QrImageView iv = (QrImageView) findViewById(R.id.ivQrCode); iv.setQrCode(data); // split the date in fragments with 8chars and a newline after three parts - String fragmentedData = ""; - int cnt=0; - for (String part : Utils.stringChopper(data, 8)){ + StringBuilder builder = new StringBuilder(); + int cnt = 0; + for (String part : Utils.stringChopper(data, 8)) { cnt++; - fragmentedData += part + (cnt%3==0 ? "\n":" "); + builder.append(part); + builder.append(cnt % 3 == 0 ? "\n" : " "); } + String fragmentedData = builder.toString(); - ((TextView)findViewById(R.id.tvShowData)).setText(fragmentedData); + ((TextView) findViewById(R.id.tvShowData)).setText(fragmentedData); findViewById(R.id.btCopyToClipboard).setOnClickListener(new OnClickListener() { @Override @@ -226,7 +227,7 @@ public void onClick(DialogInterface dialog, int id) { }); AlertDialog alertDialog = builder.create(); alertDialog.show(); - }else{ + } else { Utils.setClipboardString(data, ExportAsQrCodeActivity.this); Toast.makeText(ExportAsQrCodeActivity.this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show(); } @@ -251,7 +252,7 @@ public void onClick(DialogInterface dialog, int id) { }); AlertDialog alertDialog = builder.create(); alertDialog.show(); - }else{ + } else { Intent s = new Intent(android.content.Intent.ACTION_SEND); s.setType("text/plain"); s.putExtra(Intent.EXTRA_SUBJECT, getResources().getString(R.string.xpub_title)); @@ -260,4 +261,4 @@ public void onClick(DialogInterface dialog, int id) { } } -} \ No newline at end of file +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/export/MrdDecryptDataActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/export/MrdDecryptDataActivity.java index 79e824d0b8..c68aa8b1b4 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/export/MrdDecryptDataActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/export/MrdDecryptDataActivity.java @@ -173,12 +173,11 @@ private void updatePasswordText() { if (getPassword().length() != MrdExport.V1.V1_PASSPHRASE_LENGTH + 1) { ((TextView) findViewById(R.id.tvStatus)).setText(R.string.import_decrypt_key_enter_password); findViewById(R.id.tvStatus).setBackgroundColor(getResources().getColor(R.color.transparent)); - } else if (MrdExport.isChecksumValid(getPassword())) { - // Leave the status at what it is, it is updated by the progress - } else { + } else if (!MrdExport.isChecksumValid(getPassword())) { ((TextView) findViewById(R.id.tvStatus)).setText(R.string.import_decrypt_key_invalid_checksum); findViewById(R.id.tvStatus).setBackgroundColor(getResources().getColor(R.color.red)); } + // else Leave the status at what it is, it is updated by the progress } @@ -414,4 +413,4 @@ public void onResultReceived(ServiceTask result) { } } -} \ No newline at end of file +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/main/BalanceFragment.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/main/BalanceFragment.java index 7ac89ad934..c1620c05d4 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/main/BalanceFragment.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/main/BalanceFragment.java @@ -58,6 +58,7 @@ import com.mycelium.wapi.wallet.currency.CurrencyValue; import com.mycelium.wallet.event.*; import com.mycelium.wapi.wallet.WalletAccount; +import com.mycelium.wapi.wallet.currency.ExactBitcoinValue; import com.squareup.otto.Subscribe; import java.math.BigDecimal; @@ -180,8 +181,8 @@ private void updateUi() { updateUiKnownBalance(balance); // Set BTC rate - if (!_mbwManager.hasFiatCurrency() || _exchangeRatePrice == null) { - // No rate or no fiat set + if (!_mbwManager.hasFiatCurrency()) { + // No fiat currency selected by user _root.findViewById(R.id.tvBtcRate).setVisibility(View.INVISIBLE); } else if (_exchangeRatePrice == null) { // We have no price, exchange not available @@ -199,26 +200,18 @@ private void updateUi() { private void updateUiKnownBalance(CurrencyBasedBalance balance) { // Set Balance - String valueString = Utils.getFormattedValueWithUnit(balance.confirmed, _mbwManager); + String valueString = Utils.getFormattedValueWithUnit(balance.confirmed, _mbwManager.getBitcoinDenomination()); ((TextView) _root.findViewById(R.id.tvBalance)).setText(valueString); ((ProgressBar) _root.findViewById(R.id.pbProgress)).setVisibility( (balance.isSynchronizing ? View.VISIBLE : View.GONE)); // Show alternative values _tcdFiatDisplay.setFiatOnly(balance.confirmed.isBtc()); - BigDecimal btcValue; - if (balance.confirmed.isBtc()) { - btcValue = balance.confirmed.getValue(); - } else { - btcValue = balance.confirmed.getBitcoinValue(_mbwManager.getExchangeRateManager()).getValue(); - } - Long satoshis = btcValue == null ? 0 : Bitcoins.nearestValue(btcValue).getLongValue(); - - _tcdFiatDisplay.setValue(satoshis); + _tcdFiatDisplay.setValue(balance.confirmed); // Show/Hide Receiving if (balance.receiving.getValue().compareTo(BigDecimal.ZERO) > 0) { - String receivingString = Utils.getFormattedValueWithUnit(balance.receiving, _mbwManager); + String receivingString = Utils.getFormattedValueWithUnit(balance.receiving, _mbwManager.getBitcoinDenomination()); String receivingText = getResources().getString(R.string.receiving, receivingString); TextView tvReceiving = (TextView) _root.findViewById(R.id.tvReceiving); tvReceiving.setText(receivingText); @@ -231,7 +224,7 @@ private void updateUiKnownBalance(CurrencyBasedBalance balance) { // Show/Hide Sending if (balance.sending.getValue().compareTo(BigDecimal.ZERO) > 0) { - String sendingString = Utils.getFormattedValueWithUnit(balance.sending, _mbwManager); + String sendingString = Utils.getFormattedValueWithUnit(balance.sending, _mbwManager.getBitcoinDenomination()); String sendingText = getResources().getString(R.string.sending, sendingString); TextView tvSending = (TextView) _root.findViewById(R.id.tvSending); tvSending.setText(sendingText); @@ -252,11 +245,16 @@ private void setFiatValue(int textViewResourceId, CurrencyValue value, boolean h ) { tv.setVisibility(View.GONE); } else { - tv.setVisibility(View.VISIBLE); - long satoshis = value.getAsBitcoin(_mbwManager.getExchangeRateManager()).getLongValue(); - String converted = Utils.getFiatValueAsString(satoshis, _exchangeRatePrice); - String currency = _mbwManager.getFiatCurrency(); - tv.setText(getResources().getString(R.string.approximate_fiat_value, currency, converted)); + try { + long satoshis = value.getAsBitcoin(_mbwManager.getExchangeRateManager()).getLongValue(); + tv.setVisibility(View.VISIBLE); + String converted = Utils.getFiatValueAsString(satoshis, _exchangeRatePrice); + String currency = _mbwManager.getFiatCurrency(); + tv.setText(getResources().getString(R.string.approximate_fiat_value, currency, converted)); + }catch (IllegalArgumentException ex){ + // something failed while calculating the bitcoin amount + tv.setVisibility(View.GONE); + } } } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/main/BalanceMasterFragment.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/main/BalanceMasterFragment.java index 65b46f9378..b407b05eef 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/main/BalanceMasterFragment.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/main/BalanceMasterFragment.java @@ -64,7 +64,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa fragmentTransaction.replace(R.id.phFragmentBalance, new BalanceFragment()); fragmentTransaction.replace(R.id.phFragmentNotice, new NoticeFragment()); fragmentTransaction.replace(R.id.phFragmentLocalTrader, new LocalTraderFragment()); - fragmentTransaction.commit(); + fragmentTransaction.commitAllowingStateLoss(); return view; } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/main/TransactionArrayAdapter.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/main/TransactionArrayAdapter.java index ffa626541f..d8085e7bfc 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/main/TransactionArrayAdapter.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/main/TransactionArrayAdapter.java @@ -16,6 +16,7 @@ import com.mycelium.wallet.activity.util.TransactionConfirmationsDisplay; import com.mycelium.wallet.persistence.MetadataStorage; import com.mycelium.wapi.model.TransactionSummary; +import com.mycelium.wapi.wallet.currency.CurrencyValue; import java.text.DateFormat; import java.util.Date; @@ -70,10 +71,10 @@ public View getView(final int position, View convertView, ViewGroup parent) { // Determine Color int color; - if (record.value < 0) { - color = _context.getResources().getColor(R.color.red); - } else { + if (record.isIncoming) { color = _context.getResources().getColor(R.color.green); + } else { + color = _context.getResources().getColor(R.color.red); } // Set Date @@ -83,26 +84,41 @@ public View getView(final int position, View convertView, ViewGroup parent) { // Set value TextView tvAmount = (TextView) rowView.findViewById(R.id.tvAmount); - tvAmount.setText(_mbwManager.getBtcValueString(record.value)); + tvAmount.setText(Utils.getFormattedValueWithUnit(record.value, _mbwManager.getBitcoinDenomination())); tvAmount.setTextColor(color); - // Set fiat value + // Set alternative value TextView tvFiat = (TextView) rowView.findViewById(R.id.tvFiatAmount); - Double rate = _mbwManager.getCurrencySwitcher().getExchangeRatePrice(); - if (_mbwManager.hasFiatCurrency() && rate == null) { - _mbwManager.getExchangeRateManager().requestRefresh(); + String alternativeCurrency = _mbwManager.getCurrencySwitcher().getCurrentCurrency(); + + // if the current selected currency is the same as the transactions + if (alternativeCurrency.equals(record.value.getCurrency())) { + if (record.value.isBtc()) { + // use the current selected fiat currency + alternativeCurrency = _mbwManager.getCurrencySwitcher().getCurrentFiatCurrency(); + } else { + // always show BTC + alternativeCurrency = CurrencyValue.BTC; + } } - if (!_mbwManager.hasFiatCurrency() || rate == null) { - tvFiat.setVisibility(View.GONE); + + if (!alternativeCurrency.equals("")) { + CurrencyValue alternativeCurrencyValue = CurrencyValue.fromValue( + record.value, + alternativeCurrency, + _mbwManager.getExchangeRateManager()); + + if (alternativeCurrencyValue.getValue() == null) { + tvFiat.setVisibility(View.GONE); + } else { + tvFiat.setVisibility(View.VISIBLE); + tvFiat.setText(Utils.getFormattedValueWithUnit(alternativeCurrencyValue, _mbwManager.getBitcoinDenomination())); + tvFiat.setTextColor(color); + } } else { - tvFiat.setVisibility(View.VISIBLE); - String currency = _mbwManager.getFiatCurrency(); - String converted = Utils.getFiatValueAsString(record.value, rate); - tvFiat.setText(_context.getResources().getString(R.string.approximate_fiat_value, currency, converted)); - tvFiat.setTextColor(color); + tvFiat.setVisibility(View.GONE); } - // Show destination address and address label, if this address is in our address book TextView tvAddressLabel = (TextView) rowView.findViewById(R.id.tvAddressLabel); TextView tvDestAddress = (TextView) rowView.findViewById(R.id.tvDestAddress); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/main/TransactionHistoryFragment.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/main/TransactionHistoryFragment.java index 50211a56c4..4b896471e6 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/main/TransactionHistoryFragment.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/main/TransactionHistoryFragment.java @@ -44,15 +44,13 @@ import android.support.v7.app.ActionBarActivity; import android.support.v7.view.ActionMode; import android.view.*; -import android.widget.ArrayAdapter; import android.widget.ListView; -import android.widget.TextView; import android.widget.Toast; import com.commonsware.cwac.endless.EndlessAdapter; import com.google.common.base.Preconditions; import com.mrd.bitlib.model.Address; import com.mrd.bitlib.util.Sha256Hash; -import com.mycelium.wallet.CoinapultTransactionSummary; +import com.mycelium.wallet.coinapult.CoinapultTransactionSummary; import com.mycelium.wallet.MbwManager; import com.mycelium.wallet.R; import com.mycelium.wallet.Utils; @@ -70,7 +68,6 @@ import com.mycelium.wapi.wallet.WalletManager; import com.squareup.otto.Subscribe; -import java.text.DateFormat; import java.util.*; public class TransactionHistoryFragment extends Fragment { diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/AccountsFragment.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/AccountsFragment.java index 125aedffea..b870d82b65 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/AccountsFragment.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/AccountsFragment.java @@ -60,22 +60,25 @@ import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.mrd.bitlib.model.Address; -import com.mycelium.wallet.CoinapultManager; import com.mycelium.wallet.CurrencySwitcher; import com.mycelium.wallet.MbwManager; import com.mycelium.wallet.R; import com.mycelium.wallet.Utils; import com.mycelium.wallet.activity.AddAccountActivity; +import com.mycelium.wallet.activity.AddCoinapultAccountActivity; import com.mycelium.wallet.activity.MessageSigningActivity; import com.mycelium.wallet.activity.export.VerifyBackupActivity; import com.mycelium.wallet.activity.util.EnterAddressLabelUtil; import com.mycelium.wallet.activity.util.ToggleableCurrencyDisplay; +import com.mycelium.wallet.coinapult.CoinapultAccount; +import com.mycelium.wallet.coinapult.CoinapultManager; import com.mycelium.wallet.event.*; import com.mycelium.wallet.persistence.MetadataStorage; import com.mycelium.wapi.model.Balance; import com.mycelium.wapi.wallet.*; import com.mycelium.wapi.wallet.bip44.Bip44Account; import com.mycelium.wapi.wallet.bip44.Bip44PubOnlyAccount; +import com.mycelium.wapi.wallet.currency.CurrencySum; import com.mycelium.wapi.wallet.single.SingleAddressAccount; import com.squareup.otto.Subscribe; @@ -164,14 +167,15 @@ public void setUserVisibleHint(boolean isVisibleToUser) { @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { ActivityCompat.invalidateOptionsMenu(getActivity()); - if (requestCode == ADD_RECORD_RESULT_CODE && resultCode == AddAccountActivity.RESULT_COINAPULT) { - UUID accountid = (UUID) intent.getSerializableExtra(AddAccountActivity.RESULT_KEY); - _mbwManager.setSelectedAccount(accountid); - _mbwManager.getMetadataStorage().storeAccountLabel(accountid,"coinapultUSD"); - _focusedAccount = _mbwManager.getCoinapultManager(); + if (requestCode == ADD_RECORD_RESULT_CODE && resultCode == AddCoinapultAccountActivity.RESULT_COINAPULT) { + UUID accountId = (UUID) intent.getSerializableExtra(AddAccountActivity.RESULT_KEY); + CoinapultAccount account = (CoinapultAccount) _mbwManager.getWalletManager(false).getAccount(accountId); + _mbwManager.setSelectedAccount(accountId); + _focusedAccount = account; update(); return; } + if (requestCode == ADD_RECORD_RESULT_CODE && resultCode == Activity.RESULT_OK) { UUID accountid = (UUID) intent.getSerializableExtra(AddAccountActivity.RESULT_KEY); //check whether the account is active - we might have scanned the priv key for an archived watchonly @@ -369,21 +373,23 @@ private void update() { WalletAccount selectedAccount = _mbwManager.getSelectedAccount(); - Long spendableBalance = 0L; + CurrencySum totalSpendableBalance = new CurrencySum(); String activeTitle = getString(R.string.active_hd_accounts_name) + (activeHdRecords.isEmpty() ? " " + getString(R.string.active_accounts_empty) : ""); - LinearLayout activeHdAccountsView = createAccountViewList(activeTitle, activeHdRecords, selectedAccount, getSpendableBalance(activeHdAccounts)); + CurrencySum spendableBalanceHdAccounts = getSpendableBalance(activeHdAccounts); + LinearLayout activeHdAccountsView = createAccountViewList(activeTitle, activeHdRecords, selectedAccount, spendableBalanceHdAccounts); llRecords.addView(activeHdAccountsView); - spendableBalance = getSpendableBalance(activeHdAccounts); + totalSpendableBalance.add(spendableBalanceHdAccounts); if (!activeOtherRecords.isEmpty()) { - LinearLayout activeOtherAccountsView = createAccountViewList(getString(R.string.active_other_accounts_name), activeOtherRecords, selectedAccount, getSpendableBalance(activeOtherAccounts)); + CurrencySum spendableBalanceOtherAccounts = getSpendableBalance(activeOtherAccounts); + LinearLayout activeOtherAccountsView = createAccountViewList(getString(R.string.active_other_accounts_name), activeOtherRecords, selectedAccount, spendableBalanceOtherAccounts); llRecords.addView(activeOtherAccountsView); - spendableBalance += getSpendableBalance(activeOtherAccounts); + totalSpendableBalance.add(spendableBalanceOtherAccounts); // only show a totals row, if both account type exits - LinearLayout activeOtherSum = createActiveAccountBalanceSumView(spendableBalance); + LinearLayout activeOtherSum = createActiveAccountBalanceSumView(totalSpendableBalance); llRecords.addView(activeOtherSum); } @@ -394,7 +400,7 @@ private void update() { } } - private LinearLayout createActiveAccountBalanceSumView(Long spendableBalance) { + private LinearLayout createActiveAccountBalanceSumView(CurrencySum spendableBalance) { LinearLayout outer = new LinearLayout(getActivity()); outer.setOrientation(LinearLayout.VERTICAL); outer.setLayoutParams(_outerLayoutParameters); @@ -418,15 +424,15 @@ private LinearLayout createActiveAccountBalanceSumView(Long spendableBalance) { return outer; } - private long getSpendableBalance(List accounts) { - long balanceSum = 0; + private CurrencySum getSpendableBalance(List accounts) { + CurrencySum currencySum = new CurrencySum(); for (WalletAccount account : accounts) { - balanceSum += account.getBalance().getSpendableBalance(); + currencySum.add(account.getCurrencyBasedBalance().confirmed); } - return balanceSum; + return currencySum; } - private LinearLayout createAccountViewList(String title, List accounts, WalletAccount selectedAccount, Long spendableBalance) { + private LinearLayout createAccountViewList(String title, List accounts, WalletAccount selectedAccount, CurrencySum spendableBalance) { LinearLayout outer = new LinearLayout(getActivity()); outer.setOrientation(LinearLayout.VERTICAL); outer.setLayoutParams(_outerLayoutParameters); @@ -464,18 +470,17 @@ private LinearLayout createAccountViewList(String title, List acc return outer; } - private TextView createTitle(ViewGroup root, String title, Long balance) { + private TextView createTitle(ViewGroup root, String title, CurrencySum balance) { View view = _layoutInflater.inflate(R.layout.accounts_title_view, root, true); TextView tvTitle = (TextView) view.findViewById(R.id.tvTitle); tvTitle.setText(title); ToggleableCurrencyDisplay tvBalance = (ToggleableCurrencyDisplay) view.findViewById(R.id.tvBalance); if (balance != null) { - CurrencySwitcher currencySwitcher = _mbwManager.getCurrencySwitcher(); tvBalance.setEventBus(_mbwManager.getEventBus()); tvBalance.setCurrencySwitcher(_mbwManager.getCurrencySwitcher()); tvBalance.setValue(balance); - }else{ + } else { tvBalance.setVisibility(View.GONE); } @@ -555,11 +560,11 @@ private void updateIncludingMenus() { menus.add(R.menu.record_options_menu_active); } - if (account.isActive() && !(account instanceof CoinapultManager)) { + if (account.isActive() && !(account instanceof CoinapultAccount)) { menus.add(R.menu.record_options_menu_outputs); } - if (account instanceof CoinapultManager) { + if (account instanceof CoinapultAccount) { menus.add(R.menu.record_options_menu_set_coinapult_mail); } @@ -660,7 +665,7 @@ public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { } @Override - public void onDestroyActionMode (ActionMode actionMode){ + public void onDestroyActionMode(ActionMode actionMode) { currentActionMode = null; // Loose focus if (_focusedAccount != null) { @@ -668,15 +673,13 @@ public void onDestroyActionMode (ActionMode actionMode){ update(); } } - } - - ; - currentActionMode=parent.startSupportActionMode(actionMode); + }; + currentActionMode = parent.startSupportActionMode(actionMode); // Late set the focused record. We have to do this after // startSupportActionMode above, as it calls onDestroyActionMode when // starting for some reason, and this would clear the focus and force // an update. - _focusedAccount=account; + _focusedAccount = account; update(); } @@ -690,7 +693,9 @@ private void setCoinapultMail() { final EditText mailField = (EditText) diaView.findViewById(R.id.mail); mailField.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS); String email = _mbwManager.getMetadataStorage().getCoinapultMail(); - if (!email.isEmpty()) mailField.setText(email); + if (!email.isEmpty()) { + mailField.setText(email); + } b.setView(diaView); b.setPositiveButton(getString(R.string.button_done), new DialogInterface.OnClickListener() { @Override @@ -698,11 +703,14 @@ public void onClick(DialogInterface dialog, int which) { String mailText = mailField.getText().toString(); if (Utils.isValidEmailAddress(mailText)) { Optional mail; - if (mailText.isEmpty()) mail = Optional.absent(); - else mail = Optional.of(mailText); + if (mailText.isEmpty()) { + mail = Optional.absent(); + } else { + mail = Optional.of(mailText); + } _progress.setCancelable(false); _progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); - _progress.setMessage(getString(R.string.setting_coinapult_email)); + _progress.setMessage(getString(R.string.coinapult_setting_email)); _progress.show(); _mbwManager.getMetadataStorage().setCoinapultMail(mailText); new SetCoinapultMailAsyncTask(mail).execute(); @@ -732,7 +740,7 @@ private void verifyCoinapultMail() { // check if there is a probable verification link in the clipboard and if so, pre-fill the textbox String clipboardString = Utils.getClipboardString(getActivity()); - if (!Strings.isNullOrEmpty(clipboardString) && clipboardString.contains("coinapult.com")){ + if (!Strings.isNullOrEmpty(clipboardString) && clipboardString.contains("coinapult.com")) { verificationTextField.setText(clipboardString); } @@ -743,7 +751,7 @@ public void onClick(DialogInterface dialog, int which) { String verification = verificationTextField.getText().toString(); _progress.setCancelable(false); _progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); - _progress.setMessage(getString(R.string.verifying_coinapult_email)); + _progress.setMessage(getString(R.string.coinapult_verifying_email)); _progress.show(); new VerifyCoinapultMailAsyncTask(verification, email).execute(); dialog.dismiss(); @@ -776,9 +784,9 @@ protected Boolean doInBackground(Void... params) { protected void onPostExecute(Boolean success) { _progress.dismiss(); if (success) { - Utils.showSimpleMessageDialog(getActivity(), R.string.set_coinapult_mail_please_verify); + Utils.showSimpleMessageDialog(getActivity(), R.string.coinapult_set_mail_please_verify); } else { - Utils.showSimpleMessageDialog(getActivity(), R.string.set_coinapult_mail_failed); + Utils.showSimpleMessageDialog(getActivity(), R.string.coinapult_set_mail_failed); } } } @@ -801,15 +809,15 @@ protected Boolean doInBackground(Void... params) { protected void onPostExecute(Boolean success) { _progress.dismiss(); if (success) { - Utils.showSimpleMessageDialog(getActivity(), R.string.verify_coinapult_mail_success); + Utils.showSimpleMessageDialog(getActivity(), R.string.coinapult_verify_mail_success); } else { - Utils.showSimpleMessageDialog(getActivity(), R.string.verify_coinapult_mail_error); + Utils.showSimpleMessageDialog(getActivity(), R.string.coinapult_verify_mail_error); } } } - private void verifySingleKeyBackup(){ + private void verifySingleKeyBackup() { if (!AccountsFragment.this.isAdded()) { return; } @@ -851,9 +859,9 @@ public void run() { if (!AccountsFragment.this.isAdded()) { return; } - if (_focusedAccount instanceof CoinapultManager) { - CoinapultManager focusedAccount = (CoinapultManager) _focusedAccount; - MessageSigningActivity.callMe(getActivity(), focusedAccount.getAccountKey()); + if (_focusedAccount instanceof CoinapultAccount) { + CoinapultManager coinapultManager = _mbwManager.getCoinapultManager(); + MessageSigningActivity.callMe(getActivity(), coinapultManager.getAccountKey()); } else if (_focusedAccount instanceof SingleAddressAccount) { MessageSigningActivity.callMe(getActivity(), (SingleAddressAccount) _focusedAccount); } else { @@ -874,7 +882,7 @@ private void toastSelectedAccountChanged(WalletAccount account) { _toaster.toast(getString(R.string.selected_archived_warning), true); } else if (account instanceof Bip44Account) { _toaster.toast(getString(R.string.selected_hd_info), true); - } else { + } else if (account instanceof SingleAddressAccount) { _toaster.toast(getString(R.string.selected_single_info), true); } } @@ -893,8 +901,9 @@ public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.miAddRecord) { AddAccountActivity.callMe(this, ADD_RECORD_RESULT_CODE); return true; - } else if (item.getItemId() == R.id.miAddUsdAccount) { - AddAccountActivity.callMe(this, ADD_RECORD_RESULT_CODE, true); + } else if (item.getItemId() == R.id.miAddFiatAccount) { + Intent intent = AddCoinapultAccountActivity.getIntent(getActivity()); + this.startActivityForResult(intent, ADD_RECORD_RESULT_CODE); return true; } else if (item.getItemId() == R.id.miLockKeys) { lock(); @@ -1068,7 +1077,7 @@ private void archiveSelected() { if (!AccountsFragment.this.isAdded()) { return; } - if (_focusedAccount instanceof CoinapultManager){ + if (_focusedAccount instanceof CoinapultAccount) { _mbwManager.runPinProtectedFunction(AccountsFragment.this.getActivity(), new Runnable() { @Override @@ -1193,6 +1202,11 @@ public void run() { } }; + @Subscribe() + public void onExtraAccountsChanged(ExtraAccountsChanged event) { + update(); + } + @Subscribe public void addressChanged(ReceivingAddressChanged event) { update(); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/DarkThemeChangeLog.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/DarkThemeChangeLog.java index a787c32562..aaf68fd255 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/DarkThemeChangeLog.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/DarkThemeChangeLog.java @@ -39,7 +39,7 @@ public class DarkThemeChangeLog extends ChangeLog { public static final String DARK_THEME_CSS = - "body { color: #ffffff; background-color: #282828; }\n h1 { padding-bottom: 2px; margin-bottom:0px; line-height:100%; } \n ul{ margin-top:1px; }" + "\n" + DEFAULT_CSS; + "body { color: #ffffff; background-color: #282828; }\n h1 { padding-bottom: 2px; margin-bottom:0px; line-height:100%; } \n ul{ margin-top:1px; }" + "\n a{ color: #5fcbf2; }" + "\n" + DEFAULT_CSS; public DarkThemeChangeLog(Context context) { super(context, DARK_THEME_CSS); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/HDSigningActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/HDSigningActivity.java index f363655aee..576bfe8698 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/HDSigningActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/HDSigningActivity.java @@ -95,8 +95,6 @@ private View getItemView(Address address) { ll.setLayoutParams(WCWC); ll.setPadding(10, 10, 10, 10); - String addressString = address.toString(); - // Add address chunks AddressLabel addressLabel = new AddressLabel(this); addressLabel.setAddress(address); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.java index 0fc1f4bc30..4223a8cd1f 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/ModernMain.java @@ -59,6 +59,7 @@ import com.mycelium.wallet.activity.send.InstantWalletActivity; import com.mycelium.wallet.activity.settings.SettingsActivity; import com.mycelium.wallet.bitid.ExternalService; +import com.mycelium.wallet.coinapult.CoinapultAccount; import com.mycelium.wallet.event.*; import com.mycelium.wallet.external.cashila.activity.CashilaPaymentsActivity; import com.mycelium.wapi.api.response.Feature; @@ -67,6 +68,7 @@ import de.cketti.library.changelog.ChangeLog; import info.guardianproject.onionkit.ui.OrbotHelper; +import java.util.Date; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -75,6 +77,8 @@ public class ModernMain extends ActionBarActivity { public static final int GENERIC_SCAN_REQUEST = 4; private static final int REQUEST_SETTING_CHANGED = 5; + public static final int MIN_AUTOSYNC_INTERVAL = 1 * 60 * 1000; + public static final String LAST_SYNC = "LAST_SYNC"; private MbwManager _mbwManager; ViewPager mViewPager; @@ -83,6 +87,7 @@ public class ModernMain extends ActionBarActivity { ActionBar.Tab mAccountsTab; private MenuItem refreshItem; private Toaster _toaster; + private long _lastSync = 0; @Override public void onCreate(Bundle savedInstanceState) { @@ -137,12 +142,22 @@ public BitmapDrawable call() throws Exception { _mbwManager.getVersionManager().showFeatureWarningIfNeeded(this, Feature.APP_START); + if (savedInstanceState != null) { + _lastSync = savedInstanceState.getLong(LAST_SYNC, 0); + } + + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putLong(LAST_SYNC, _lastSync); } private void checkTorState() { - if (_mbwManager.getTorMode() == ServerEndpointType.Types.ONLY_TOR){ + if (_mbwManager.getTorMode() == ServerEndpointType.Types.ONLY_TOR) { OrbotHelper obh = new OrbotHelper(this); - if (!obh.isOrbotRunning()){ + if (!obh.isOrbotRunning()) { obh.requestOrbotStart(this); } } @@ -153,16 +168,21 @@ private void checkTorState() { protected void onResume() { _mbwManager.getEventBus().register(this); - // Start WAPI & classic synchronization as a delayed action. This way we don't immediately block the account + // Start WAPI as a delayed action. This way we don't immediately block the account // while synchronizing Handler h = new Handler(); - h.postDelayed(new Runnable() { - @Override - public void run() { - _mbwManager.getVersionManager().checkForUpdate(); - _mbwManager.getWalletManager(false).startSynchronization(); - } - }, 5); + if (_lastSync == 0 || new Date().getTime() - _lastSync > MIN_AUTOSYNC_INTERVAL) { + h.postDelayed(new Runnable() { + @Override + public void run() { + _mbwManager.getVersionManager().checkForUpdate(); + _mbwManager.getWalletManager(false).startSynchronization(); + _mbwManager.getExchangeRateManager().requestRefresh(); + } + }, 70); + _lastSync = new Date().getTime(); + } + supportInvalidateOptionsMenu(); super.onResume(); @@ -222,7 +242,7 @@ public boolean onPrepareOptionsMenu(Menu menu) { final boolean isRecords = tabIdx == 0; final boolean locked = _mbwManager.isKeyManagementLocked(); Preconditions.checkNotNull(menu.findItem(R.id.miAddRecord)).setVisible(isRecords && !locked); - Preconditions.checkNotNull(menu.findItem(R.id.miAddUsdAccount)).setVisible(isRecords && !locked && !_mbwManager.hasUsdAccount()); + Preconditions.checkNotNull(menu.findItem(R.id.miAddFiatAccount)).setVisible(isRecords); // Lock menu final boolean hasPin = _mbwManager.isPinProtected(); @@ -273,13 +293,15 @@ public boolean onOptionsItemSelected(MenuItem item) { } else if (itemId == R.id.miBackup) { Utils.pinProtectedWordlistBackup(this); return true; - //with wordlists, we just need to backup and verify in one step - //} else if (itemId == R.id.miVerifyBackup) { - // VerifyBackupActivity.callMe(this); - // return true; + //with wordlists, we just need to backup and verify in one step + //} else if (itemId == R.id.miVerifyBackup) { + // VerifyBackupActivity.callMe(this); + // return true; } else if (itemId == R.id.miRefresh) { //switch server every third time the refresh button gets hit - if (new Random().nextInt(3) == 0) _mbwManager.switchServer(); + if (new Random().nextInt(3) == 0) { + _mbwManager.switchServer(); + } _mbwManager.getWalletManager(false).startSynchronization(); } else if (itemId == R.id.miExplore) { _mbwManager.getExploreHelper().redirectToCoinmap(this); @@ -291,7 +313,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } else if (itemId == R.id.miRescanTransactions) { _mbwManager.getSelectedAccount().dropCachedData(); _mbwManager.getWalletManager(false).startSynchronization(); - } else if ( itemId == R.id.miSepaSend) { + } else if (itemId == R.id.miSepaSend) { _mbwManager.getVersionManager().showFeatureWarningIfNeeded(this, Feature.CASHILA, true, new Runnable() { @Override public void run() { @@ -348,12 +370,12 @@ public void setRefreshAnimation() { if (_mbwManager.getTorMode() == ServerEndpointType.Types.ONLY_TOR && _mbwManager.getTorManager() != null) { ivTorIcon.setVisibility(View.VISIBLE); - if (_mbwManager.getTorManager().getInitState()==100) { + if (_mbwManager.getTorManager().getInitState() == 100) { ivTorIcon.setImageResource(R.drawable.tor); - }else{ + } else { ivTorIcon.setImageResource(R.drawable.tor_gray); } - }else{ + } else { ivTorIcon.setVisibility(View.GONE); } @@ -392,7 +414,7 @@ public void transactionBroadcasted(TransactionBroadcasted event) { public void onNewFeatureWarnings(final FeatureWarningsAvailable event) { _mbwManager.getVersionManager().showFeatureWarningIfNeeded(this, Feature.MAIN_SCREEN); - if (_mbwManager.getSelectedAccount() instanceof CoinapultManager){ + if (_mbwManager.getSelectedAccount() instanceof CoinapultAccount) { _mbwManager.getVersionManager().showFeatureWarningIfNeeded(this, Feature.COINAPULT); } } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/RecordRowBuilder.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/RecordRowBuilder.java index 6ca8c1981b..562ff3cf68 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/RecordRowBuilder.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/modern/RecordRowBuilder.java @@ -53,6 +53,8 @@ import com.mycelium.wapi.wallet.WalletAccount; import com.mycelium.wapi.wallet.bip44.Bip44Account; import com.mycelium.wapi.wallet.bip44.Bip44PubOnlyAccount; +import com.mycelium.wapi.wallet.currency.CurrencyBasedBalance; +import com.mycelium.wapi.wallet.currency.CurrencySum; import com.mycelium.wapi.wallet.single.SingleAddressAccount; public class RecordRowBuilder { @@ -149,9 +151,9 @@ public View buildRecordView(ViewGroup parent, WalletAccount walletAccount, boole // Set balance if (walletAccount.isActive()) { - Balance balance = walletAccount.getBalance(); + CurrencyBasedBalance balance = walletAccount.getCurrencyBasedBalance(); rowView.findViewById(R.id.tvBalance).setVisibility(View.VISIBLE); - String balanceString = mbwManager.getBtcValueString(balance.confirmed + balance.pendingChange); + String balanceString = Utils.getFormattedValueWithUnit(balance.confirmed, mbwManager.getBitcoinDenomination()); TextView tvBalance = ((TextView) rowView.findViewById(R.id.tvBalance)); tvBalance.setText(balanceString); tvBalance.setTextColor(textColor); @@ -182,7 +184,7 @@ public View buildRecordView(ViewGroup parent, WalletAccount walletAccount, boole return rowView; } - public View buildTotalView(LinearLayout parent, long balanceSum) { + public View buildTotalView(LinearLayout parent, CurrencySum balanceSum) { View rowView = inflater.inflate(R.layout.record_row_total, parent, false); ToggleableCurrencyButton tcdBalance = ((ToggleableCurrencyButton) rowView.findViewById(R.id.tcdBalance)); tcdBalance.setEventBus(mbwManager.getEventBus()); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopActivity.java index 1bc7298314..9293ab706b 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopActivity.java @@ -62,6 +62,7 @@ import com.mycelium.wapi.model.TransactionDetails; import com.mycelium.wapi.model.TransactionSummary; import com.mycelium.wapi.wallet.WalletAccount; +import com.mycelium.wapi.wallet.currency.ExactBitcoinValue; import com.squareup.okhttp.*; import java.io.IOException; @@ -163,7 +164,10 @@ private void updateUi(TransactionSummary transactionSummary) { // Set amount long amountSatoshis = getPaymentAmountSatoshis(transactionSummary); String value = _mbwManager.getBtcValueString(amountSatoshis); - String fiatValue = _mbwManager.getCurrencySwitcher().getFormattedFiatValue(amountSatoshis, true); + String fiatValue = _mbwManager.getCurrencySwitcher().getFormattedFiatValue( + ExactBitcoinValue.from(amountSatoshis), + true + ); String fiatAppendment = ""; if (!Strings.isNullOrEmpty(fiatValue)) { fiatAppendment = " (" + fiatValue + ")"; @@ -207,10 +211,10 @@ private URL getUrl(String pParam) { } private long getPaymentAmountSatoshis(TransactionSummary transactionSummary) { - long amountSatoshis = transactionSummary.value; - if (amountSatoshis < 0) { - amountSatoshis = -amountSatoshis; + if (!(transactionSummary.value.isBtc())) { + return 0; } + long amountSatoshis = ((ExactBitcoinValue) transactionSummary.value).getLongValue(); TransactionDetails transactionDetails = _mbwManager.getSelectedAccount().getTransactionDetails(transactionSummary.txid); amountSatoshis -= getFee(transactionDetails); return amountSatoshis; diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopSelectTransactionActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopSelectTransactionActivity.java index b7ef109dff..d02159a833 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopSelectTransactionActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopSelectTransactionActivity.java @@ -190,7 +190,7 @@ public void onCreate(Bundle savedInstanceState) { List list = new ArrayList(); for (TransactionSummary transactionSummary : history) { - if (transactionSummary.value >= 0L) { + if (transactionSummary.isIncoming) { // We are only interested in payments continue; } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopUtils.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopUtils.java index d508b0ff2d..b9d0d01117 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopUtils.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/pop/PopUtils.java @@ -37,6 +37,7 @@ import com.mycelium.wallet.persistence.MetadataStorage; import com.mycelium.wallet.pop.PopRequest; import com.mycelium.wapi.model.TransactionSummary; +import com.mycelium.wapi.wallet.currency.BitcoinValue; class PopUtils { public static boolean matches(PopRequest popRequest, MetadataStorage metadataStorage, TransactionSummary transactionSummary) { @@ -44,7 +45,14 @@ public static boolean matches(PopRequest popRequest, MetadataStorage metadataSto return false; } Long amountSatoshis = popRequest.getAmountSatoshis(); - if (amountSatoshis != null && amountSatoshis != -transactionSummary.value) { + Long txSatoshis; + if (transactionSummary.value.isBtc()) { + txSatoshis = ((BitcoinValue) transactionSummary.value).getLongValue(); + } else { + txSatoshis = -1L; + } + + if (amountSatoshis != null && !amountSatoshis.equals(txSatoshis)) { return false; } if (popRequest.getLabel() != null) { diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/receive/ReceiveCoinsActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/receive/ReceiveCoinsActivity.java index e916ce7f09..2233d1cffe 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/receive/ReceiveCoinsActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/receive/ReceiveCoinsActivity.java @@ -51,7 +51,9 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; - +import butterknife.ButterKnife; +import butterknife.InjectView; +import butterknife.OnClick; import com.google.common.base.Preconditions; import com.mrd.bitlib.model.Address; import com.mrd.bitlib.util.CoinUtil; @@ -60,8 +62,9 @@ import com.mycelium.wallet.Utils; import com.mycelium.wallet.activity.GetAmountActivity; import com.mycelium.wallet.activity.util.QrImageView; +import com.mycelium.wapi.wallet.currency.BitcoinValue; import com.mycelium.wapi.wallet.currency.CurrencyValue; -import com.mycelium.wapi.wallet.currency.ExactBitcoinValue; +import com.mycelium.wapi.wallet.currency.ExchangeBasedBitcoinValue; //todo HD for the future: keep receiving slots for 20 addresses. assign a name @@ -69,10 +72,21 @@ public class ReceiveCoinsActivity extends Activity { private static final int GET_AMOUNT_RESULT_CODE = 1; + @InjectView(R.id.tvAmountLabel) TextView tvAmountLabel; + @InjectView(R.id.tvAmount) TextView tvAmount; + @InjectView(R.id.tvWarning) TextView tvWarning; + @InjectView(R.id.tvTitle) TextView tvTitle; + @InjectView(R.id.tvAddress1) TextView tvAddress1; + @InjectView(R.id.tvAddress2) TextView tvAddress2; + @InjectView(R.id.tvAddress3) TextView tvAddress3; + @InjectView(R.id.ivNfc) ImageView ivNfc; + @InjectView(R.id.ivQrCode) QrImageView ivQrCode; + @InjectView(R.id.btShare) Button btShare; + private MbwManager _mbwManager; private Address _address; private boolean _havePrivateKey; - private Long _amount; + private CurrencyValue _amount; public static void callMe(Activity currentActivity, Address address, boolean havePrivateKey) { Intent intent = new Intent(currentActivity, ReceiveCoinsActivity.class); @@ -90,6 +104,7 @@ public void onCreate(Bundle savedInstanceState) { this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); super.onCreate(savedInstanceState); setContentView(R.layout.receive_coins_activity); + ButterKnife.inject(this); _mbwManager = MbwManager.getInstance(getApplication()); @@ -99,17 +114,12 @@ public void onCreate(Bundle savedInstanceState) { // Load saved state if (savedInstanceState != null) { - _amount = savedInstanceState.getLong("amount", -1); - if (_amount == -1) { - _amount = null; - } + _amount = (CurrencyValue) savedInstanceState.getSerializable("amount"); } - // Enter Amount - findViewById(R.id.btEnterAmount).setOnClickListener(amountClickListener); // Amount Hint - ((TextView) findViewById(R.id.tvAmount)).setHint(getResources().getString(R.string.amount_hint_denomination, + tvAmount.setHint(getResources().getString(R.string.amount_hint_denomination, _mbwManager.getBitcoinDenomination().toString())); shareByNfc(); @@ -117,7 +127,7 @@ public void onCreate(Bundle savedInstanceState) { @TargetApi(16) protected void shareByNfc() { - if (Build.VERSION.SDK_INT < 16){ + if (Build.VERSION.SDK_INT < 16) { // the function isNdefPushEnabled is only available for SdkVersion >= 16 // We would be theoretically able to push the message over Ndef, but it is not // possible to check if Ndef/NFC is available or not - so dont try it at all, if @@ -126,8 +136,7 @@ protected void shareByNfc() { } NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this); - View ivNfc = findViewById(R.id.ivNfc); - if (nfc!=null && nfc.isNdefPushEnabled() ) { + if (nfc != null && nfc.isNdefPushEnabled()) { nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() { @Override public NdefMessage createNdefMessage(NfcEvent event) { @@ -150,7 +159,7 @@ public void onClick(View v) { @Override protected void onSaveInstanceState(Bundle outState) { if (_amount != null) { - outState.putLong("amount", _amount); + outState.putSerializable("amount", _amount); } super.onSaveInstanceState(outState); } @@ -161,38 +170,50 @@ protected void onResume() { super.onResume(); } + BitcoinValue getBitcoinAmount() { + if (_amount == null) { + return null; + } + + if (!_amount.isBtc()) { + // convert the amount to btc, but only once and stay within btc for all next calls + _amount = ExchangeBasedBitcoinValue.fromValue(_amount, _mbwManager.getExchangeRateManager()); + } + + return (BitcoinValue) _amount; + } + private void updateUi() { final String qrText = getPaymentUri(); if (_amount == null) { - ((TextView) findViewById(R.id.tvTitle)).setText(R.string.bitcoin_address_title); - ((Button) findViewById(R.id.btShare)).setText(R.string.share_bitcoin_address); - ((TextView) findViewById(R.id.tvAmountLabel)).setText(R.string.optional_amount); - ((TextView) findViewById(R.id.tvAmount)).setText(""); + tvTitle.setText(R.string.bitcoin_address_title); + btShare.setText(R.string.share_bitcoin_address); + tvAmountLabel.setText(R.string.optional_amount); + tvAmount.setText(""); } else { - ((TextView) findViewById(R.id.tvTitle)).setText(R.string.payment_request); - ((Button) findViewById(R.id.btShare)).setText(R.string.share_payment_request); - ((TextView) findViewById(R.id.tvAmountLabel)).setText(R.string.amount_title); - ((TextView) findViewById(R.id.tvAmount)).setText(_mbwManager.getBtcValueString(_amount)); + tvTitle.setText(R.string.payment_request); + btShare.setText(R.string.share_payment_request); + tvAmountLabel.setText(R.string.amount_title); + tvAmount.setText( + Utils.getFormattedValueWithUnit(getBitcoinAmount(), _mbwManager.getBitcoinDenomination()) + ); } // QR code - ImageView imageView = (ImageView) findViewById(R.id.ivQrCode); - //two-step cast to not confuse lint - QrImageView iv = (QrImageView) imageView; - iv.setQrCode(qrText); + ivQrCode.setQrCode(qrText); // Show warning if the record has no private key if (_havePrivateKey) { - findViewById(R.id.tvWarning).setVisibility(View.GONE); + tvWarning.setVisibility(View.GONE); } else { - findViewById(R.id.tvWarning).setVisibility(View.VISIBLE); + tvWarning.setVisibility(View.VISIBLE); } String[] addressStrings = Utils.stringChopper(getBitcoinAddress(), 12); - ((TextView) findViewById(R.id.tvAddress1)).setText(addressStrings[0]); - ((TextView) findViewById(R.id.tvAddress2)).setText(addressStrings[1]); - ((TextView) findViewById(R.id.tvAddress3)).setText(addressStrings[2]); + tvAddress1.setText(addressStrings[0]); + tvAddress2.setText(addressStrings[1]); + tvAddress3.setText(addressStrings[2]); updateAmount(); } @@ -200,18 +221,20 @@ private void updateUi() { private void updateAmount() { if (_amount == null) { // No amount to show - ((TextView) findViewById(R.id.tvAmount)).setText(""); + tvAmount.setText(""); } else { // Set Amount - ((TextView) findViewById(R.id.tvAmount)).setText(_mbwManager.getBtcValueString(_amount)); + tvAmount.setText( + Utils.getFormattedValueWithUnit(getBitcoinAmount(), _mbwManager.getBitcoinDenomination()) + ); } } private String getPaymentUri() { final StringBuilder uri = new StringBuilder("bitcoin:"); - uri.append(getBitcoinAddress()); + uri.append(getBitcoinAddress()); if (_amount != null) { - uri.append("?amount=").append(CoinUtil.valueString(_amount, false)); + uri.append("?amount=").append(CoinUtil.valueString(getBitcoinAmount().getLongValue(), false)); } return uri.toString(); } @@ -221,8 +244,8 @@ private String getBitcoinAddress() { } public void shareRequest(View view) { - Intent s = new Intent(android.content.Intent.ACTION_SEND); - s.setType("text/plain"); + Intent s = new Intent(android.content.Intent.ACTION_SEND); + s.setType("text/plain"); if (_amount == null) { s.putExtra(Intent.EXTRA_SUBJECT, getResources().getString(R.string.bitcoin_address_title)); s.putExtra(Intent.EXTRA_TEXT, getBitcoinAddress()); @@ -236,7 +259,7 @@ public void shareRequest(View view) { public void copyToClipboard(View view) { String text; - if (_amount == null) { + if (CurrencyValue.isNullOrZero(_amount)) { text = getBitcoinAddress(); } else { text = getPaymentUri(); @@ -248,19 +271,20 @@ public void copyToClipboard(View view) { public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { if (requestCode == GET_AMOUNT_RESULT_CODE && resultCode == RESULT_OK) { // Get result from address chooser (may be null) - _amount = (Long) intent.getSerializableExtra(GetAmountActivity.AMOUNT_SATOSHI); + _amount = (CurrencyValue) intent.getSerializableExtra(GetAmountActivity.AMOUNT); } else { super.onActivityResult(requestCode, resultCode, intent); } } - private OnClickListener amountClickListener = new OnClickListener() { - - @Override - public void onClick(View arg0) { - ExactBitcoinValue amountToSend = ExactBitcoinValue.from(_amount); - GetAmountActivity.callMe(ReceiveCoinsActivity.this, amountToSend, GET_AMOUNT_RESULT_CODE); + @OnClick(R.id.btEnterAmount) + public void onEnterClick() { + if (_amount == null) { + GetAmountActivity.callMe(ReceiveCoinsActivity.this, null, GET_AMOUNT_RESULT_CODE); + } else { + // call the amount activity with the exact amount, so that the user sees the same amount he had entered + // it in non-BTC + GetAmountActivity.callMe(ReceiveCoinsActivity.this, _amount.getExactValueIfPossible(), GET_AMOUNT_RESULT_CODE); } - }; - -} \ No newline at end of file + } +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/send/InstantWalletActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/send/InstantWalletActivity.java index 59f9e877fe..6a792c3b1c 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/send/InstantWalletActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/send/InstantWalletActivity.java @@ -126,12 +126,13 @@ protected void onResume() { public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { if (requestCode == REQUEST_SCAN) { - if (resultCode == RESULT_OK) { - // We don't call finish() here, so that this activity stays on the back stack. - // So the user can click back and scan the next cold storage. - } else { + if (resultCode != RESULT_OK) { ScanActivity.toastScanError(resultCode, intent, this); } + // else { + // We don't call finish() here, so that this activity stays on the back stack. + // So the user can click back and scan the next cold storage. + // } } else if (requestCode == REQUEST_TREZOR){ if (resultCode == RESULT_OK) { finish(); @@ -154,4 +155,4 @@ public void finish() { MbwManager.getInstance(this).forgetColdStorageWalletManager(); super.finish(); } -} \ No newline at end of file +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/send/SendMainActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/send/SendMainActivity.java index 3548e42392..a8ab4f41c3 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/send/SendMainActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/send/SendMainActivity.java @@ -43,12 +43,12 @@ import android.os.AsyncTask; import android.os.Bundle; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.Window; -import android.widget.Button; -import android.widget.TextView; -import android.widget.Toast; +import android.widget.*; +import butterknife.ButterKnife; +import butterknife.InjectView; +import butterknife.OnClick; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Strings; @@ -73,6 +73,7 @@ import com.mycelium.wallet.activity.modern.AddressBookFragment; import com.mycelium.wallet.activity.modern.GetFromAddressBookActivity; import com.mycelium.wallet.bitid.ExternalService; +import com.mycelium.wallet.coinapult.CoinapultAccount; import com.mycelium.wallet.event.ExchangeRatesRefreshed; import com.mycelium.wallet.event.SelectedCurrencyChanged; import com.mycelium.wallet.event.SyncFailed; @@ -84,12 +85,11 @@ import com.mycelium.wapi.wallet.WalletAccount; import com.mycelium.wapi.wallet.WalletManager; import com.mycelium.wapi.wallet.bip44.Bip44AccountExternalSignature; -import com.mycelium.wapi.wallet.currency.CurrencyValue; -import com.mycelium.wapi.wallet.currency.ExactBitcoinValue; -import com.mycelium.wapi.wallet.currency.ExactFiatValue; +import com.mycelium.wapi.wallet.currency.*; import com.squareup.otto.Subscribe; import org.bitcoin.protocols.payments.PaymentACK; +import java.math.BigDecimal; import java.util.Arrays; import java.util.UUID; @@ -105,10 +105,9 @@ public class SendMainActivity extends Activity { private static final int REQUEST_PAYMENT_HANDLER = 8; public static final String RAW_PAYMENT_REQUEST = "rawPaymentRequest"; private BillPay _sepaPayment; - public static final String AMOUNT_TO_SEND = "amountToSend"; - public static final String ACCOUNT = "account"; + private static final String AMOUNT = "amount"; public static final String IS_COLD_STORAGE = "isColdStorage"; public static final String RECEIVING_ADDRESS = "receivingAddress"; public static final String HD_KEY = "hdKey"; @@ -124,22 +123,46 @@ private enum TransactionStatus { MissingArguments, OutputTooSmall, InsufficientFunds, OK } + @InjectView(R.id.tvAmount) TextView tvAmount; + @InjectView(R.id.tvError) TextView tvError; + @InjectView(R.id.tvAmountFiat) TextView tvAmountFiat; + @InjectView(R.id.tvAmountTitle) TextView tvAmountTitle; + @InjectView(R.id.tvUnconfirmedWarning) TextView tvUnconfirmedWarning; + @InjectView(R.id.tvReceiver) TextView tvReceiver; + @InjectView(R.id.tvRecipientTitle) TextView tvRecipientTitle; + @InjectView(R.id.tvWarning) TextView tvWarning; + @InjectView(R.id.tvReceiverLabel) TextView tvReceiverLabel; + @InjectView(R.id.tvReceiverAddress) TextView tvReceiverAddress; + @InjectView(R.id.tvTransactionLabelTitle) TextView tvTransactionLabelTitle; + @InjectView(R.id.tvTransactionLabel) TextView tvTransactionLabel; + @InjectView(R.id.tvFeeValue) TextView tvFeeValue; + @InjectView(R.id.btEnterAmount) ImageButton btEnterAmount; + @InjectView(R.id.btFeeLvl) Button btFeeLvl; + @InjectView(R.id.btClipboard) Button btClipboard; + @InjectView(R.id.btSend) Button btSend; + @InjectView(R.id.btAddressBook) Button btAddressBook; + @InjectView(R.id.btManualEntry) Button btManualEntry; + @InjectView(R.id.btSepaTransfer) Button btSepaTransfer; + @InjectView(R.id.btScan) Button btScan; + @InjectView(R.id.pbSend) ProgressBar pbSend; + @InjectView(R.id.llFee) LinearLayout llFee; + @InjectView(R.id.llEnterRecipient) LinearLayout llEnterRecipient; + @InjectView(R.id.llRecipientAddress) LinearLayout llRecipientAddress; + private MbwManager _mbwManager; private PaymentRequestHandler _paymentRequestHandler; private String _paymentRequestHandlerUuid; protected WalletAccount _account; - private Double _oneBtcInFiat; // May be null - private Long _amountToSend; - private CurrencyValue _amountEntered; + private CurrencyValue _amountToSend; + private BitcoinValue _lastBitcoinAmountToSend = null; private Address _receivingAddress; protected String _transactionLabel; private BitcoinUri _bitcoinUri; protected boolean _isColdStorage; - protected boolean _isCoinapult; private TransactionStatus _transactionStatus; protected UnsignedTransaction _unsigned; - protected CoinapultManager.PreparedCoinapult _preparedCoinapult; + protected CoinapultAccount.PreparedCoinapult _preparedCoinapult; private Transaction _signedTransaction; private MinerFee _fee; private ProgressDialog _progress; @@ -157,7 +180,7 @@ public static Intent getIntent(Activity currentActivity, UUID account, Long amountToSend, Address receivingAddress, boolean isColdStorage) { Intent intent = new Intent(currentActivity, SendMainActivity.class); intent.putExtra(ACCOUNT, account); - intent.putExtra(AMOUNT_TO_SEND, amountToSend); + intent.putExtra(AMOUNT, ExactBitcoinValue.from(amountToSend)); intent.putExtra(RECEIVING_ADDRESS, receivingAddress); intent.putExtra(IS_COLD_STORAGE, isColdStorage); return intent; @@ -167,7 +190,7 @@ public static Intent getSepaIntent(Activity currentActivity, UUID account, BillPay sepaPayment, String txLabel, boolean isColdStorage) { Intent intent = new Intent(currentActivity, SendMainActivity.class); intent.putExtra(ACCOUNT, account); - intent.putExtra(AMOUNT_TO_SEND, Bitcoins.nearestValue(sepaPayment.details.amountToDeposit).getLongValue()); + intent.putExtra(AMOUNT, ExactBitcoinValue.from(sepaPayment.details.amountToDeposit)); intent.putExtra(RECEIVING_ADDRESS, sepaPayment.details.address); intent.putExtra(TRANSACTION_LABEL, txLabel); intent.putExtra(SEPA_PAYMENT, sepaPayment); @@ -200,7 +223,7 @@ public static Intent getIntent(Activity currentActivity, UUID account, byte[] ra } private boolean isCoinapult() { - return _account != null && _account instanceof CoinapultManager; + return _account != null && _account instanceof CoinapultAccount; } @SuppressLint("ShowToast") @@ -209,14 +232,14 @@ public void onCreate(Bundle savedInstanceState) { this.requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); setContentView(R.layout.send_main_activity); + ButterKnife.inject(this); _mbwManager = MbwManager.getInstance(getApplication()); // Get intent parameters UUID accountId = Preconditions.checkNotNull((UUID) getIntent().getSerializableExtra(ACCOUNT)); // May be null - _amountToSend = (Long) getIntent().getSerializableExtra(AMOUNT_TO_SEND); - _amountEntered = (CurrencyValue) getIntent().getSerializableExtra(GetAmountActivity.AMOUNT); + setAmountToSend((CurrencyValue) getIntent().getSerializableExtra(AMOUNT)); // May be null _receivingAddress = (Address) getIntent().getSerializableExtra(RECEIVING_ADDRESS); //May be null @@ -234,8 +257,7 @@ public void onCreate(Bundle savedInstanceState) { // Load saved state, overwriting amount and address if (savedInstanceState != null) { - _amountToSend = (Long) savedInstanceState.getSerializable(AMOUNT_TO_SEND); - _amountEntered = (CurrencyValue) savedInstanceState.getSerializable(GetAmountActivity.AMOUNT); + setAmountToSend((CurrencyValue) savedInstanceState.getSerializable(AMOUNT)); _receivingAddress = (Address) savedInstanceState.getSerializable(RECEIVING_ADDRESS); _transactionLabel = savedInstanceState.getString(TRANSACTION_LABEL); _fee = MinerFee.fromString(savedInstanceState.getString(FEE_LVL)); @@ -252,9 +274,6 @@ public void onCreate(Bundle savedInstanceState) { } } - if (_amountEntered == null && _amountToSend != null){ - _amountEntered = ExactBitcoinValue.from(_amountToSend); - } //if we do not have a stored receiving address, and got a keynode, we need to figure out the address if (_receivingAddress == null) { @@ -272,7 +291,7 @@ public void onCreate(Bundle savedInstanceState) { //we need the user to pick a spending account - the activity will then init sendmain correctly BitcoinUri uri; if (_bitcoinUri == null) { - uri = BitcoinUri.from(_receivingAddress, _amountToSend, _transactionLabel, null); + uri = BitcoinUri.from(_receivingAddress, getBitcoinValueToSend().getLongValue(), _transactionLabel, null); } else { uri = _bitcoinUri; } @@ -290,10 +309,9 @@ public void onCreate(Bundle savedInstanceState) { // SEPA transfer for if cashila account is paired if (_mbwManager.isWalletPaired(ExternalService.CASHILA)) { - findViewById(R.id.btSepaTransfer).setVisibility(View.VISIBLE); - findViewById(R.id.btSepaTransfer).setOnClickListener(sepaClickListener); + btSepaTransfer.setVisibility(View.VISIBLE); } else { - findViewById(R.id.btSepaTransfer).setVisibility(View.GONE); + btSepaTransfer.setVisibility(View.GONE); } _sepaPayment = (BillPay) getIntent().getSerializableExtra(SEPA_PAYMENT); @@ -310,39 +328,37 @@ public void onCreate(Bundle savedInstanceState) { verifyPaymentRequest(_bitcoinUri); } - - // Scan - findViewById(R.id.btScan).setOnClickListener(scanClickListener); - - // Address Book - findViewById(R.id.btAddressBook).setOnClickListener(addressBookClickListener); - - // Manual Entry - findViewById(R.id.btManualEntry).setOnClickListener(manualEntryClickListener); - - // Clipboard - findViewById(R.id.btClipboard).setOnClickListener(clipboardClickListener); - - // Enter Amount - findViewById(R.id.btEnterAmount).setOnClickListener(amountClickListener); - //Remove Miner fee if coinapult if (isCoinapult()) { - findViewById(R.id.llFee).setVisibility(View.GONE); + llFee.setVisibility(View.GONE); } - // Change miner fee - findViewById(R.id.btFeeLvl).setOnClickListener(feeClickListener); - - // Send button - findViewById(R.id.btSend).setOnClickListener(sendClickListener); // Amount Hint - ((TextView) findViewById(R.id.tvAmount)).setHint(getResources().getString(R.string.amount_hint_denomination, + tvAmount.setHint(getResources().getString(R.string.amount_hint_denomination, _mbwManager.getBitcoinDenomination().toString())); + } - //unconfirmed spending warning - findViewById(R.id.tvUnconfirmedWarning).setOnClickListener(unconfirmedClickListener); + // returns the amcountToSend in Bitcoin - it tries to get it from the entered amount and + // only uses the ExchangeRate-Manager if we dont have it already converted + private BitcoinValue getBitcoinValueToSend() { + if (CurrencyValue.isNullOrZero(_amountToSend)) { + return null; + } else if (_amountToSend.getExactValueIfPossible().isBtc()) { + return (BitcoinValue) _amountToSend.getExactValueIfPossible(); + } else if (_amountToSend.isBtc()) { + return (BitcoinValue) _amountToSend; + } else { + if (_lastBitcoinAmountToSend == null) { + // only convert once and keep that fx rate for further calls - the cache gets invalidated in setAmountToSend + _lastBitcoinAmountToSend = (BitcoinValue) ExchangeBasedBitcoinValue.fromValue(_amountToSend, _mbwManager.getExchangeRateManager()); + } + return _lastBitcoinAmountToSend; + } + } + private void setAmountToSend(CurrencyValue toSend) { + _amountToSend = toSend; + _lastBitcoinAmountToSend = null; } private void verifyPaymentRequest(BitcoinUri uri) { @@ -357,8 +373,8 @@ private void verifyPaymentRequest(byte[] rawPr) { private void showSepaInfo(BillPay sepaPayment) { // show the sepa information, instead of the Btc Address - ViewGroup parent = (ViewGroup) findViewById(R.id.tvReceiver).getParent(); - findViewById(R.id.tvReceiver).setVisibility(View.GONE); + ViewGroup parent = (ViewGroup) tvReceiver.getParent(); + tvReceiver.setVisibility(View.GONE); View view = getLayoutInflater().inflate(R.layout.ext_cashila_sepa_info, parent, true); ((TextView) view.findViewById(R.id.tvName)).setText(sepaPayment.recipient.name); @@ -373,14 +389,13 @@ private void showSepaInfo(BillPay sepaPayment) { ((TextView) view.findViewById(R.id.tvBtcAddress)).setText(String.format("(%s)", sepaPayment.details.address.toString())); // hide the button to change the amount - findViewById(R.id.btEnterAmount).setVisibility(View.GONE); + btEnterAmount.setVisibility(View.GONE); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); - savedInstanceState.putSerializable(AMOUNT_TO_SEND, _amountToSend); - savedInstanceState.putSerializable(GetAmountActivity.AMOUNT, _amountEntered); + savedInstanceState.putSerializable(AMOUNT, _amountToSend); savedInstanceState.putSerializable(RECEIVING_ADDRESS, _receivingAddress); savedInstanceState.putString(TRANSACTION_LABEL, _transactionLabel); savedInstanceState.putString(FEE_LVL, _fee.tag); @@ -390,159 +405,140 @@ public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putSerializable(SIGNED_TRANSACTION, _signedTransaction); } + @OnClick(R.id.btScan) + void onClickScan() { + ScanActivity.callMe(SendMainActivity.this, SCAN_RESULT_CODE, StringHandleConfig.returnKeyOrAddressOrUriOrKeynode()); + } - private OnClickListener scanClickListener = new OnClickListener() { - @Override - public void onClick(View arg0) { - ScanActivity.callMe(SendMainActivity.this, SCAN_RESULT_CODE, StringHandleConfig.returnKeyOrAddressOrUriOrKeynode()); - } - }; - - private OnClickListener sepaClickListener = new OnClickListener() { - @Override - public void onClick(View arg0) { - _mbwManager.getVersionManager().showFeatureWarningIfNeeded(SendMainActivity.this, Feature.CASHILA, true, new Runnable() { - @Override - public void run() { - startActivity(CashilaPaymentsActivity.getIntent(SendMainActivity.this)); - finish(); - } - }); - } - }; - - private OnClickListener addressBookClickListener = new OnClickListener() { - - @Override - public void onClick(View arg0) { - Intent intent = new Intent(SendMainActivity.this, GetFromAddressBookActivity.class); - startActivityForResult(intent, ADDRESS_BOOK_RESULT_CODE); - } - }; - - private OnClickListener manualEntryClickListener = new OnClickListener() { + @OnClick(R.id.btSepaTransfer) + void onClickSepaPayment() { + _mbwManager.getVersionManager().showFeatureWarningIfNeeded(SendMainActivity.this, Feature.CASHILA, true, new Runnable() { + @Override + public void run() { + startActivity(CashilaPaymentsActivity.getIntent(SendMainActivity.this)); + finish(); + } + }); + } - @Override - public void onClick(View arg0) { - Intent intent = new Intent(SendMainActivity.this, ManualAddressEntry.class); - startActivityForResult(intent, MANUAL_ENTRY_RESULT_CODE); - } - }; + @OnClick(R.id.btAddressBook) + void onClickAddressBook() { + Intent intent = new Intent(SendMainActivity.this, GetFromAddressBookActivity.class); + startActivityForResult(intent, ADDRESS_BOOK_RESULT_CODE); + } - private OnClickListener clipboardClickListener = new OnClickListener() { + @OnClick(R.id.btManualEntry) + void onClickManualEntry() { + Intent intent = new Intent(SendMainActivity.this, ManualAddressEntry.class); + startActivityForResult(intent, MANUAL_ENTRY_RESULT_CODE); + } - @Override - public void onClick(View arg0) { - BitcoinUriWithAddress uri = getUriFromClipboard(); - if (uri != null) { - Toast.makeText(SendMainActivity.this, getResources().getString(R.string.using_address_from_clipboard), - Toast.LENGTH_SHORT).show(); - _receivingAddress = uri.address; - if (uri.amount != null) { - _amountToSend = uri.amount; - } - _transactionStatus = tryCreateUnsignedTransaction(); - updateUi(); + @OnClick(R.id.btClipboard) + void onClickClipboard() { + BitcoinUriWithAddress uri = getUriFromClipboard(); + if (uri != null) { + Toast.makeText(SendMainActivity.this, getResources().getString(R.string.using_address_from_clipboard), + Toast.LENGTH_SHORT).show(); + _receivingAddress = uri.address; + if (uri.amount != null) { + _amountToSend = ExactBitcoinValue.from(uri.amount); } + _transactionStatus = tryCreateUnsignedTransaction(); + updateUi(); } - }; - - private OnClickListener amountClickListener = new OnClickListener() { + } - @Override - public void onClick(View arg0) { - ExactBitcoinValue amount = ExactBitcoinValue.from(_amountToSend); - GetAmountActivity.callMe(SendMainActivity.this, GET_AMOUNT_RESULT_CODE, _account.getId(), amount, getFeePerKb().getLongValue(), _isColdStorage); + @OnClick(R.id.btEnterAmount) + void onClickAmount() { + CurrencyValue presetAmount = _amountToSend; + if (CurrencyValue.isNullOrZero(presetAmount)) { + // if no amount is set so far, use an unknown amount but in the current accounts currency + presetAmount = ExactCurrencyValue.from(null, _account.getAccountDefaultCurrency()); } - }; - - private Bitcoins getFeePerKb() { - return _fee.getFeePerKb(_mbwManager.getWalletManager(_isColdStorage).getLastFeeEstimations()); + GetAmountActivity.callMe(SendMainActivity.this, GET_AMOUNT_RESULT_CODE, _account.getId(), presetAmount, getFeePerKb().getLongValue(), _isColdStorage); } - private OnClickListener sendClickListener = new OnClickListener() { + @OnClick(R.id.btSend) + void onClickSend() { + if (isCoinapult()) { + sendCoinapultTransaction(); + } else if (_isColdStorage || _account instanceof Bip44AccountExternalSignature) { + // We do not ask for pin when the key is from cold storage or from a external device (trezor,...) + signTransaction(); + } else { + _mbwManager.runPinProtectedFunction(SendMainActivity.this, pinProtectedSignAndSend); + } + } - @Override - public void onClick(View arg0) { - if (isCoinapult()) { - _mbwManager.getVersionManager().showFeatureWarningIfNeeded(SendMainActivity.this, - Feature.COINAPULT_MAKE_OUTGOING_TX, true, new Runnable() { + private void sendCoinapultTransaction() { + _mbwManager.getVersionManager().showFeatureWarningIfNeeded(SendMainActivity.this, + Feature.COINAPULT_MAKE_OUTGOING_TX, true, new Runnable() { + @Override + public void run() { + _mbwManager.runPinProtectedFunction(SendMainActivity.this, new Runnable() { @Override public void run() { - _mbwManager.runPinProtectedFunction(SendMainActivity.this, new Runnable() { - @Override - public void run() { - if (_account instanceof CoinapultManager) { - final ProgressDialog progress = new ProgressDialog(SendMainActivity.this); - progress.setCancelable(false); - progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); - progress.setMessage(getString(R.string.sendingViaCoinapult)); - progress.show(); - final CoinapultManager coinapultManager = (CoinapultManager) _account; - disableButtons(); - new AsyncTask() { - @Override - protected Boolean doInBackground(CoinapultManager.PreparedCoinapult... params) { - return coinapultManager.broadcast(params[0]); - } - - @Override - protected void onPostExecute(Boolean aBoolean) { - super.onPostExecute(aBoolean); - progress.dismiss(); - if (aBoolean) { - SendMainActivity.this.finish(); - } else { - Toast.makeText(SendMainActivity.this, R.string.coinapult_failed_to_broadcast, Toast.LENGTH_SHORT).show(); - updateUi(); - } - } - }.execute(_preparedCoinapult); + if (_account instanceof CoinapultAccount) { + final ProgressDialog progress = new ProgressDialog(SendMainActivity.this); + progress.setCancelable(false); + progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); + progress.setMessage(getString(R.string.coinapult_sending_via_coinapult)); + progress.show(); + final CoinapultAccount coinapultManager = (CoinapultAccount) _account; + disableButtons(); + new AsyncTask() { + @Override + protected Boolean doInBackground(CoinapultAccount.PreparedCoinapult... params) { + return coinapultManager.broadcast(params[0]); } - } - }); + + @Override + protected void onPostExecute(Boolean aBoolean) { + super.onPostExecute(aBoolean); + progress.dismiss(); + if (aBoolean) { + SendMainActivity.this.finish(); + } else { + Toast.makeText(SendMainActivity.this, R.string.coinapult_failed_to_broadcast, Toast.LENGTH_SHORT).show(); + updateUi(); + } + } + }.execute(_preparedCoinapult); + } } }); + } + }); + } - } else if (_isColdStorage || _account instanceof Bip44AccountExternalSignature) { - // We do not ask for pin when the key is from cold storage or from a external device (trezor,...) - signTransaction(); - } else { - _mbwManager.runPinProtectedFunction(SendMainActivity.this, pinProtectedSignAndSend); - } - } - }; - - private OnClickListener unconfirmedClickListener = new OnClickListener() { + @OnClick(R.id.tvUnconfirmedWarning) + void onClickUnconfirmedWarning() { + new AlertDialog.Builder(SendMainActivity.this) + .setTitle(getString(R.string.spending_unconfirmed_title)) + .setMessage(getString(R.string.spending_unconfirmed_description)) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // continue + } + }) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + } - @Override - public void onClick(View arg0) { - new AlertDialog.Builder(SendMainActivity.this) - .setTitle(getString(R.string.spending_unconfirmed_title)) - .setMessage(getString(R.string.spending_unconfirmed_description)) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // continue - } - }) - .setIcon(android.R.drawable.ic_dialog_alert) - .show(); + @OnClick(R.id.btFeeLvl) + void onClickFeeLevel() { + _fee = _fee.getNext(); + _transactionStatus = tryCreateUnsignedTransaction(); + updateUi(); + //warn user if minimum fee is selected + if (_fee == MinerFee.ECONOMIC || _fee == MinerFee.LOWPRIO) { + Toast.makeText(SendMainActivity.this, getString(R.string.toast_warning_low_fee), Toast.LENGTH_SHORT).show(); } - }; - - private OnClickListener feeClickListener = new OnClickListener() { + } - @Override - public void onClick(View arg0) { - _fee = _fee.getNext(); - _transactionStatus = tryCreateUnsignedTransaction(); - updateUi(); - //warn user if minimum fee is selected - if (_fee == MinerFee.ECONOMIC || _fee == MinerFee.LOWPRIO) { - Toast.makeText(SendMainActivity.this, getString(R.string.toast_warning_low_fee), Toast.LENGTH_SHORT).show(); - } - } - }; + private Bitcoins getFeePerKb() { + return _fee.getFeePerKb(_mbwManager.getWalletManager(_isColdStorage).getLastFeeEstimations()); + } private TransactionStatus tryCreateUnsignedTransaction() { if (isCoinapult()) { @@ -555,14 +551,15 @@ private TransactionStatus tryCreateUnsignedTransaction() { private TransactionStatus tryCreateUnsignedTransactionFromWallet() { _unsigned = null; - if (_paymentRequestHandler == null && (_amountToSend == null || _receivingAddress == null)) { + BitcoinValue toSend = getBitcoinValueToSend(); + if (_paymentRequestHandler == null && (toSend == null || toSend.getAsBitcoin() == null || _receivingAddress == null)) { return TransactionStatus.MissingArguments; } // Create the unsigned transaction try { if (_paymentRequestHandler == null || !_paymentRequestHandler.hasValidPaymentRequest()) { - WalletAccount.Receiver receiver = new WalletAccount.Receiver(_receivingAddress, _amountToSend); + WalletAccount.Receiver receiver = new WalletAccount.Receiver(_receivingAddress, toSend.getLongValue()); _unsigned = _account.createUnsignedTransaction(Arrays.asList(receiver), getFeePerKb().getLongValue()); checkSpendingUnconfirmed(); return TransactionStatus.OK; @@ -572,14 +569,14 @@ private TransactionStatus tryCreateUnsignedTransactionFromWallet() { // has the payment request an amount set? if (paymentRequestInformation.hasAmount()) { - _amountToSend = paymentRequestInformation.getOutputs().getTotalAmount(); + setAmountToSend(ExactBitcoinValue.from(paymentRequestInformation.getOutputs().getTotalAmount())); } else { - if (_amountToSend == null || _amountToSend == 0) { + if (CurrencyValue.isNullOrZero(_amountToSend)) { return TransactionStatus.MissingArguments; } // build new output list with user specified amount - outputs = outputs.newOutputsWithTotalAmount(_amountToSend); + outputs = outputs.newOutputsWithTotalAmount(toSend.getLongValue()); } _unsigned = _account.createUnsignedTransaction(outputs, getFeePerKb().getLongValue()); _receivingAddress = null; @@ -602,31 +599,45 @@ private TransactionStatus tryCreateUnsignedTransactionFromWallet() { } private TransactionStatus tryCreateCoinapultTX() { - if (_account instanceof CoinapultManager) { - CoinapultManager coinapultManager = (CoinapultManager) _account; + if (_account instanceof CoinapultAccount) { + CoinapultAccount coinapultAccount = (CoinapultAccount) _account; _unsigned = null; _preparedCoinapult = null; - if (_amountToSend == null || _receivingAddress == null) { + if (CurrencyValue.isNullOrZero(_amountToSend) || _receivingAddress == null) { return TransactionStatus.MissingArguments; } try { - Optional usdAmount = CurrencyValue.checkUsdAmount(_amountEntered); - if (usdAmount.isPresent()) { - if (usdAmount.get().getValue().compareTo(Constants.COINAPULT_MINIMUM_AMOUNT) < 0) { + // try to get it in the accounts native currency, but dont convert anything + Optional nativeAmount = CurrencyValue.checkCurrencyAmount( + _amountToSend, + coinapultAccount.getCoinapultCurrency().name + ); + + BigDecimal minimumConversationValue = coinapultAccount.getCoinapultCurrency().minimumConversationValue; + if (nativeAmount.isPresent()) { + if (nativeAmount.get().getValue().compareTo(minimumConversationValue) < 0) { //trying to send less than coinapults minimum withdrawal return TransactionStatus.OutputTooSmall; } - _preparedCoinapult = coinapultManager.prepareCoinapultTx(_receivingAddress, usdAmount.get()); + _preparedCoinapult = coinapultAccount.prepareCoinapultTx(_receivingAddress, nativeAmount.get()); return TransactionStatus.OK; } else { - if (CurrencyValue.fromValue(_amountEntered, "USD", _mbwManager.getExchangeRateManager()).getValue().compareTo(Constants.COINAPULT_MINIMUM_AMOUNT) < 0) { + // if we dont have it in the account-native currency, send it as bitcoin value and + // let coinapult to the conversation + + // convert it to native, only to check if its larger the the minValue + BigDecimal nativeValue = CurrencyValue.fromValue( + _amountToSend, coinapultAccount.getCoinapultCurrency().name, + _mbwManager.getExchangeRateManager()).getValue(); + + if (nativeValue.compareTo(minimumConversationValue) < 0) { //trying to send less than coinapults minimum withdrawal return TransactionStatus.OutputTooSmall; } - WalletAccount.Receiver receiver = new WalletAccount.Receiver(_receivingAddress, _amountToSend); - _preparedCoinapult = coinapultManager.prepareCoinapultTx(receiver); + WalletAccount.Receiver receiver = new WalletAccount.Receiver(_receivingAddress, getBitcoinValueToSend().getLongValue()); + _preparedCoinapult = coinapultAccount.prepareCoinapultTx(receiver); return TransactionStatus.OK; } } catch (InsufficientFundsException e) { @@ -657,7 +668,7 @@ private void updateUi() { updateAmount(); // Enable/disable send button - findViewById(R.id.btSend).setEnabled(_transactionStatus == TransactionStatus.OK); + btSend.setEnabled(_transactionStatus == TransactionStatus.OK); findViewById(R.id.root).invalidate(); } @@ -665,62 +676,57 @@ private void updateRecipient() { boolean hasPaymentRequest = _paymentRequestHandler != null && _paymentRequestHandler.hasValidPaymentRequest(); if (_receivingAddress == null && !hasPaymentRequest) { // Hide address, show "Enter" - ((TextView) findViewById(R.id.tvRecipientTitle)).setText(R.string.enter_recipient_title); - findViewById(R.id.llEnterRecipient).setVisibility(View.VISIBLE); - findViewById(R.id.llRecipientAddress).setVisibility(View.GONE); - findViewById(R.id.tvWarning).setVisibility(View.GONE); + tvRecipientTitle.setText(R.string.enter_recipient_title); + llEnterRecipient.setVisibility(View.VISIBLE); + llRecipientAddress.setVisibility(View.GONE); + tvWarning.setVisibility(View.GONE); return; } // Hide "Enter", show address - ((TextView) findViewById(R.id.tvRecipientTitle)).setText(R.string.recipient_title); - findViewById(R.id.llRecipientAddress).setVisibility(View.VISIBLE); - findViewById(R.id.llEnterRecipient).setVisibility(View.GONE); - - // Set address label if applicable - TextView receiverLabel = (TextView) findViewById(R.id.tvReceiverLabel); + tvRecipientTitle.setText(R.string.recipient_title); + llRecipientAddress.setVisibility(View.VISIBLE); + llEnterRecipient.setVisibility(View.GONE); // See if the address is in the address book or one of our accounts String label = null; if (_receivingAddress != null) { label = getAddressLabel(_receivingAddress); } - if (label == null || label.length() == 0) { // Hide label - receiverLabel.setVisibility(View.GONE); + tvReceiverLabel.setVisibility(View.GONE); } else { // Show label - receiverLabel.setText(label); - receiverLabel.setVisibility(View.VISIBLE); + tvReceiverLabel.setText(label); + tvReceiverLabel.setVisibility(View.VISIBLE); } // Set Address if (_sepaPayment == null && !hasPaymentRequest) { String choppedAddress = _receivingAddress.toMultiLineString(); - ((TextView) findViewById(R.id.tvReceiver)).setText(choppedAddress); + tvReceiver.setText(choppedAddress); } if (hasPaymentRequest) { PaymentRequestInformation paymentRequestInformation = _paymentRequestHandler.getPaymentRequestInformation(); if (paymentRequestInformation.hasValidSignature()) { - ((TextView) findViewById(R.id.tvReceiver)).setText(paymentRequestInformation.getPkiVerificationData().displayName); + tvReceiver.setText(paymentRequestInformation.getPkiVerificationData().displayName); } else { - ((TextView) findViewById(R.id.tvReceiver)).setText(getString(R.string.label_unverified_recipient)); + tvReceiver.setText(getString(R.string.label_unverified_recipient)); } } // show address (if available - some PRs might have more than one address or a not decodeable input) if (hasPaymentRequest && _receivingAddress != null) { - ((TextView) findViewById(R.id.tvReceiverAddress)).setText(_receivingAddress.toDoubleLineString()); - findViewById(R.id.tvReceiverAddress).setVisibility(View.VISIBLE); + tvReceiverAddress.setText(_receivingAddress.toDoubleLineString()); + tvReceiverAddress.setVisibility(View.VISIBLE); } else { - findViewById(R.id.tvReceiverAddress).setVisibility(View.GONE); + tvReceiverAddress.setVisibility(View.GONE); } //Check the wallet manager to see whether its our own address, and whether we can spend from it WalletManager walletManager = _mbwManager.getWalletManager(false); if (_receivingAddress != null && walletManager.isMyAddress(_receivingAddress)) { - TextView tvWarning = (TextView) findViewById(R.id.tvWarning); if (walletManager.hasPrivateKeyForAddress(_receivingAddress)) { // Show a warning as we are sending to one of our own addresses tvWarning.setVisibility(View.VISIBLE); @@ -735,17 +741,17 @@ private void updateRecipient() { } } else { - findViewById(R.id.tvWarning).setVisibility(View.GONE); + tvWarning.setVisibility(View.GONE); } //if present, show transaction label if (_transactionLabel != null) { - findViewById(R.id.tvTransactionLabelTitle).setVisibility(View.VISIBLE); - findViewById(R.id.tvTransactionLabel).setVisibility(View.VISIBLE); - ((TextView) findViewById(R.id.tvTransactionLabel)).setText(_transactionLabel); + tvTransactionLabelTitle.setVisibility(View.VISIBLE); + tvTransactionLabel.setVisibility(View.VISIBLE); + tvTransactionLabel.setText(_transactionLabel); } else { - findViewById(R.id.tvTransactionLabelTitle).setVisibility(View.GONE); - findViewById(R.id.tvTransactionLabel).setVisibility(View.GONE); + tvTransactionLabelTitle.setVisibility(View.GONE); + tvTransactionLabel.setVisibility(View.GONE); } } @@ -763,64 +769,110 @@ private void updateAmount() { // Update Amount if (_amountToSend == null) { // No amount to show - ((TextView) findViewById(R.id.tvAmountTitle)).setText(R.string.enter_amount_title); - ((TextView) findViewById(R.id.tvAmount)).setText(""); - findViewById(R.id.tvAmountFiat).setVisibility(View.GONE); - findViewById(R.id.tvError).setVisibility(View.GONE); + ((TextView) tvAmountTitle).setText(R.string.enter_amount_title); + tvAmount.setText(""); + tvAmountFiat.setVisibility(View.GONE); + tvError.setVisibility(View.GONE); } else { - ((TextView) findViewById(R.id.tvAmountTitle)).setText(R.string.amount_title); + tvAmountTitle.setText(R.string.amount_title); if (_transactionStatus == TransactionStatus.OutputTooSmall) { // Amount too small - ((TextView) findViewById(R.id.tvAmount)).setText(_mbwManager.getBtcValueString(_amountToSend)); - findViewById(R.id.tvAmountFiat).setVisibility(View.GONE); + tvAmount.setText(_mbwManager.getBtcValueString(getBitcoinValueToSend().getLongValue())); + tvAmountFiat.setVisibility(View.GONE); if (isCoinapult()) { - ((TextView) findViewById(R.id.tvError)).setText(getString(R.string.amount_too_small_coinapult, Constants.COINAPULT_MINIMUM_AMOUNT.toString())); + CoinapultAccount coinapultAccount = (CoinapultAccount) _account; + tvError.setText( + getString( + R.string.coinapult_amount_too_small, + coinapultAccount.getCoinapultCurrency().minimumConversationValue, + coinapultAccount.getCoinapultCurrency().name) + ); } else { - ((TextView) findViewById(R.id.tvError)).setText(R.string.amount_too_small_short); + tvError.setText(R.string.amount_too_small_short); } - findViewById(R.id.tvError).setVisibility(View.VISIBLE); + tvError.setVisibility(View.VISIBLE); } else if (_transactionStatus == TransactionStatus.InsufficientFunds) { // Insufficient funds - ((TextView) findViewById(R.id.tvAmount)).setText(_mbwManager.getBtcValueString(_amountToSend)); - ((TextView) findViewById(R.id.tvError)).setText(R.string.insufficient_funds); - findViewById(R.id.tvError).setVisibility(View.VISIBLE); + tvAmount.setText( + Utils.getFormattedValueWithUnit(_amountToSend, _mbwManager.getBitcoinDenomination()) + ); + tvError.setText(R.string.insufficient_funds); + tvError.setVisibility(View.VISIBLE); } else { // Set Amount - String sendAmount = _mbwManager.getBtcValueString(_amountToSend); - if (isCoinapult()) { - sendAmount = "~ " + sendAmount; //when using coinapult, btc amount is estimated - } - ((TextView) findViewById(R.id.tvAmount)).setText(sendAmount); - if (!_mbwManager.hasFiatCurrency() || _oneBtcInFiat == null) { - findViewById(R.id.tvAmountFiat).setVisibility(View.GONE); + if (!CurrencyValue.isNullOrZero(_amountToSend)) { + // show the user entered value as primary amount + CurrencyValue primaryAmount = _amountToSend; + CurrencyValue alternativeAmount; + if (primaryAmount.getCurrency().equals(_account.getAccountDefaultCurrency())) { + if (primaryAmount.isBtc()) { + // if the accounts default currency is BTC and the user entered BTC, use the current + // selected fiat as alternative currency + alternativeAmount = CurrencyValue.fromValue( + primaryAmount, _mbwManager.getFiatCurrency(), _mbwManager.getExchangeRateManager() + ); + } else { + // if the accounts default currency isn't BTC, use BTC as alternative + alternativeAmount = ExchangeBasedBitcoinValue.fromValue( + primaryAmount, _mbwManager.getExchangeRateManager() + ); + } + } else { + // use the accounts default currency as alternative + alternativeAmount = CurrencyValue.fromValue( + primaryAmount, _account.getAccountDefaultCurrency(), _mbwManager.getExchangeRateManager() + ); + } + String sendAmount = Utils.getFormattedValueWithUnit(primaryAmount, _mbwManager.getBitcoinDenomination()); + if (!primaryAmount.isBtc()) { + // if the amount is not in BTC, show a ~ to inform the user, its only approximate and depends + // on a FX rate + sendAmount = "~ " + sendAmount; + } + tvAmount.setText(sendAmount); + if (CurrencyValue.isNullOrZero(alternativeAmount)) { + tvAmountFiat.setVisibility(View.GONE); + } else { + // show the alternative amount + String alternativeAmountString = + Utils.getFormattedValueWithUnit(alternativeAmount, _mbwManager.getBitcoinDenomination()); + + if (!alternativeAmount.isBtc()) { + // if the amount is not in BTC, show a ~ to inform the user, its only approximate and depends + // on a FX rate + alternativeAmountString = "~ " + alternativeAmountString; + } + + tvAmountFiat.setText(alternativeAmountString); + tvAmountFiat.setVisibility(View.VISIBLE); + } } else { - // Set approximate amount in fiat - TextView tvAmountFiat = ((TextView) findViewById(R.id.tvAmountFiat)); - tvAmountFiat.setText(getFiatValue(_amountToSend, _oneBtcInFiat)); - tvAmountFiat.setVisibility(View.VISIBLE); + tvAmount.setText(""); + tvAmountFiat.setText(""); } - findViewById(R.id.tvError).setVisibility(View.GONE); + + + tvError.setVisibility(View.GONE); //check if we need to warn the user about unconfirmed funds if (_spendingUnconfirmed) { - findViewById(R.id.tvUnconfirmedWarning).setVisibility(View.VISIBLE); + tvUnconfirmedWarning.setVisibility(View.VISIBLE); } else { - findViewById(R.id.tvUnconfirmedWarning).setVisibility(View.GONE); + tvUnconfirmedWarning.setVisibility(View.GONE); } } } // Disable Amount button if we have a payment request with valid amount if (_paymentRequestHandler != null && _paymentRequestHandler.getPaymentRequestInformation().hasAmount()) { - findViewById(R.id.btEnterAmount).setEnabled(false); + btEnterAmount.setEnabled(false); } // Update Fee-Display - TextView btFeeLvl = (Button) findViewById(R.id.btFeeLvl); if (_unsigned == null) { // Only show button for fee lvl, cannot calculate fee yet - ((Button) findViewById(R.id.btFeeLvl)).setText(_fee.getMinerFeeName(this)); - findViewById(R.id.tvFeeValue).setVisibility(View.INVISIBLE); + btFeeLvl.setText(_fee.getMinerFeeName(this)); + tvFeeValue.setVisibility(View.INVISIBLE); } else { // Show fee fully calculated btFeeLvl.setVisibility(View.VISIBLE); @@ -830,40 +882,37 @@ private void updateAmount() { //show fee lvl on button - always show the fees in mBtc CoinUtil.Denomination feeDenomination = CoinUtil.Denomination.mBTC; String feeString = CoinUtil.valueString(fee, feeDenomination, true) + " " + feeDenomination.getUnicodeName(); - ((Button) findViewById(R.id.btFeeLvl)).setText(_fee.getMinerFeeName(this)); + btFeeLvl.setText(_fee.getMinerFeeName(this)); - if (!_mbwManager.hasFiatCurrency() || _oneBtcInFiat == null) { - } else { - // Set approximate fee in fiat - feeString += ", " + getFiatValue(fee, _oneBtcInFiat); + CurrencyValue fiatFee = CurrencyValue.fromValue( + ExactBitcoinValue.from(fee), + _mbwManager.getFiatCurrency(), + _mbwManager.getExchangeRateManager() + ); + + if (!CurrencyValue.isNullOrZero(fiatFee)) { + // Show approximate fee in fiat + feeString += ", " + Utils.getFormattedValueWithUnit(fiatFee, _mbwManager.getBitcoinDenomination()); } - TextView tvFeeFiat = ((TextView) findViewById(R.id.tvFeeValue)); - tvFeeFiat.setVisibility(View.VISIBLE); - tvFeeFiat.setText("(" + feeString + ")"); + tvFeeValue.setVisibility(View.VISIBLE); + tvFeeValue.setText(String.format("(%s)", feeString)); } } - private String getFiatValue(long satoshis, Double oneBtcInFiat) { - String currency = _mbwManager.getFiatCurrency(); - String converted = Utils.getFiatValueAsString(satoshis, oneBtcInFiat); - return getResources().getString(R.string.approximate_fiat_value, currency, converted); - } - @Override protected void onResume() { _mbwManager.getEventBus().register(this); // If we don't have a fresh exchange rate, now is a good time to request one, as we will need it in a minute - _oneBtcInFiat = _mbwManager.getCurrencySwitcher().getExchangeRatePrice(); - if (_oneBtcInFiat == null) { + if (!_mbwManager.getCurrencySwitcher().isFiatExchangeRateAvailable()) { _mbwManager.getExchangeRateManager().requestRefresh(); } - findViewById(R.id.btClipboard).setEnabled(getUriFromClipboard() != null); - findViewById(R.id.pbSend).setVisibility(View.GONE); + btClipboard.setEnabled(getUriFromClipboard() != null); + pbSend.setVisibility(View.GONE); updateUi(); super.onResume(); @@ -899,13 +948,13 @@ protected void signTransaction() { } protected void disableButtons() { - findViewById(R.id.pbSend).setVisibility(View.VISIBLE); - findViewById(R.id.btSend).setEnabled(false); - findViewById(R.id.btAddressBook).setEnabled(false); - findViewById(R.id.btManualEntry).setEnabled(false); - findViewById(R.id.btClipboard).setEnabled(false); - findViewById(R.id.btScan).setEnabled(false); - findViewById(R.id.btEnterAmount).setEnabled(false); + pbSend.setVisibility(View.VISIBLE); + btSend.setEnabled(false); + btAddressBook.setEnabled(false); + btManualEntry.setEnabled(false); + btClipboard.setEnabled(false); + btScan.setEnabled(false); + btEnterAmount.setEnabled(false); } @@ -938,10 +987,10 @@ public void onActivityResult(final int requestCode, final int resultCode, final _transactionLabel = uri.label; if (uri.amount != null) { //we set the amount to the one contained in the qr code, even if another one was entered previously - if (_amountToSend != null && _amountToSend > 0) { + if (!CurrencyValue.isNullOrZero(_amountToSend)) { Toast.makeText(this, R.string.amount_changed, Toast.LENGTH_LONG).show(); } - _amountToSend = uri.amount; + setAmountToSend(ExactBitcoinValue.from(uri.amount)); } } else if (type == StringHandlerActivity.ResultType.URI) { //todo: maybe merge with BitcoinUriWithAddress ? @@ -985,10 +1034,9 @@ public void onActivityResult(final int requestCode, final int resultCode, final updateUi(); } else if (requestCode == GET_AMOUNT_RESULT_CODE && resultCode == RESULT_OK) { // Get result from AmountEntry - Long amountSatoshi = (Long) intent.getSerializableExtra(GetAmountActivity.AMOUNT_SATOSHI); - if (amountSatoshi != null) { - _amountToSend = amountSatoshi; - _amountEntered = (CurrencyValue) intent.getSerializableExtra(GetAmountActivity.AMOUNT); + CurrencyValue enteredAmount = (CurrencyValue) intent.getSerializableExtra(GetAmountActivity.AMOUNT); + setAmountToSend(enteredAmount); + if (!CurrencyValue.isNullOrZero(_amountToSend)) { _transactionStatus = tryCreateUnsignedTransaction(); } updateUi(); @@ -1090,13 +1138,11 @@ public void paymentRequestAck(PaymentACK paymentACK) { @Subscribe public void exchangeRatesRefreshed(ExchangeRatesRefreshed event) { - _oneBtcInFiat = _mbwManager.getCurrencySwitcher().getExchangeRatePrice(); updateUi(); } @Subscribe public void selectedCurrencyChanged(SelectedCurrencyChanged event) { - _oneBtcInFiat = _mbwManager.getCurrencySwitcher().getExchangeRatePrice(); updateUi(); } @@ -1121,4 +1167,4 @@ public void syncFailed(SyncFailed event) { //todo: warn the user about address reuse for xpub } -} \ No newline at end of file +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/send/VerifyPaymentRequestActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/send/VerifyPaymentRequestActivity.java index 4624adafce..57391ee3e0 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/send/VerifyPaymentRequestActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/send/VerifyPaymentRequestActivity.java @@ -56,6 +56,7 @@ import com.mycelium.wallet.paymentrequest.PaymentRequestHandler; import com.mycelium.paymentrequest.PaymentRequestInformation; import com.mycelium.paymentrequest.PkiVerificationData; +import com.mycelium.wapi.wallet.currency.ExactBitcoinValue; import com.squareup.okhttp.OkHttpClient; import com.squareup.otto.Subscribe; import org.ocpsoft.prettytime.PrettyTime; @@ -269,7 +270,10 @@ private void updateUi() { CurrencySwitcher currencySwitcher = mbw.getCurrencySwitcher(); if (currencySwitcher.isFiatExchangeRateAvailable()){ tvFiatAmount.setVisibility(View.VISIBLE); - tvFiatAmount.setText(String.format("(~%s)", currencySwitcher.getFormattedFiatValue(totalAmount, true))); + tvFiatAmount.setText( + String.format("(~%s)", + currencySwitcher.getFormattedFiatValue(ExactBitcoinValue.from(totalAmount), true)) + ); } else { tvFiatAmount.setVisibility(View.GONE); } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/settings/SetLocalCurrencyActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/settings/SetLocalCurrencyActivity.java index edaf811fb9..e835d844c8 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/settings/SetLocalCurrencyActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/settings/SetLocalCurrencyActivity.java @@ -34,10 +34,7 @@ package com.mycelium.wallet.activity.settings; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import android.app.Activity; import android.content.Intent; @@ -49,6 +46,7 @@ import android.view.ViewGroup; import android.widget.*; +import com.google.common.collect.Sets; import com.mycelium.wallet.MbwManager; import com.mycelium.wallet.R; import com.mycelium.wapi.api.lib.CurrencyCode; @@ -61,7 +59,7 @@ public static void callMe(Activity currentActivity) { } private Map _currencySelectionToCurrencyMap; - private List _currencies; + private Set _currencies; private ArrayAdapter _adapter; @Override @@ -110,7 +108,7 @@ public View getView(int pos, View convertView, ViewGroup parent){ ListView listview = (ListView) findViewById(R.id.lvCurrencies); listview.setAdapter(_adapter); - _currencies = MbwManager.getInstance(this).getCurrencyList(); + _currencies = Sets.newHashSet(MbwManager.getInstance(this).getCurrencyList()); } View.OnClickListener itemClicked = new View.OnClickListener() { @@ -147,9 +145,6 @@ public void afterTextChanged(Editable s) { private void setCurrency(String currency, boolean isSelected) { if (isSelected) { - //should not happen - should be fail instead? - if (_currencies.contains(currency)) return; - _currencies.add(currency); } else { _currencies.remove(currency); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/settings/SettingsActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/settings/SettingsActivity.java index 7cd333be9f..dcd69d2b19 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/settings/SettingsActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/settings/SettingsActivity.java @@ -219,7 +219,7 @@ public void onClick(DialogInterface dialog, int which) { llDialog.addView(tvInfo); llDialog.addView(aidEdit); b.setView(llDialog); - AlertDialog dialog = b.show(); + b.show(); return true; } }; @@ -491,7 +491,6 @@ protected void onResume() { @SuppressWarnings("deprecation") private void setupLocalTraderSettings() { if (!_ltManager.hasLocalTraderAccount()) { - PreferenceScreen myceliumPreferences = (PreferenceScreen) findPreference("myceliumPreferences"); PreferenceCategory localTraderPrefs = (PreferenceCategory) findPreference("localtraderPrefs"); CheckBoxPreference disableLt = (CheckBoxPreference) findPreference("ltDisable"); if (localTraderPrefs != null) { @@ -708,4 +707,4 @@ public void onLtError(int errorCode) { } } -} \ No newline at end of file +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/util/MasterseedScanManager.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/util/MasterseedScanManager.java index 8cc30b6c93..b7d5102bce 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/util/MasterseedScanManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/util/MasterseedScanManager.java @@ -62,7 +62,7 @@ public MasterseedScanManager(Context context, NetworkParameters network, Bip39.M public MasterseedScanManager(Context context, NetworkParameters network, String[] words, String password, Bus eventBus){ super(context, network, eventBus); - this.words = words; + this.words = words.clone(); this.password = password; } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/util/ToggleableCurrencyButton.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/util/ToggleableCurrencyButton.java index a29d72ebb8..c0ec73cf75 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/util/ToggleableCurrencyButton.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/util/ToggleableCurrencyButton.java @@ -95,7 +95,7 @@ protected void updateUi(){ public void switchToNextCurrency(){ - String nextCurrency = Preconditions.checkNotNull(this.currencySwitcher).getNextCurrency(!fiatOnly); + Preconditions.checkNotNull(this.currencySwitcher).getNextCurrency(!fiatOnly); if (eventBus != null){ // update UI via event bus, also inform other parts of the app about the change eventBus.post(new SelectedCurrencyChanged()); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/util/ToggleableCurrencyDisplay.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/util/ToggleableCurrencyDisplay.java index 09f03a6493..6c00170d2d 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/util/ToggleableCurrencyDisplay.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/util/ToggleableCurrencyDisplay.java @@ -47,6 +47,8 @@ import com.mycelium.wallet.R; import com.mycelium.wallet.event.ExchangeRatesRefreshed; import com.mycelium.wallet.event.SelectedCurrencyChanged; +import com.mycelium.wapi.wallet.currency.CurrencySum; +import com.mycelium.wapi.wallet.currency.CurrencyValue; import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; @@ -59,7 +61,7 @@ public class ToggleableCurrencyDisplay extends LinearLayout { protected TextView tvValue; protected LinearLayout llContainer; - protected long satoshis; + protected CurrencyValue currentValue; protected boolean fiatOnly = false; protected boolean hideOnNoExchangeRate = false; private int precision = -1; @@ -81,16 +83,14 @@ public ToggleableCurrencyDisplay(Context context) { init(context); } - void parseXML(Context context, AttributeSet attrs){ + void parseXML(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ToggleableCurrencyButton); final int N = a.getIndexCount(); - for (int i = 0; i < N; ++i) - { + for (int i = 0; i < N; ++i) { int attr = a.getIndex(i); - switch (attr) - { + switch (attr) { case R.styleable.ToggleableCurrencyButton_fiatOnly: fiatOnly = a.getBoolean(attr, false); break; @@ -110,28 +110,28 @@ void parseXML(Context context, AttributeSet attrs){ } - protected void init(Context context){ - LayoutInflater mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + protected void init(Context context) { + LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view; view = mInflater.inflate(R.layout.toggleable_currency_display, this, true); tvCurrency = (TextView) view.findViewById(R.id.tvCurrency); - tvValue = (TextView) view.findViewById(R.id.tvValue); + tvValue = (TextView) view.findViewById(R.id.tvDisplayValue); llContainer = (LinearLayout) view.findViewById(R.id.llContainer); } - private void setTextSize(int size){ + private void setTextSize(int size) { tvCurrency.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); tvValue.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); } - private void setTextColor(int color){ + private void setTextColor(int color) { tvCurrency.setTextColor(color); tvValue.setTextColor(color); } - protected void updateUi(){ + protected void updateUi() { Preconditions.checkNotNull(currencySwitcher); if (fiatOnly) { @@ -139,43 +139,47 @@ protected void updateUi(){ } else { // Switch to BTC if no fiat fx rate is available if (!currencySwitcher.isFiatExchangeRateAvailable()) { - currencySwitcher.setCurrency(CurrencySwitcher.BTC); + currencySwitcher.setCurrency(CurrencyValue.BTC); } llContainer.setVisibility(VISIBLE); String formattedValue; - if (precision>=0){ - formattedValue = currencySwitcher.getFormattedValue(satoshis, false, precision); - }else { - formattedValue = currencySwitcher.getFormattedValue(satoshis, false); + if (precision >= 0) { + formattedValue = currencySwitcher.getFormattedValue(currentValue, false, precision); + } else { + formattedValue = currencySwitcher.getFormattedValue(currentValue, false); } tvValue.setText(formattedValue); - tvCurrency.setText(currencySwitcher.getCurrentCurrencyIncludingDenomination()); + String currentCurrency = currencySwitcher.getCurrentCurrencyIncludingDenomination(); + tvCurrency.setText(currentCurrency); } } private void showFiat() { - if (hideOnNoExchangeRate && !currencySwitcher.isFiatExchangeRateAvailable()){ + if (hideOnNoExchangeRate && !currencySwitcher.isFiatExchangeRateAvailable()) { // hide everything llContainer.setVisibility(GONE); } else { llContainer.setVisibility(VISIBLE); - tvCurrency.setText(currencySwitcher.getCurrentFiatCurrency()); String formattedFiatValue; - if (precision >= 0){ - formattedFiatValue = currencySwitcher.getFormattedFiatValue(satoshis, false, precision); + + // convert to the target fiat currency, if needed + CurrencyValue valueToShow = currencySwitcher.getAsFiatValue(currentValue); + + if (precision >= 0) { + formattedFiatValue = currencySwitcher.getFormattedFiatValue(valueToShow, false, precision); } else { - formattedFiatValue = currencySwitcher.getFormattedFiatValue(satoshis, false); + formattedFiatValue = currencySwitcher.getFormattedFiatValue(valueToShow, false); } + tvCurrency.setText(currencySwitcher.getCurrentFiatCurrency()); tvValue.setText(formattedFiatValue); } } - public void setEventBus(Bus eventBus){ + public void setEventBus(Bus eventBus) { this.eventBus = eventBus; - this.eventBus.register(this); } @@ -184,18 +188,23 @@ protected void onDetachedFromWindow() { super.onDetachedFromWindow(); // unregister from the event bus - if (eventBus != null){ + if (eventBus != null) { eventBus.unregister(this); } } - public void setCurrencySwitcher(CurrencySwitcher currencySwitcher){ + public void setCurrencySwitcher(CurrencySwitcher currencySwitcher) { this.currencySwitcher = currencySwitcher; updateUi(); } - public void setValue(long satoshis){ - this.satoshis = satoshis; + public void setValue(CurrencyValue value) { + this.currentValue = value; + updateUi(); + } + + public void setValue(CurrencySum sum) { + this.currentValue = currencySwitcher.getValueFromSum(sum); updateUi(); } @@ -204,12 +213,12 @@ public void setFiatOnly(boolean fiatOnly) { } @Subscribe - public void onExchangeRateChange(ExchangeRatesRefreshed event){ + public void onExchangeRateChange(ExchangeRatesRefreshed event) { updateUi(); } @Subscribe - public void onSelectedCurrencyChange(SelectedCurrencyChanged event){ + public void onSelectedCurrencyChange(SelectedCurrencyChanged event) { updateUi(); } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/bitid/BitIDAuthenticationActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/bitid/BitIDAuthenticationActivity.java index 9accf924cf..ccf506c4b4 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/bitid/BitIDAuthenticationActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/bitid/BitIDAuthenticationActivity.java @@ -210,10 +210,11 @@ private void handleError(BitIdResponse response) { } } } else if (code >= 500 && code < 600) { - //server-side error + // server-side error userInfo = getString(R.string.bitid_error); } else { - //redirect or strange status code + // redirect or strange status code + // return same error, maybe refine later userInfo = getString(R.string.bitid_error); } errorView.setText(userInfo); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/bitid/ExternalService.java b/public/mbw/src/main/java/com/mycelium/wallet/bitid/ExternalService.java index 48702ecc03..a44ef53b1b 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/bitid/ExternalService.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/bitid/ExternalService.java @@ -51,7 +51,7 @@ public enum ExternalService { private final String testnetApi; private final int welcomeMessageId; - private ExternalService(String prodnetHost, String testnetHost, String prodnetApi, String testnetApi, int welcomeMessageId) { + ExternalService(String prodnetHost, String testnetHost, String prodnetApi, String testnetApi, int welcomeMessageId) { this.prodnetHost = prodnetHost; this.testnetHost = testnetHost; this.prodnetApi = prodnetApi; @@ -76,12 +76,7 @@ public void showWelcomeMessage(Context context) { .setTitle(context.getString(R.string.cashila_title_paired)) .setMessage(context.getString(welcomeMessageId)) .setIcon(android.R.drawable.ic_dialog_alert) - .setPositiveButton(context.getString(R.string.ok),new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - //dont do anything for now - } - }) + .setPositiveButton(context.getString(R.string.ok), null) .show(); } } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/CoinapultManager.java b/public/mbw/src/main/java/com/mycelium/wallet/coinapult/CoinapultAccount.java similarity index 51% rename from public/mbw/src/main/java/com/mycelium/wallet/CoinapultManager.java rename to public/mbw/src/main/java/com/mycelium/wallet/coinapult/CoinapultAccount.java index 0fe2f11d2c..67ca8fdde6 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/CoinapultManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/coinapult/CoinapultAccount.java @@ -1,9 +1,45 @@ -package com.mycelium.wallet; +/* + * Copyright 2013, 2014 Megion Research and Development GmbH + * + * Licensed under the Microsoft Reference Source License (MS-RSL) + * + * This license governs use of the accompanying software. If you use the software, you accept this license. + * If you do not accept the license, do not use the software. + * + * 1. Definitions + * The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law. + * "You" means the licensee of the software. + * "Your company" means the company you worked for when you downloaded the software. + * "Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes + * of debugging your products, maintaining your products, or enhancing the interoperability of your products with the + * software, and specifically excludes the right to distribute the software outside of your company. + * "Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor + * under this license. + * + * 2. Grant of Rights + * (A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, + * worldwide, royalty-free copyright license to reproduce the software for reference use. + * (B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, + * worldwide, royalty-free patent license under licensed patents for reference use. + * + * 3. Limitations + * (A) No Trademark License- This license does not grant you any rights to use the Licensor’s name, logo, or trademarks. + * (B) If you begin patent litigation against the Licensor over patents that you think may apply to the software + * (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically. + * (C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, + * guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot + * change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, + * fitness for a particular purpose and non-infringement. + */ + +package com.mycelium.wallet.coinapult; import android.os.AsyncTask; import android.os.Handler; import android.util.Log; -import com.coinapult.api.httpclient.*; +import com.coinapult.api.httpclient.AccountInfo; +import com.coinapult.api.httpclient.CoinapultClient; +import com.coinapult.api.httpclient.Transaction; import com.google.api.client.repackaged.com.google.common.base.Preconditions; import com.google.common.base.Function; import com.google.common.base.Optional; @@ -15,9 +51,12 @@ import com.mrd.bitlib.model.Address; import com.mrd.bitlib.model.NetworkParameters; import com.mrd.bitlib.model.OutputList; -import com.mrd.bitlib.model.Transaction; +import com.mrd.bitlib.util.ByteWriter; +import com.mrd.bitlib.util.HashUtils; import com.mrd.bitlib.util.Sha256Hash; import com.mycelium.WapiLogger; +import com.mycelium.wallet.ExchangeRateManager; +import com.mycelium.wallet.MbwManager; import com.mycelium.wallet.event.BalanceChanged; import com.mycelium.wallet.event.SyncFailed; import com.mycelium.wallet.persistence.MetadataStorage; @@ -27,7 +66,6 @@ import com.mycelium.wapi.wallet.currency.CurrencyBasedBalance; import com.mycelium.wapi.wallet.currency.CurrencyValue; import com.mycelium.wapi.wallet.currency.ExactCurrencyValue; -import com.mycelium.wapi.wallet.currency.ExactFiatValue; import com.squareup.otto.Bus; import javax.annotation.Nullable; @@ -37,128 +75,93 @@ import java.math.MathContext; import java.nio.ByteBuffer; import java.security.NoSuchAlgorithmException; +import java.text.DecimalFormat; import java.util.*; -import static com.coinapult.api.httpclient.CoinapultClient.CoinapultBackendException; +public class CoinapultAccount implements WalletAccount { + private static final Balance EMPTY_BALANCE = new Balance(0, 0, 0, 0, 0, 0, true, true); + private static final BigDecimal SATOSHIS_PER_BTC = BigDecimal.valueOf(100000000); -public class CoinapultManager implements WalletAccount { - - public static final BigDecimal SATOSHIS_PER_BTC = BigDecimal.valueOf(100000000); - public static final Balance EMPTY_BALANCE = new Balance(0, 0, 0, 0, 0, 0, true, true); - public static final BigDecimal MINIMUM_USD_AMOUNT = new BigDecimal("1.0"); - public static final Predicate TX_NOT_CONVERSION = Predicates.not(new Predicate() { + public static final Predicate TX_NOT_CONVERSION = Predicates.not(new Predicate() { @Override public boolean apply(@Nullable com.coinapult.api.httpclient.Transaction.Json input) { return input.type.equals("conversion"); } }); - private final MbwEnvironment env; - private final Bus eventBus; - private final UUID uuid; - private CoinapultClient coinapultClient; - private Optional

currentAddress = Optional.absent(); - private CurrencyBasedBalance balanceUSD; + + private final CoinapultManager manager; private final InMemoryPrivateKey accountKey; + private final Bus eventBus; private final Handler handler; - private final MetadataStorage metadataStorage; + private final UUID uuid; private final ExchangeRateManager exchangeRateManager; + private final MetadataStorage metadataStorage; + private final WapiLogger logger; + + private final Currency coinapultCurrency; + private boolean archived; - private List history; + private List accountHistory; + + // list to hold local generated transactions, to ensure they show up in the tx-hist list correctly private List extraHistory = Lists.newArrayList(); - public CoinapultManager(MbwEnvironment env, BitidKeyDerivation bitidKeyDerivation, final Bus eventBus, Handler handler, MetadataStorage metadataStorage, ExchangeRateManager exchangeRateManager, WapiLogger logger) { - this.env = env; + private Optional
currentAddress = Optional.absent(); + private CurrencyBasedBalance balanceFiat; + + + public CoinapultAccount(CoinapultManager manager, MetadataStorage metadataStorage, InMemoryPrivateKey accountKey, + ExchangeRateManager exchangeRateManager, Handler handler, Bus eventBus, WapiLogger logger, Currency coinapultCurrency) { + this.manager = manager; + this.accountKey = accountKey; this.eventBus = eventBus; this.handler = handler; - this.metadataStorage = metadataStorage; this.exchangeRateManager = exchangeRateManager; this.logger = logger; - accountKey = bitidKeyDerivation.deriveKey(0, "coinapult.com"); - coinapultClient = createClient(); - uuid = UUID.fromString(getGuidFromByteArray(accountKey.getPrivateKeyBytes())); - archived = metadataStorage.getArchived(uuid); - metadataStorage.storeAccountLabel(uuid, "Coinapult USD"); - handler.post(new Runnable() { - @Override - public void run() { - eventBus.register(CoinapultManager.this); - } - }); + this.metadataStorage = metadataStorage; + this.coinapultCurrency = coinapultCurrency; - } + // derive the UUID for the account from the "sha256(PubKey(AccountPrivateKey) || )" + ByteWriter byteWriter = new ByteWriter(36); + byteWriter.putBytes(accountKey.getPublicKey().getPublicKeyBytes()); + byteWriter.putRawStringUtf8(coinapultCurrency.name); + Sha256Hash accountId = HashUtils.sha256(byteWriter.toBytes()); + uuid = getGuidFromByteArray(accountId.getBytes()); - public InMemoryPrivateKey getAccountKey() { - return accountKey; + archived = metadataStorage.getArchived(uuid); } - public static String getGuidFromByteArray(byte[] bytes) { + public static UUID getGuidFromByteArray(byte[] bytes) { ByteBuffer bb = ByteBuffer.wrap(bytes); long high = bb.getLong(); long low = bb.getLong(); UUID uuid = new UUID(high, low); - return uuid.toString(); + return uuid; } - public void queryActive() throws CoinapultBackendException { - if (coinapultClient.accountExists()) { - initBalance(); + public void queryActive() throws CoinapultClient.CoinapultBackendException { + if (manager.userAccountExists()) { + refreshReceivingAddress(); } } - public void addUSD(Optional mail) throws CoinapultBackendException { - try { - if (!coinapultClient.accountExists()) { - Map options = new HashMap(); - if (mail.isPresent()) { - options.put("email", mail.get()); - } - coinapultClient.createAccount(options); - coinapultClient.activateAccount(true); - } - initBalance(); - } catch (Exception e) { - Log.e("CoinapultManager", "Failed to addUsd account", e); - throw new CoinapultClient.CoinapultBackendException(e); - } - } - private void initBalance() { - try { - String address; - Optional
lastCoinapultAddress = metadataStorage.getCoinapultAddress(); - if (!lastCoinapultAddress.isPresent()) { - //requesting fresh address - address = coinapultClient.getBitcoinAddress().address; - } else { - // check if address was already used (via new coinapult api), - // if so: request a fresh one from coinapult - HashMap criteria = new HashMap(1); - criteria.put("to", lastCoinapultAddress.get().toString()); - com.coinapult.api.httpclient.Transaction.Json search = coinapultClient.search(criteria); - boolean alreadyUsed = search.containsKey("transaction_id"); - if (alreadyUsed) { - // get a new one - address = coinapultClient.getBitcoinAddress().address; - } else { - address = lastCoinapultAddress.get().toString(); - } - } - //setting preference to USD - coinapultClient.config(address, "USD"); - currentAddress = Optional.of(Address.fromString(address)); - metadataStorage.storeCoinapultAddress(currentAddress.get()); - } catch (Exception e) { - Log.e("CoinapultManager", "Failed to initBalance", e); - handler.post(new Runnable() { - @Override - public void run() { - eventBus.post(new SyncFailed()); - } - }); + @Override + public void checkAmount(Receiver receiver, long kbMinerFee, CurrencyValue enteredAmount) throws StandardTransactionBuilder.InsufficientFundsException, StandardTransactionBuilder.OutputTooSmallException { + Optional sendValue = com.mycelium.wapi.wallet.currency.CurrencyValue.checkCurrencyAmount(enteredAmount, coinapultCurrency.name); + + if (balanceFiat == null || sendValue.isPresent() && sendValue.get().getValue().compareTo(balanceFiat.confirmed.getValue()) > 0) { + //not enough funds + throw new StandardTransactionBuilder.InsufficientFundsException(receiver.amount, 0); + } + if (!sendValue.isPresent() && receiver.amount > getSatoshis(balanceFiat.confirmed)) { + //non-fiat value, but approximately not enough funds + throw new StandardTransactionBuilder.InsufficientFundsException(receiver.amount, 0); } } + // if it is a fiat value convert it, otherwise take the exact value private long getSatoshis(BigDecimal amount, String currency) { if (currency.equals("BTC")) { return amount.multiply(SATOSHIS_PER_BTC).longValue(); @@ -172,62 +175,193 @@ private long getSatoshis(BigDecimal amount, String currency) { } } - private AccountInfo.Balance getBalanceUSD() throws CoinapultBackendException { - List balances; + // if it is a fiat value convert it, otherwise take the exact value + private long getSatoshis(ExactCurrencyValue confirmed) { + return getSatoshis(confirmed.getValue(), confirmed.getCurrency()); + } + + private ExactCurrencyValue getCurrencyValue(com.coinapult.api.httpclient.Transaction.Half half) { + return ExactCurrencyValue.from(half.expected, half.currency); + } + + private void queryHistory() throws CoinapultClient.CoinapultBackendException { + // get the complete history from the manager, and filter out only the relevant tx + accountHistory = filterHistory(manager.getHistory()); + + // now we know the accountHistory, we know if funds are incoming + buildBalance(); + } + + private List filterHistory(List completeHistory) { + if (completeHistory == null) { + return null; + } + return Lists.newArrayList( + Iterables.filter(completeHistory, new Predicate() { + @Override + public boolean apply(@Nullable Transaction.Json input) { + // only add items with the correct currency for the current selected account + if (input != null) { + if (input.state.equals("canceled")) { + // todo: show somehow that a tx got canceled + // dont show canceled transactions + return false; + } + + boolean isSending = isSending(input); + // depending on the sending/incoming direction, check either in or out half + if (isSending) { + return input.in.currency.equals(coinapultCurrency.name); + } else { + return input.out.currency.equals(coinapultCurrency.name); + } + } + return false; + } + } + ) + ); + + } + + public PreparedCoinapult prepareCoinapultTx(Address receivingAddress, ExactCurrencyValue amountEntered) throws StandardTransactionBuilder.InsufficientFundsException { + if (balanceFiat.confirmed.getValue().compareTo(amountEntered.getValue()) < 0) { + throw new StandardTransactionBuilder.InsufficientFundsException(getSatoshis(amountEntered), 0); + } + return new PreparedCoinapult(receivingAddress, amountEntered); + } + + + public PreparedCoinapult prepareCoinapultTx(WalletAccount.Receiver receiver) throws StandardTransactionBuilder.InsufficientFundsException { + if (getSatoshis(balanceFiat.confirmed) < receiver.amount) { + throw new StandardTransactionBuilder.InsufficientFundsException(receiver.amount, 0); + } else { + return new PreparedCoinapult(receiver); + } + } + + public boolean broadcast(PreparedCoinapult preparedCoinapult) { try { - balances = coinapultClient.accountInfo().balances; - //expect only a usd balance - for (AccountInfo.Balance bal : balances) { - if (bal.currency.equals("USD")) { - return bal; - } + final com.coinapult.api.httpclient.Transaction.Json send; + if (preparedCoinapult.amount != null) { + send = manager.getClient().send(preparedCoinapult.amount.getValue(), coinapultCurrency.name, preparedCoinapult.address.toString(), BigDecimal.ZERO, null, null, null); + } else { + BigDecimal fullBtc = new BigDecimal(preparedCoinapult.satoshis).divide(SATOSHIS_PER_BTC, MathContext.DECIMAL32); + send = manager.getClient().send(BigDecimal.ZERO, coinapultCurrency.name, preparedCoinapult.address.toString(), fullBtc, null, null, null); + } + Object error = send.get("error"); + if (error != null) { + return false; + } + Object transaction_id = send.get("transaction_id"); + boolean hasId = transaction_id != null; + if (hasId) { + extraHistory.add(send); } + return hasId; + } catch (IOException e) { + return false; } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); - } catch (IOException e) { - throw new CoinapultBackendException(e); } - - AccountInfo.Balance empty = new AccountInfo.Balance(); - empty.currency = "USD"; - empty.amount = BigDecimal.ZERO; - return empty; } - private CoinapultClient createClient() { - CoinapultConfig cc; - NetworkParameters network = env.getNetwork(); - if (network.equals(NetworkParameters.testNetwork)) { - cc = new CoinapultPlaygroundConfig(); - } else if (network.equals(NetworkParameters.productionNetwork)) { - cc = new CoinapultProdConfig(); + private AccountInfo.Balance getBalanceFiat() throws CoinapultClient.CoinapultBackendException { + Map balances; + balances = manager.getBalances(); + + if (balances.containsKey(coinapultCurrency.name)) { + return balances.get(coinapultCurrency.name); } else { - throw new IllegalStateException("unknown network: " + network); + AccountInfo.Balance empty = new AccountInfo.Balance(); + empty.currency = coinapultCurrency.name; + empty.amount = BigDecimal.ZERO; + return empty; + } + } + + private void buildBalance() throws CoinapultClient.CoinapultBackendException { + AccountInfo.Balance balanceFiat = getBalanceFiat(); + final long oneMinuteAgo = new Date().getTime() - 1000 * 60; + + BigDecimal receivingFiat = BigDecimal.ZERO; + BigDecimal receivingFiatNotIncludedInBalance = BigDecimal.ZERO; + BigDecimal sendingFiatNotIncludedInBalance = BigDecimal.ZERO; + for (com.coinapult.api.httpclient.Transaction.Json json : getHistoryWithExtras()) { + boolean sending = isSending(json); + if (json.state.equals("processing") || json.completeTime * 1000 > oneMinuteAgo) { + if (sending) { + sendingFiatNotIncludedInBalance = sendingFiatNotIncludedInBalance.add(json.in.expected); + } else { + receivingFiat = receivingFiat.add(json.out.amount); + } + } else if (json.state.equals("confirming")) { + if (sending) { + sendingFiatNotIncludedInBalance = sendingFiatNotIncludedInBalance.add(json.in.expected); + } else { + receivingFiatNotIncludedInBalance = receivingFiatNotIncludedInBalance.add(json.out.expected); + } + } } + BigDecimal withoutUnconfirmed = balanceFiat.amount.subtract(receivingFiat); + BigDecimal totalReceiving = receivingFiat.add(receivingFiatNotIncludedInBalance); - return new CoinapultClient(AndroidKeyConverter.convertKeyFormat(accountKey), new ECC_SC(), cc, logger); + if (withoutUnconfirmed.compareTo(BigDecimal.ZERO) < 0) { + MbwManager.getInstance(null).reportIgnoredException( + new RuntimeException(String + .format("Calculated withoutUnconfirmed-amount for coinapult account is negative withoutUnconfirmed: %f, sending: %f, recv: %f", withoutUnconfirmed, sendingFiatNotIncludedInBalance, totalReceiving)) + ); + withoutUnconfirmed = BigDecimal.ZERO; + } + + if (sendingFiatNotIncludedInBalance.compareTo(BigDecimal.ZERO) < 0) { + MbwManager.getInstance(null).reportIgnoredException( + new RuntimeException(String + .format("Calculated sendingUsdNotIncludedInBalance-amount for coinapult account is negative withoutUnconfirmed: %f, sending: %f, recv: %f", withoutUnconfirmed, sendingFiatNotIncludedInBalance, totalReceiving)) + ); + sendingFiatNotIncludedInBalance = BigDecimal.ZERO; + } + + if (totalReceiving.compareTo(BigDecimal.ZERO) < 0) { + MbwManager.getInstance(null).reportIgnoredException( + new RuntimeException(String + .format("Calculated totalReceiving-amount for coinapult account is negative withoutUnconfirmed: %f, sending: %f, recv: %f", withoutUnconfirmed, sendingFiatNotIncludedInBalance, totalReceiving)) + ); + sendingFiatNotIncludedInBalance = BigDecimal.ZERO; + } + + + this.balanceFiat = new CurrencyBasedBalance( + ExactCurrencyValue.from(withoutUnconfirmed, coinapultCurrency.name), + ExactCurrencyValue.from(sendingFiatNotIncludedInBalance, coinapultCurrency.name), + ExactCurrencyValue.from(totalReceiving, coinapultCurrency.name) + ); } - @Override - public void checkAmount(Receiver receiver, long kbMinerFee, CurrencyValue enteredAmount) throws StandardTransactionBuilder.InsufficientFundsException, StandardTransactionBuilder.OutputTooSmallException { - Optional usd = CurrencyValue.checkUsdAmount(enteredAmount); - if (usd.isPresent() && usd.get().getValue().compareTo(MINIMUM_USD_AMOUNT) < 0) { - //output too small - //JD, July 16th, 2015: commented this out, because if the number pad does not accept small amounts without explanation, - //users will get confused -> opt to show an explanation in sendmain - //throw new StandardTransactionBuilder.OutputTooSmallException(receiver.amount); - } - if (balanceUSD == null || usd.isPresent() && usd.get().getValue().compareTo(balanceUSD.confirmed.getValue()) > 0) { - //not enough funds - throw new StandardTransactionBuilder.InsufficientFundsException(receiver.amount, 0); + + private boolean isSending(com.coinapult.api.httpclient.Transaction.Json input) { + boolean isPayment = input.type.equals("payment"); + if (isPayment) { + return true; } - if (!usd.isPresent() && receiver.amount > getSatoshis(balanceUSD.confirmed)) { - //non-usd value, but approximately not enough funds - throw new StandardTransactionBuilder.InsufficientFundsException(receiver.amount, 0); + boolean isInvoice = input.type.equals("invoice"); + if (isInvoice) { + return false; + } + // other unexpected tx type - but ignore it + return false; + } + + private List limitedList(int offset, int limit, List list) { + if (offset >= list.size()) { + return Collections.emptyList(); } + int endIndex = Math.min(offset + limit, list.size()); + return Collections.unmodifiableList(list.subList(offset, endIndex)); } + @Override public NetworkParameters getNetwork() { return NetworkParameters.productionNetwork; @@ -240,7 +374,6 @@ public UUID getId() { @Override public void setAllowZeroConfSpending(boolean allowZeroConfSpending) { - } @Override @@ -251,7 +384,7 @@ public int getBlockChainHeight() { @Override public Optional
getReceivingAddress() { if (!currentAddress.isPresent()) { - currentAddress = metadataStorage.getCoinapultAddress(); + currentAddress = metadataStorage.getCoinapultAddress(coinapultCurrency.name); } return currentAddress; } @@ -263,27 +396,29 @@ public boolean canSpend() { @Override public Balance getBalance() { - if (balanceUSD == null) { + if (balanceFiat == null) { return EMPTY_BALANCE; } - ExactCurrencyValue confirmed = balanceUSD.confirmed; - return new Balance(getSatoshis(confirmed), getSatoshis(balanceUSD.receiving), getSatoshis(balanceUSD.sending), 0, 0, 0, false, true); - } - - private long getSatoshis(ExactCurrencyValue confirmed) { - return getSatoshis(confirmed.getValue(), confirmed.getCurrency()); //currency should be USD + ExactCurrencyValue confirmed = balanceFiat.confirmed; + return new Balance(getSatoshis(confirmed), getSatoshis(balanceFiat.receiving), getSatoshis(balanceFiat.sending), 0, 0, 0, false, true); } @Override public List getTransactionHistory(int offset, int limit) { - if (history != null) { - List list = Lists.transform(getHistoryWithExtras(), new Function() { + if (accountHistory != null) { + List list = Lists.transform(getHistoryWithExtras(), new Function() { @Nullable @Override public TransactionSummary apply(@Nullable com.coinapult.api.httpclient.Transaction.Json input) { input = Preconditions.checkNotNull(input); Optional
address = Optional.fromNullable(input.address).transform(Address.FROM_STRING); - return new CoinapultTransactionSummary(address, satoshiDifference(input), input); + boolean isIncoming = !isSending(input); + // use the relevant amount from the transaction. + // if it is an incoming transaction, the "out"-side of the tx is in the native currency + // and v.v. for outgoing tx + Transaction.Half half = isIncoming ? input.out : input.in; + + return new CoinapultTransactionSummary(address, getCurrencyValue(half), isIncoming, input); } }); return limitedList(offset, limit, list); @@ -293,62 +428,6 @@ public TransactionSummary apply(@Nullable com.coinapult.api.httpclient.Transacti } } - private List getHistoryWithExtras() { - Function tx = new Function() { - @Nullable - @Override - public String apply(@Nullable com.coinapult.api.httpclient.Transaction.Json input) { - return input.tid; - } - }; - ImmutableMap id2Tx1 = Maps.uniqueIndex(extraHistory, tx); - ImmutableMap id2Tx2 = Maps.uniqueIndex(history, tx); - HashMap merged = Maps.newHashMap(); - merged.putAll(id2Tx1); - merged.putAll(id2Tx2); //history overwrites local results - Collection unfiltered = merged.values(); - Iterable withoutConversion = Iterables.filter(unfiltered, TX_NOT_CONVERSION); - ImmutableList ret = Ordering.natural().onResultOf(new Function() { - @Nullable - @Override - public Comparable apply(@Nullable com.coinapult.api.httpclient.Transaction.Json input) { - Long completeTime = input.completeTime; - if (completeTime.equals(0L)) { - return input.timestamp; - } - return completeTime; - } - }).reverse().immutableSortedCopy(withoutConversion); - return ret; - } - - private long satoshiDifference(com.coinapult.api.httpclient.Transaction.Json input) { - boolean isSending = isSending(input); - int sign = isSending ? -1 : 1; - return sign * CoinapultManager.this.getSatoshis(input.out.expected, input.out.currency); - } - - private boolean isSending(com.coinapult.api.httpclient.Transaction.Json input) { - boolean isPayment = input.type.equals("payment"); - if (isPayment) { - return true; - } - boolean isInvoice = input.type.equals("invoice"); - if (isInvoice) { - return false; - } - // other unexpected tx type - but ignore it - return false; - } - - private List limitedList(int offset, int limit, List list) { - if (offset >= list.size()) { - return Collections.emptyList(); - } - int endIndex = Math.min(offset + limit, list.size()); - return Collections.unmodifiableList(list.subList(offset, endIndex)); - } - @Override public TransactionSummary getTransactionSummary(Sha256Hash txid) { return null; @@ -393,7 +472,7 @@ private class AddCoinapultAsyncTask extends AsyncTask { @Override protected UUID doInBackground(Void... params) { try { - addUSD(Optional.absent()); + manager.activateAccount(Optional.absent()); } catch (CoinapultClient.CoinapultBackendException e) { return null; } @@ -405,6 +484,43 @@ protected void onPostExecute(UUID account) { } } + private void refreshReceivingAddress() { + try { + String address; + Optional
lastCoinapultAddress = metadataStorage.getCoinapultAddress(coinapultCurrency.name); + if (!lastCoinapultAddress.isPresent()) { + //requesting fresh address + address = manager.getClient().getBitcoinAddress().address; + } else { + // check if address was already used (via new coinapult api), + // if so: request a fresh one from coinapult + HashMap criteria = new HashMap(1); + criteria.put("to", lastCoinapultAddress.get().toString()); + com.coinapult.api.httpclient.Transaction.Json search = manager.getClient().search(criteria); + boolean alreadyUsed = search.containsKey("transaction_id"); + if (alreadyUsed) { + // get a new one + address = manager.getClient().getBitcoinAddress().address; + } else { + address = lastCoinapultAddress.get().toString(); + } + } + //setting preference to the selected fiat currency + manager.getClient().config(address, coinapultCurrency.name); + currentAddress = Optional.of(Address.fromString(address)); + metadataStorage.storeCoinapultAddress(currentAddress.get(), coinapultCurrency.name); + } catch (Exception e) { + Log.e("CoinapultManager", "Failed to refreshReceivingAddress", e); + handler.post(new Runnable() { + @Override + public void run() { + eventBus.post(new SyncFailed()); + } + }); + } + } + + @Override public StandardTransactionBuilder.UnsignedTransaction createUnsignedTransaction(List receivers, long minerFeeToUse) throws StandardTransactionBuilder.OutputTooSmallException, StandardTransactionBuilder.InsufficientFundsException { throw new IllegalStateException("not supported, use prepareCoinaputTX instead"); @@ -416,7 +532,8 @@ public StandardTransactionBuilder.UnsignedTransaction createUnsignedTransaction( } @Override - public Transaction signTransaction(StandardTransactionBuilder.UnsignedTransaction unsigned, KeyCipher cipher) throws KeyCipher.InvalidKeyCipher { + public com.mrd.bitlib.model.Transaction signTransaction(StandardTransactionBuilder.UnsignedTransaction unsigned, KeyCipher + cipher) throws KeyCipher.InvalidKeyCipher { return null; } @@ -434,13 +551,16 @@ public boolean isMine(Address address) { @Override public boolean synchronize(boolean synchronizeTransactionHistory) { + return synchronizeIntern(synchronizeTransactionHistory, true); + } + public boolean synchronizeIntern(boolean synchronizeTransactionHistory, boolean scanForAccounts) { try { queryActive(); if (synchronizeTransactionHistory) { queryHistory(); } - } catch (CoinapultBackendException e) { + } catch (CoinapultClient.CoinapultBackendException e) { Log.e("CoinapultManager", "Failed to query history", e); handler.post(new Runnable() { @Override @@ -456,94 +576,47 @@ public void run() { eventBus.post(new BalanceChanged(uuid)); } }); - return true; - } - private void queryHistory() throws CoinapultBackendException { - SearchMany.Json batch; - //get first page to get pageCount - batch = coinapultClient.history(1); - history = Lists.newArrayList(); - addToHistory(batch); - //get extra pages - for (int i = 2; batch.page < batch.pageCount; i++) { - batch = coinapultClient.history(i); - addToHistory(batch); + if (scanForAccounts) { + manager.scanForAccounts(); } - //now we know the history, we know if funds are incoming - - buildBalance(); + return true; } - private void buildBalance() throws CoinapultBackendException { - AccountInfo.Balance balanceUSD1 = getBalanceUSD(); - final long oneMinuteAgo = new Date().getTime() - 1000 * 60; - - BigDecimal receivingUsd = BigDecimal.ZERO; - BigDecimal receivingUsdNotIncludedInBalance = BigDecimal.ZERO; - BigDecimal sendingUsdNotIncludedInBalance = BigDecimal.ZERO; - for (com.coinapult.api.httpclient.Transaction.Json json : getHistoryWithExtras()) { - boolean sending = isSending(json); - if (json.state.equals("processing") || json.completeTime * 1000 > oneMinuteAgo) { - if (sending) { - sendingUsdNotIncludedInBalance = sendingUsdNotIncludedInBalance.add(json.in.expected); - } else { - receivingUsd = receivingUsd.add(json.out.amount); - } - } else if (json.state.equals("confirming")) { - if (sending) { - sendingUsdNotIncludedInBalance = sendingUsdNotIncludedInBalance.add(json.in.expected); - } else { - receivingUsdNotIncludedInBalance = receivingUsdNotIncludedInBalance.add(json.out.expected); + private List getHistoryWithExtras() { + if (accountHistory == null) { + return Lists.newArrayList(); + } + Function tx = new Function() { + @Nullable + @Override + public String apply(@Nullable com.coinapult.api.httpclient.Transaction.Json input) { + return input.tid; + } + }; + ImmutableMap id2Tx1 = Maps.uniqueIndex(extraHistory, tx); + ImmutableMap id2Tx2 = Maps.uniqueIndex(accountHistory, tx); + HashMap merged = Maps.newHashMap(); + merged.putAll(id2Tx1); + merged.putAll(id2Tx2); //accountHistory overwrites local results + Collection unfiltered = merged.values(); + Iterable withoutConversion = Iterables.filter(unfiltered, TX_NOT_CONVERSION); + ImmutableList ret = Ordering.natural().onResultOf(new Function() { + @Nullable + @Override + public Comparable apply(@Nullable com.coinapult.api.httpclient.Transaction.Json input) { + Long completeTime = input.completeTime; + if (completeTime.equals(0L)) { + return input.timestamp; } + return completeTime; } - } - BigDecimal withoutUnconfirmed = balanceUSD1.amount.subtract(receivingUsd); - BigDecimal totalReceiving = receivingUsd.add(receivingUsdNotIncludedInBalance); - - if (withoutUnconfirmed.compareTo(BigDecimal.ZERO) < 0) { - MbwManager.getInstance(null).reportIgnoredException( - new RuntimeException(String - .format("Calculated withoutUnconfirmed-amount for coinapult account is negative withoutUnconfirmed: %f, sending: %f, recv: %f", withoutUnconfirmed, sendingUsdNotIncludedInBalance, totalReceiving)) - ); - withoutUnconfirmed=BigDecimal.ZERO; - } - - if (sendingUsdNotIncludedInBalance.compareTo(BigDecimal.ZERO) < 0) { - MbwManager.getInstance(null).reportIgnoredException( - new RuntimeException(String - .format("Calculated sendingUsdNotIncludedInBalance-amount for coinapult account is negative withoutUnconfirmed: %f, sending: %f, recv: %f", withoutUnconfirmed, sendingUsdNotIncludedInBalance, totalReceiving)) - ); - sendingUsdNotIncludedInBalance=BigDecimal.ZERO; - } - - if (totalReceiving.compareTo(BigDecimal.ZERO) < 0) { - MbwManager.getInstance(null).reportIgnoredException( - new RuntimeException(String - .format("Calculated totalReceiving-amount for coinapult account is negative withoutUnconfirmed: %f, sending: %f, recv: %f", withoutUnconfirmed, sendingUsdNotIncludedInBalance, totalReceiving)) - ); - sendingUsdNotIncludedInBalance=BigDecimal.ZERO; - } - - balanceUSD = new CurrencyBasedBalance( - ExactCurrencyValue.from(withoutUnconfirmed, "USD"), - ExactCurrencyValue.from(sendingUsdNotIncludedInBalance, "USD"), - ExactCurrencyValue.from(totalReceiving, "USD") - ); - } - - private void addToHistory(SearchMany.Json batch) { - if (batch == null) { - return; - } - if (batch.result == null) { - return; - } - history.addAll(batch.result); + }).reverse().immutableSortedCopy(withoutConversion); + return ret; } @Override - public BroadcastResult broadcastTransaction(Transaction transaction) { + public BroadcastResult broadcastTransaction(com.mrd.bitlib.model.Transaction transaction) { return null; } @@ -553,7 +626,7 @@ public TransactionEx getTransaction(Sha256Hash txid) { } @Override - public void queueTransaction(Transaction transaction) { + public void queueTransaction(com.mrd.bitlib.model.Transaction transaction) { } @@ -602,96 +675,32 @@ public List getUnspentTransactionOutputSummary() { return null; } - public PreparedCoinapult prepareCoinapultTx(Receiver receiver) throws StandardTransactionBuilder.InsufficientFundsException { - if (getSatoshis(balanceUSD.confirmed) < receiver.amount) { - throw new StandardTransactionBuilder.InsufficientFundsException(receiver.amount, 0); - } else { - return new PreparedCoinapult(receiver); - } - } - - public boolean broadcast(PreparedCoinapult preparedCoinapult) { - try { - final com.coinapult.api.httpclient.Transaction.Json send; - if (preparedCoinapult.amount != null) { - send = coinapultClient.send(preparedCoinapult.amount.getValue(), "USD", preparedCoinapult.address.toString(), BigDecimal.ZERO, null, null, null); - } else { - BigDecimal fullBtc = new BigDecimal(preparedCoinapult.satoshis).divide(SATOSHIS_PER_BTC, MathContext.DECIMAL32); - send = coinapultClient.send(BigDecimal.ZERO, "USD", preparedCoinapult.address.toString(), fullBtc, null, null, null); - } - extraHistory.add(send); - Object error = send.get("error"); - if (error != null) { - return false; - } - Object transaction_id = send.get("transaction_id"); - if (transaction_id != null) { - return true; - } - return false; - } catch (IOException e) { - return false; - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - public CurrencyBasedBalance getCurrencyBasedBalance() { - if (balanceUSD != null) { - return balanceUSD; - } //result precomputed from history query + if (balanceFiat != null) { + return balanceFiat; + } //result precomputed from accountHistory query - // if we dont have a balance, return 0 - ExactFiatValue zero = new ExactFiatValue("USD", BigDecimal.ZERO); + // if we dont have a balance, return 0 in the accounts native currency + ExactCurrencyValue zero = ExactCurrencyValue.from(BigDecimal.ZERO, coinapultCurrency.name); return new CurrencyBasedBalance(zero, zero, zero, true); } - public PreparedCoinapult prepareCoinapultTx(Address receivingAddress, ExactFiatValue amountEntered) throws StandardTransactionBuilder.InsufficientFundsException { - if (balanceUSD.confirmed.getValue().compareTo(amountEntered.getValue()) < 0) { - throw new StandardTransactionBuilder.InsufficientFundsException(getSatoshis(amountEntered), 0); - } - return new PreparedCoinapult(receivingAddress, amountEntered); - } - - public boolean setMail(Optional mail) { - if (!mail.isPresent()) { - return false; - } - try { - EmailAddress.Json result = coinapultClient.setMail(mail.get()); - return result.email != null && result.email.equals(mail.get()); - } catch (IOException e) { - return false; - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - public boolean verifyMail(String link, String email) { - try { - EmailAddress.Json result = coinapultClient.verifyMail(link, email); - if (!result.verified) { - logger.logError("Coinapult email error: " + result.error); - } - return result.verified; - } catch (IOException e) { - return false; - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } + @Override + public boolean onlySyncWhenActive() { + return true; } public static class PreparedCoinapult implements Serializable { final Address address; Long satoshis; - ExactFiatValue amount; + ExactCurrencyValue amount; - public PreparedCoinapult(Address address, ExactFiatValue value) { + public PreparedCoinapult(Address address, ExactCurrencyValue value) { this.address = address; this.amount = value; } - public PreparedCoinapult(Receiver receiver) { + public PreparedCoinapult(WalletAccount.Receiver receiver) { address = receiver.address; satoshis = receiver.amount; } @@ -704,9 +713,67 @@ public String toString() { } } + public String getDefaultLabel() { + return "Coinapult " + coinapultCurrency.name; + } + + public static class Currency { + public static final Currency USD = new Currency("USD", BigDecimal.ONE); + public static final Currency EUR = new Currency("EUR", BigDecimal.ONE); + public static final Currency GBP = new Currency("GBP", BigDecimal.ONE); + public static final Currency BTC = new Currency("BTC", BigDecimal.ZERO); + public static final Map all = ImmutableMap.of( + USD.name, USD, + EUR.name, EUR, + GBP.name, GBP, + BTC.name, BTC + ); + + final public String name; + final public BigDecimal minimumConversationValue; + + private Currency(String name, BigDecimal minimumConversationValue) { + this.name = name; + this.minimumConversationValue = minimumConversationValue; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Currency currency = (Currency) o; + + if (!name.equals(currency.name)) { + return false; + } + return minimumConversationValue.equals(currency.minimumConversationValue); + + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + minimumConversationValue.hashCode(); + return result; + } + + public String getMinimumConversationString() { + return new DecimalFormat("#0.00##").format(minimumConversationValue) + " " + name; + } + } + + public Currency getCoinapultCurrency() { + return coinapultCurrency; + } + @Override - public boolean onlySyncWhenActive() { - return true; + public String getAccountDefaultCurrency() { + return getCoinapultCurrency().name; } -} \ No newline at end of file +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/coinapult/CoinapultManager.java b/public/mbw/src/main/java/com/mycelium/wallet/coinapult/CoinapultManager.java new file mode 100644 index 0000000000..457f6d9d0b --- /dev/null +++ b/public/mbw/src/main/java/com/mycelium/wallet/coinapult/CoinapultManager.java @@ -0,0 +1,376 @@ +package com.mycelium.wallet.coinapult; + +import android.os.AsyncTask; +import android.os.Handler; +import android.util.Log; +import com.coinapult.api.httpclient.*; +import com.google.common.base.*; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.mrd.bitlib.crypto.InMemoryPrivateKey; +import com.mrd.bitlib.model.NetworkParameters; +import com.mycelium.WapiLogger; +import com.mycelium.wallet.BitIdKeyDerivation; +import com.mycelium.wallet.ExchangeRateManager; +import com.mycelium.wallet.MbwEnvironment; +import com.mycelium.wallet.event.ExtraAccountsChanged; +import com.mycelium.wallet.persistence.MetadataStorage; +import com.mycelium.wapi.wallet.AccountProvider; +import com.mycelium.wapi.wallet.WalletAccount; +import com.squareup.otto.Bus; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.math.BigDecimal; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +public class CoinapultManager implements AccountProvider { + + public static final int CACHE_DURATION = 10; + private final MbwEnvironment env; + private final Bus eventBus; + private final Handler handler; + private final ExchangeRateManager exchangeRateManager; + private final CoinapultClient coinapultClient; + private final InMemoryPrivateKey accountKey; + private final MetadataStorage metadataStorage; + private final WapiLogger logger; + private final HashMap coinapultAccounts; + + public CoinapultManager(MbwEnvironment env, BitIdKeyDerivation bitIdKeyDerivation, final Bus eventBus, Handler handler, + MetadataStorage metadataStorage, ExchangeRateManager exchangeRateManager, WapiLogger logger) { + this.env = env; + this.eventBus = eventBus; + this.handler = handler; + this.metadataStorage = metadataStorage; + this.exchangeRateManager = exchangeRateManager; + this.logger = logger; + accountKey = bitIdKeyDerivation.deriveKey(0, "coinapult.com"); + coinapultClient = createClient(); + handler.post(new Runnable() { + @Override + public void run() { + eventBus.register(CoinapultManager.this); + } + }); + coinapultAccounts = new HashMap(); + loadAccounts(); + } + + private void saveEnabledCurrencies() { + String all = Joiner.on(",").join(Iterables.transform(coinapultAccounts.values(), new Function() { + @Nullable + @Override + public String apply(@Nullable CoinapultAccount input) { + Preconditions.checkNotNull(input); + return input.getCoinapultCurrency().name; + } + })); + metadataStorage.storeCoinapultCurrencies(all); + } + + private void loadAccounts() { + Iterable currencies = Splitter.on(",").split(metadataStorage.getCoinapultCurrencies()); + int countAccounts = 0; + for (String currency : currencies) { + if (!Strings.isNullOrEmpty(currency)) { + CoinapultAccount.Currency currencyDefinition = CoinapultAccount.Currency.all.get(currency); + createAccount(currencyDefinition); + countAccounts++; + } + } + + if (countAccounts == 0) { + // if there were no accounts active, try to fetch the balance anyhow and activate + // all accounts with a balance > 0 + // but do it in background, as this function gets called via the constructor, which + // gets called in the MbwManager constructor + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + scanForAccounts(); + return null; + } + }.execute(); + } + } + + private CoinapultAccount createAccount(CoinapultAccount.Currency currency) { + CoinapultAccount account = new CoinapultAccount( + CoinapultManager.this, metadataStorage, accountKey, + exchangeRateManager, handler, eventBus, logger, currency + ); + coinapultAccounts.put(account.getId(), account); + return account; + } + + @android.support.annotation.Nullable + private UUID enableCurrency(String currency) { + if (CoinapultAccount.Currency.all.containsKey(currency)) { + CoinapultAccount.Currency currencyAccount = CoinapultAccount.Currency.all.get(currency); + return enableCurrency(currencyAccount); + } else { + return null; + } + } + + public UUID enableCurrency(CoinapultAccount.Currency currency) { + // check if we already have it enabled + CoinapultAccount account = getAccountForCurrency(currency); + if (account != null) { + return account.getId(); + } + + // otherwise create a new account for it, and persist the setting + CoinapultAccount newAccount = createAccount(currency); + + // check if we already have a label for this account, otherwise set the default one + String label = metadataStorage.getLabelByAccount(newAccount.getId()); + if (Strings.isNullOrEmpty(label)) { + metadataStorage.storeAccountLabel(newAccount.getId(), newAccount.getDefaultLabel()); + } + + // get its initial balance + newAccount.synchronizeIntern(true, false); + + // keep it disabled while testing + //saveEnabledCurrencies(); + + // broadcast event, so that the UI shows the newly added account + handler.post(new Runnable() { + @Override + public void run() { + eventBus.post(new ExtraAccountsChanged()); + } + }); + + // and save it + saveEnabledCurrencies(); + + return newAccount.getId(); + } + + @android.support.annotation.Nullable + private CoinapultAccount getAccountForCurrency(CoinapultAccount.Currency currency) { + for (CoinapultAccount account : coinapultAccounts.values()) { + if (account.getCoinapultCurrency().equals(currency)) { + return account; + } + } + return null; + } + + @android.support.annotation.Nullable + private CoinapultAccount getAccountForCurrency(String currency) { + if (CoinapultAccount.Currency.all.containsKey(currency)) { + CoinapultAccount.Currency currencyAccount = CoinapultAccount.Currency.all.get(currency); + return getAccountForCurrency(currencyAccount); + } else { + return null; + } + } + + public boolean hasCurrencyEnabled(CoinapultAccount.Currency currency){ + return getAccountForCurrency(currency) != null; + } + + + @Override + public Map getAccounts() { + return ImmutableMap.copyOf(coinapultAccounts); + } + + @Override + public CoinapultAccount getAccount(UUID id) { + return coinapultAccounts.get(id); + } + + public boolean scanForAccounts() { + try { + Map balances = getBalances(); + + // check if we have accounts enabled for all currencies with balances, + // if not, enable the account, if we know how to handle it + for (AccountInfo.Balance balance : balances.values()) { + if (balance.amount.compareTo(BigDecimal.ZERO) > 0) { + CoinapultAccount accountForCurrency = getAccountForCurrency(balance.currency); + if (accountForCurrency == null) { + enableCurrency(balance.currency); + } + } + } + return true; + } catch (CoinapultClient.CoinapultBackendException e) { + logger.logError("error while scanning for accounts"); + return false; + } + } + + @Override + public boolean hasAccount(UUID uuid) { + return coinapultAccounts.containsKey(uuid); + } + + // no timeout needed for this cache - if we created or know that the account exists, + // it will likely be true from here on + private Boolean userAccountExistsCache = null; + + public boolean userAccountExists() throws CoinapultClient.CoinapultBackendException { + if (userAccountExistsCache == null) { + userAccountExistsCache = getClient().accountExists(); + } + return userAccountExistsCache; + } + + public void activateAccount(Optional mail) throws CoinapultClient.CoinapultBackendException { + try { + if (!userAccountExists()) { + Map options = new HashMap(); + if (mail.isPresent()) { + options.put("email", mail.get()); + } + getClient().createAccount(options); + getClient().activateAccount(true); + } + //initBalance(); + } catch (Exception e) { + Log.e("CoinapultManager", "Failed to addUsd account", e); + throw new CoinapultClient.CoinapultBackendException(e); + } + } + + public InMemoryPrivateKey getAccountKey() { + return accountKey; + } + + private CoinapultClient createClient() { + CoinapultConfig cc; + NetworkParameters network = env.getNetwork(); + if (network.equals(NetworkParameters.testNetwork)) { + cc = new CoinapultPlaygroundConfig(); + } else if (network.equals(NetworkParameters.productionNetwork)) { + cc = new CoinapultProdConfig(); + } else { + throw new IllegalStateException("unknown network: " + network); + } + + return new CoinapultClient(AndroidKeyConverter.convertKeyFormat(accountKey), new ECC_SC(), cc, logger); + } + + private Supplier> queryBalances = new Supplier>() { + @Override + public List get() { + try { + List balances = getClient().accountInfo().balances; + return balances; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (IOException e) { + logger.logError("error while getting balances"); + return null; + } + } + }; + + private final Supplier> balancesCache = + Suppliers.memoizeWithExpiration(queryBalances, CACHE_DURATION, TimeUnit.SECONDS); + + private final Supplier> historyCache = Suppliers.synchronizedSupplier( + Suppliers.memoizeWithExpiration(queryHistory(), CACHE_DURATION, TimeUnit.SECONDS) + ); + + public Map getBalances() throws CoinapultClient.CoinapultBackendException { + List balances; + balances = balancesCache.get(); + if (balances == null) { + throw new CoinapultClient.CoinapultBackendException("unable to get balances"); + } + + return Maps.uniqueIndex(balances, new Function() { + @Nullable + @Override + public String apply(AccountInfo.Balance input) { + return input.currency; + } + }); + } + + public boolean setMail(Optional mail) { + if (!mail.isPresent()) { + return false; + } + try { + EmailAddress.Json result = coinapultClient.setMail(mail.get()); + return result.email != null && result.email.equals(mail.get()); + } catch (IOException e) { + return false; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public boolean verifyMail(String link, String email) { + try { + EmailAddress.Json result = coinapultClient.verifyMail(link, email); + if (!result.verified) { + logger.logError("Coinapult email error: " + result.error); + } + return result.verified; + } catch (IOException e) { + return false; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public CoinapultClient getClient() { + return coinapultClient; + } + + + private Supplier> queryHistory() { + return Suppliers.synchronizedSupplier(new Supplier>() { + private List history; + + @Override + public synchronized List get() { + SearchMany.Json batch; + history = Lists.newArrayList(); + //get first page to get pageCount + try { + batch = getClient().history(1); + addToHistory(batch); + //get extra pages + for (int i = 2; batch.page < batch.pageCount; i++) { + batch = getClient().history(i); + addToHistory(batch); + } + + return history; + } catch (CoinapultClient.CoinapultBackendException e) { + logger.logError("error while getting history", e); + return null; + } + } + + private void addToHistory(SearchMany.Json batch) { + if (batch == null || batch.result == null) { + return; + } + history.addAll(batch.result); + } + }); + } + + + public List getHistory() throws CoinapultClient.CoinapultBackendException { + return historyCache.get(); + } +} \ No newline at end of file diff --git a/public/mbw/src/main/java/com/mycelium/wallet/CoinapultTransactionSummary.java b/public/mbw/src/main/java/com/mycelium/wallet/coinapult/CoinapultTransactionSummary.java similarity index 85% rename from public/mbw/src/main/java/com/mycelium/wallet/CoinapultTransactionSummary.java rename to public/mbw/src/main/java/com/mycelium/wallet/coinapult/CoinapultTransactionSummary.java index 2da2f1fbe2..b4b154c18d 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/CoinapultTransactionSummary.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/coinapult/CoinapultTransactionSummary.java @@ -1,4 +1,4 @@ -package com.mycelium.wallet; +package com.mycelium.wallet.coinapult; import com.coinapult.api.httpclient.Transaction; import com.google.common.base.Optional; @@ -7,13 +7,14 @@ import com.mrd.bitlib.util.HexUtils; import com.mrd.bitlib.util.Sha256Hash; import com.mycelium.wapi.model.TransactionSummary; +import com.mycelium.wapi.wallet.currency.CurrencyValue; public class CoinapultTransactionSummary extends TransactionSummary { public final Transaction.Json input; - public CoinapultTransactionSummary(Optional
address, long satoshis, Transaction.Json input) { - super(getTxid(input), satoshis, showTime(input), -1, confs(input), false, address); + public CoinapultTransactionSummary(Optional
address, CurrencyValue value, boolean isIncoming, Transaction.Json input) { + super(getTxid(input), value, isIncoming, showTime(input), -1, confs(input), false, address); this.input = input; } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/event/ExtraAccountsChanged.java b/public/mbw/src/main/java/com/mycelium/wallet/event/ExtraAccountsChanged.java new file mode 100644 index 0000000000..832ce0ea50 --- /dev/null +++ b/public/mbw/src/main/java/com/mycelium/wallet/event/ExtraAccountsChanged.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013, 2014 Megion Research and Development GmbH + * + * Licensed under the Microsoft Reference Source License (MS-RSL) + * + * This license governs use of the accompanying software. If you use the software, you accept this license. + * If you do not accept the license, do not use the software. + * + * 1. Definitions + * The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law. + * "You" means the licensee of the software. + * "Your company" means the company you worked for when you downloaded the software. + * "Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes + * of debugging your products, maintaining your products, or enhancing the interoperability of your products with the + * software, and specifically excludes the right to distribute the software outside of your company. + * "Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor + * under this license. + * + * 2. Grant of Rights + * (A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, + * worldwide, royalty-free copyright license to reproduce the software for reference use. + * (B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, + * worldwide, royalty-free patent license under licensed patents for reference use. + * + * 3. Limitations + * (A) No Trademark License- This license does not grant you any rights to use the Licensor’s name, logo, or trademarks. + * (B) If you begin patent litigation against the Licensor over patents that you think may apply to the software + * (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically. + * (C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, + * guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot + * change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, + * fitness for a particular purpose and non-infringement. + */ + +package com.mycelium.wallet.event; + +public class ExtraAccountsChanged { + +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/BcdCodedSepaData.java b/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/BcdCodedSepaData.java deleted file mode 100644 index 97164f0725..0000000000 --- a/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/BcdCodedSepaData.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2013, 2014 Megion Research and Development GmbH - * - * Licensed under the Microsoft Reference Source License (MS-RSL) - * - * This license governs use of the accompanying software. If you use the software, you accept this license. - * If you do not accept the license, do not use the software. - * - * 1. Definitions - * The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law. - * "You" means the licensee of the software. - * "Your company" means the company you worked for when you downloaded the software. - * "Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes - * of debugging your products, maintaining your products, or enhancing the interoperability of your products with the - * software, and specifically excludes the right to distribute the software outside of your company. - * "Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor - * under this license. - * - * 2. Grant of Rights - * (A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, - * worldwide, royalty-free copyright license to reproduce the software for reference use. - * (B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, - * worldwide, royalty-free patent license under licensed patents for reference use. - * - * 3. Limitations - * (A) No Trademark License- This license does not grant you any rights to use the Licensor’s name, logo, or trademarks. - * (B) If you begin patent litigation against the Licensor over patents that you think may apply to the software - * (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically. - * (C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties, - * guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot - * change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability, - * fitness for a particular purpose and non-infringement. - */ - -package com.mycelium.wallet.external.cashila.activity; - -import com.google.common.base.Strings; - -import java.io.Serializable; -import java.math.BigDecimal; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.text.ParseException; -import java.util.Locale; - -// based on https://www.stuzza.at/de/download/qr-code/126-qr-code-und-bcd-definitionen/file.html -public class BcdCodedSepaData implements Serializable { - - public final String bic; - public final String iban; - public final String recipient; - public final String reference; - public final String displayText; - public final BigDecimal amount; - - public static BcdCodedSepaData fromString(String bcdData) { - String[] lines = bcdData.split("\\r?\\n"); - - // first 7 lines are mandatory - if (lines.length < 7) { - return null; - } - - if (!lines[0].equals("BCD")) { - return null; - } - - if (!lines[1].equals("001")) { - return null; - } - - int encodingId; - try { - encodingId = Integer.parseInt(lines[2]); - } catch (NumberFormatException ex) { - return null; - } - - // todo - String encoding; - switch (encodingId) { - case 1: - encoding = "UTF-8"; - break; - case 2: - encoding = "ISO 8895-1"; - break; - case 3: - encoding = "ISO 8895-2"; - break; - case 4: - encoding = "ISO 8895-4"; - break; - case 5: - encoding = "ISO 8895-5"; - break; - case 6: - encoding = "ISO 8895-7"; - break; - case 7: - encoding = "ISO 8895-10"; - break; - case 8: - encoding = "ISO 8895-15"; - break; - default: - return null; - } - - if (!lines[3].equals("SCT")) { - return null; - } - - final String bic = lines[4]; - final String recipient = lines[5]; - final String iban = lines[6]; - - BigDecimal amount; - if (lines.length > 7) { - if (!lines[7].startsWith("EUR")) { - return null; - } - - // amount uses "." as decimal separator - eg: "EUR184.6" - try { - DecimalFormat format = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); - format.setParseBigDecimal(true); - amount = (BigDecimal) format.parseObject(lines[7].substring(3)); - } catch (ParseException e) { - return null; - } - - } else { - amount = BigDecimal.ZERO; - } - - String code; - if (lines.length > 8) { - code = lines[8]; - } else { - code = ""; - } - - // use either line9 (Reference) or line10 (Text) as Reference - String reference = ""; - if (lines.length > 9) { - reference = lines[9]; - } - - if (lines.length > 10) { - if (!Strings.isNullOrEmpty(lines[10].trim())) { - reference = lines[10]; - } - } - - String displayText; - if (lines.length > 11) { - displayText = lines[11]; - } else { - displayText = ""; - } - - - return new BcdCodedSepaData(bic, iban, recipient, reference, displayText, amount); - } - - private BcdCodedSepaData(String bic, String iban, String recipient, String reference, String displayText, BigDecimal amount) { - this.bic = bic; - this.iban = iban; - this.recipient = recipient; - this.reference = reference; - this.displayText = displayText; - this.amount = amount; - } -} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaNewFragment.java b/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaNewFragment.java index bef86cfe6b..ac63beefec 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaNewFragment.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaNewFragment.java @@ -84,7 +84,6 @@ public class CashilaNewFragment extends Fragment { private CashilaService cs; private MbwManager mbw; private Bus eventBus; - private BcdCodedSepaData bcd; /** @@ -114,18 +113,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, eventBus = mbw.getEventBus(); int selItem = 0; - if (savedInstanceState != null){ + if (savedInstanceState != null) { selItem = savedInstanceState.getInt("spRecipient", 0); } getRecentRecipientsList(selItem); - bcd = (BcdCodedSepaData) getActivity().getIntent().getSerializableExtra("bcd"); - if (bcd != null) { - initFromBcd(bcd); - } - - return rootView; } @@ -178,33 +171,6 @@ public void refresh() { getRecentRecipientsList(spRecipients.getSelectedItemPosition()); } - private CreateBillPay getBillPayFromBcdEntry(BcdCodedSepaData bcd) { - throw new UnsupportedOperationException(); - /* - if (bcd == null){ - return null; - } - - BigDecimal amount = getAmount(); - if (amount == null) { - return null; - } - - CreateBillPay newBillPay = new CreateBillPayNew( - bcd.recipient, - bcd.iban, - bcd.bic, - "", // bcd is missing address data - "", - "", - bcd.iban.substring(0,2), - amount , "EUR", - etReference.getText().toString(), - mbw.getSelectedAccount().getReceivingAddress()); - return newBillPay; - */ - } - private CreateBillPay getBillPayFromUserEntry() { BillPayRecentRecipient selectedItem = (BillPayRecentRecipient) spRecipients.getSelectedItem(); if (selectedItem == null) { @@ -217,7 +183,7 @@ private CreateBillPay getBillPayFromUserEntry() { } Optional
receivingAddress = mbw.getSelectedAccount().getReceivingAddress(); - if (!receivingAddress.isPresent()){ + if (!receivingAddress.isPresent()) { return null; } @@ -230,11 +196,7 @@ private CreateBillPay getBillPayFromUserEntry() { } private CreateBillPay getBillPay() { - if (bcd != null) { - return getBillPayFromBcdEntry(bcd); - } else { - return getBillPayFromUserEntry(); - } + return getBillPayFromUserEntry(); } private BigDecimal getAmount() { @@ -349,21 +311,6 @@ public void onPause() { super.onPause(); } - public void initFromBcd(BcdCodedSepaData bcd) { - etAmount.setText(bcd.amount.toString()); - etReference.setText(bcd.reference); - - ViewGroup parent = (ViewGroup) spRecipients.getParent(); - parent.removeAllViews(); - - View view = getActivity().getLayoutInflater().inflate(R.layout.ext_cashila_new_recipient, parent, true); - - ((TextView) view.findViewById(R.id.tvName)).setText(bcd.recipient); - ((TextView) view.findViewById(R.id.tvInfo)).setText(bcd.bic); - ((TextView) view.findViewById(R.id.tvIban)).setText(bcd.iban); - ((TextView) view.findViewById(R.id.tvDisplayText)).setText(bcd.displayText); - } - // Adapter for Recipient Spinner public static class RecipientArrayAdapter extends android.widget.ArrayAdapter { private final LayoutInflater inflater; diff --git a/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaPaymentsActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaPaymentsActivity.java index b3b95af441..49fe6ce191 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaPaymentsActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaPaymentsActivity.java @@ -82,13 +82,6 @@ public class CashilaPaymentsActivity extends ActionBarActivity implements Action private MbwManager mbw; private boolean warningsShown; - - public static Intent getIntent(Context context, BcdCodedSepaData bcdQrCode) { - Intent intent = new Intent(context, CashilaPaymentsActivity.class); - intent.putExtra("bcd", bcdQrCode); - return intent; - } - public static Intent getIntent(Context context) { return new Intent(context, CashilaPaymentsActivity.class); } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaPendingFragment.java b/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaPendingFragment.java index d4c63012cd..4ae196a1f5 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaPendingFragment.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/activity/CashilaPendingFragment.java @@ -315,8 +315,6 @@ public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { } else if (itemId == R.id.miPayNow) { payNow(billPay); return true; - } else { - // ... } return false; diff --git a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/ChangeLocationActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/ChangeLocationActivity.java index 40b4864f70..0edaf26d3a 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/ChangeLocationActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/ChangeLocationActivity.java @@ -42,7 +42,6 @@ import android.widget.Button; import android.widget.TextView; import android.widget.Toast; - import com.mycelium.lt.api.model.GpsLocation; import com.mycelium.lt.location.RemoteGeocodeException; import com.mycelium.wallet.GpsLocationFetcher; @@ -188,9 +187,8 @@ private void updateUi() { public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { if (requestCode == ENTER_LOCATION_REQUEST_CODE && resultCode == RESULT_OK) { _chosenAddress = (GpsLocationEx) intent.getSerializableExtra("location"); - } else { - // We didn't like what we got, bail } + // else We didn't like what we got, bail... } -} \ No newline at end of file +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/MyInfoFragment.java b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/MyInfoFragment.java index 8b5a7bef3f..75160aa9df 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/MyInfoFragment.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/MyInfoFragment.java @@ -118,7 +118,7 @@ private void updateUi() { viTraderInfo.setVisibility(View.VISIBLE); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.flTraderInfo, TraderInfoFragment.createInstance(info)); - ft.commit(); + ft.commitAllowingStateLoss(); } } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/SendRequestActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/SendRequestActivity.java index 6368845680..a32a9d5a86 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/SendRequestActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/SendRequestActivity.java @@ -139,12 +139,12 @@ protected void onSaveInstanceState(Bundle outState) { public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { if (requestCode == CREATE_TRADER_RESULT_CODE) { - if (resultCode == RESULT_OK) { - // great, we will try and create the trade on resume - } else { + if (resultCode != RESULT_OK) { // Creation failed, bail out finish(); } + // else: great, we will try and create the trade on resume + } else if (requestCode == SOLVE_CAPTCHA_RESULT_CODE) { if (resultCode == RESULT_OK) { // great, we will try and create the trade on resume @@ -224,4 +224,4 @@ public void onLtPublicTraderInfoFetched(com.mycelium.lt.api.model.PublicTraderIn }; -} \ No newline at end of file +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TradeActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TradeActivity.java index bca6a0ca6d..acf19b8032 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TradeActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TradeActivity.java @@ -58,18 +58,10 @@ import android.view.*; import android.view.View.OnClickListener; import android.view.inputmethod.EditorInfo; -import android.widget.AdapterView; +import android.widget.*; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; -import android.widget.Toast; import com.google.common.base.Preconditions; import com.megiontechnologies.Bitcoins; @@ -95,7 +87,6 @@ import com.mycelium.wapi.wallet.WalletAccount; public class TradeActivity extends Activity { - protected static final int CHANGE_PRICE_REQUEST_CODE = 1; protected static final int REFRESH_PRICE_REQUEST_CODE = 2; private static final int SIGN_TX_REQUEST_CODE = 3; @@ -553,6 +544,10 @@ private void updateUi(TradeSession tradeSession) { // Chat _chatAdapter.clear(); + // add a scary warning to the top of the chat + ChatEntry scaryWarning = new ChatEntry(0L, ChatEntry.TYPE_EVENT, ChatEntry.EVENT_SUBTYPE_CASH_ONLY_WARNING, ""); + _chatAdapter.add(scaryWarning); + // add all the persisted messages for (ChatEntry chatEntry : tradeSession.chatEntries) { _chatAdapter.add(chatEntry); } @@ -669,16 +664,7 @@ public View getView(int position, View convertView, ViewGroup parent) { } ChatEntry o = getItem(position); - // Date, format depending on whether it is the same day or earlier - Date date = new Date(o.time); - String dateString; - if (date.before(_midnight)) { - dateString = _dayFormat.format(date) + "\n" + _hourFormat.format(date); - } else { - dateString = _hourFormat.format(date); - } - TextView tvDate = (TextView) v.findViewById(R.id.tvDate); - tvDate.setText(dateString); + addDateString(v, o); // Message text and color TextView tvMessage = (TextView) v.findViewById(R.id.tvMessage); @@ -717,9 +703,52 @@ public View getView(int position, View convertView, ViewGroup parent) { tvMessage.setText(text); v.setBackgroundColor(color); + ImageView ivExtra = (ImageView) v.findViewById(R.id.ivExtra); + if(o.subtype == ChatEntry.EVENT_SUBTYPE_CASH_ONLY_WARNING) { + ivExtra.setImageResource(R.drawable.lt_local_only_warning); + ivExtra.setVisibility(View.VISIBLE); + ivExtra.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(TradeActivity.this); + builder.setMessage(getString(R.string.lt_cash_only_warning)); + builder.setPositiveButton(R.string.button_ok, null); + builder.show(); + } + }); + } else { + ivExtra.setVisibility(View.GONE); + ivExtra.setOnClickListener(null); + } + v.setTag(o); return v; } + + /** + * Date, format depending on whether it is the same day or earlier + * If the date is 0, the date is hidden. + * @param chatEntryRow the R.layout.lt_chat_entry_row + * @param chatEntry the ChatEntry + */ + private void addDateString(View chatEntryRow, ChatEntry chatEntry) { + TextView tvDate = (TextView) chatEntryRow.findViewById(R.id.tvDate); + long unixTime = chatEntry.time; + if(unixTime > 0) { + // we have a date + Date date = new Date(chatEntry.time); + String dateString; + if (date.before(_midnight)) { + dateString = _dayFormat.format(date) + "\n" + _hourFormat.format(date); + } else { + dateString = _hourFormat.format(date); + } + tvDate.setText(dateString); + tvDate.setVisibility(View.VISIBLE); + } else { + tvDate.setVisibility(View.GONE); + } + } } private void scrollChatToBottom() { @@ -732,7 +761,6 @@ public void run() { } class MyListener extends TradeSessionChangeMonitor.Listener { - protected MyListener(UUID tradeSessionId, long lastChange) { super(tradeSessionId, lastChange); } @@ -745,17 +773,15 @@ public void onTradeSessionChanged(TradeSession tradeSession) { // Tell other listeners that we have taken care of audibly notifying up // till this timestamp _ltManager.setLastNotificationSoundTimestamp(tradeSession.lastChange); - if (tradeSession.confidence != null && tradeSession.confidence > 0) { - // While displaying confidence we do not play a notification sound - } else { + if (tradeSession.confidence == null || tradeSession.confidence <= 0) { if (_dingOnUpdates && _updateSound != null && _ltManager.getPlaySoundOnTradeNotification()) { _updateSound.play(); } _dingOnUpdates = true; } + // else: While displaying confidence we do not play a notification sound updateUi(); } - } public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { @@ -789,7 +815,6 @@ public void onActivityResult(final int requestCode, final int resultCode, final } private LocalTraderEventSubscriber ltSubscriber = new LocalTraderEventSubscriber(new Handler()) { - @Override public void onLtError(int errorCode) { Toast.makeText(TradeActivity.this, R.string.lt_error_api_occurred, Toast.LENGTH_LONG).show(); @@ -807,7 +832,5 @@ public boolean onNoLtConnection() { public void onLtBtcReleased(Boolean success, ReleaseBtc request) { _mbwManager.getWalletManager(false).startSynchronization(); } - }; - } \ No newline at end of file diff --git a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TradeHistoryFragment.java b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TradeHistoryFragment.java index af77199cb5..aba2f32a58 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TradeHistoryFragment.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TradeHistoryFragment.java @@ -55,7 +55,6 @@ import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; -import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; @@ -67,13 +66,12 @@ import com.mycelium.wallet.lt.LocalTraderEventSubscriber; import com.mycelium.wallet.lt.LocalTraderManager; import com.mycelium.wallet.lt.api.GetFinalTradeSessions; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; public class TradeHistoryFragment extends Fragment { - - public static final String PARAMETER_IS_BUY = "isBuy"; - private MbwManager _mbwManager; private LocalTraderManager _ltManager; + // TODO: 11/26/15 why exactly should this warning be suppressed? Could we agree to always set a reason for ignoring warnings. I'd like to remove it. It does nothing as far as I can see. @SuppressWarnings("unused") private TradeSession _selectedTradeSession; private Wrapper _myAdapter; @@ -117,8 +115,7 @@ public void onPause() { super.onPause(); } - OnItemClickListener itemListClickListener = new OnItemClickListener() { - + private OnItemClickListener itemListClickListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView listView, final View view, int position, long id) { _selectedTradeSession = (TradeSession) view.getTag(); @@ -126,7 +123,7 @@ public void onItemClick(AdapterView listView, final View view, int position, } }; - private class TradeSessionsAdapter extends ArrayAdapter { + private final class TradeSessionsAdapter extends ArrayAdapter { private Context _context; private Date _midnight; private DateFormat _dayFormat; @@ -192,7 +189,7 @@ private class Wrapper extends EndlessAdapter { private List _toAdd; private GetFinalTradeSessions _request; - private Wrapper(Context ctxt, ArrayList list) { + private Wrapper(Context ctxt, List list) { super(new TradeSessionsAdapter(ctxt, list)); rotate = new RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotate.setDuration(600); @@ -211,7 +208,6 @@ public void onLtError(int errorCode) { } public void onLtFinalTradeSessionsFetched(List list, GetFinalTradeSessions request) { - synchronized (_fetched) { if (_request != request) { return; @@ -222,7 +218,6 @@ public void onLtFinalTradeSessionsFetched(List list, GetFinalTrade _fetched.notify(); } } - }; public void detach() { @@ -239,6 +234,9 @@ protected View getPendingView(ViewGroup parent) { } @Override + @SuppressFBWarnings( + justification = "looping happens anyway, but in a higher level", + value = "WA_NOT_IN_LOOP") protected boolean cacheInBackground() { if (!_ltManager.hasLocalTraderAccount()) { return false; diff --git a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TraderInfoAdapter.java b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TraderInfoAdapter.java index cb2908b27e..8b3ef7c9fa 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TraderInfoAdapter.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/TraderInfoAdapter.java @@ -83,7 +83,7 @@ public View getView(int position, View convertView, ViewGroup parent) { if (o.value != null) { // Set String value v = Preconditions.checkNotNull(vi.inflate(R.layout.lt_trader_info_row, null)); - ((TextView) v.findViewById(R.id.tvValue)).setText(o.value); + ((TextView) v.findViewById(R.id.tvDisplayValue)).setText(o.value); } else if (o.rating != null) { // Set Rating v = Preconditions.checkNotNull(vi.inflate(R.layout.lt_trader_info_rating_row, null)); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/ViewTraderInfoActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/ViewTraderInfoActivity.java index bf57c89e4f..aaf84edc49 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/ViewTraderInfoActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/ViewTraderInfoActivity.java @@ -66,7 +66,7 @@ public void onCreate(Bundle savedInstanceState) { protected void onResume() { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.replace(R.id.flTraderInfo, TraderInfoFragment.createInstance(_traderInfo)); - ft.commit(); + ft.commitAllowingStateLoss(); super.onResume(); } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/sell/CreateOrEditAdActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/sell/CreateOrEditAdActivity.java index 6f28b87c1e..091567d58a 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/sell/CreateOrEditAdActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/lt/activity/sell/CreateOrEditAdActivity.java @@ -555,9 +555,8 @@ public void onActivityResult(final int requestCode, final int resultCode, final } else if (requestCode == ENTER_MIN_AMOUNT_REQUEST_CODE && resultCode == RESULT_OK) { _minAmount = (Integer) intent.getSerializableExtra("amount"); enableUi(); - } else { - // We didn't like what we got, bail } + // else: We didn't like what we got, bail } private LocalTraderEventSubscriber ltSubscriber = new LocalTraderEventSubscriber(new Handler()) { @@ -594,4 +593,4 @@ public void onLtBtcSellPriceAssesed(BtcSellPrice btcSellPrice, AssessBtcSellPric }; -} \ No newline at end of file +} diff --git a/public/mbw/src/main/java/com/mycelium/wallet/persistence/MetadataStorage.java b/public/mbw/src/main/java/com/mycelium/wallet/persistence/MetadataStorage.java index 22eaee5924..81a98270c9 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/persistence/MetadataStorage.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/persistence/MetadataStorage.java @@ -84,9 +84,9 @@ public String getLabelByAccount(UUID account) { public Optional getAccountByLabel(String label) { Optional account = getFirstKeyForCategoryValue(ACCOUNTLABEL_CATEGORY, label); - if (account.isPresent()){ + if (account.isPresent()) { return Optional.of(UUID.fromString(account.get())); - }else{ + } else { return Optional.absent(); } } @@ -98,7 +98,7 @@ public void storeAccountLabel(UUID account, String label) { } // Removes all metadata (account label,...) from the database - public void deleteAccountMetadata(UUID account){ + public void deleteAccountMetadata(UUID account) { deleteAllByKey(account.toString()); } @@ -125,9 +125,9 @@ public void deleteAddressMetadata(Address address) { public Optional
getAddressByLabel(String label) { Optional address = getFirstKeyForCategoryValue(ADDRESSLABEL_CATEGORY, label); - if (address.isPresent()){ + if (address.isPresent()) { return Optional.of(Address.fromString(address.get())); - }else{ + } else { return Optional.absent(); } } @@ -138,16 +138,16 @@ public void storeAddressLabel(Address address, String label) { } } - public void setIgnoreLegacyWarning(UUID account, Boolean ignore){ + public void setIgnoreLegacyWarning(UUID account, Boolean ignore) { storeKeyCategoryValueEntry(IGNORE_LEGACY_WARNING_CATEGORY.of(account.toString()), ignore ? "1" : "0"); } - public Boolean getIgnoreLegacyWarning(UUID account){ - return "1".equals(getKeyCategoryValueEntry(IGNORE_LEGACY_WARNING_CATEGORY.of(account.toString()), "0")); + public Boolean getIgnoreLegacyWarning(UUID account) { + return "1".equals(getKeyCategoryValueEntry(IGNORE_LEGACY_WARNING_CATEGORY.of(account.toString()), "0")); } - public boolean firstMasterseedBackupFinished(){ - return getMasterSeedBackupState().equals(BackupState.VERIFIED); + public boolean firstMasterseedBackupFinished() { + return getMasterSeedBackupState().equals(BackupState.VERIFIED); } public BackupState getMasterSeedBackupState() { @@ -179,15 +179,15 @@ public void setPairedService(String serviceName, boolean paired) { storeKeyCategoryValueEntry(PAIRED_SERVICES_CATEGORY.of(serviceName), Boolean.toString(paired)); } - public void deleteMasterKeyBackupAgeMs(){ + public void deleteMasterKeyBackupAgeMs() { deleteByKeyCategory(SEED_BACKUPSTATE); } - public Optional getMasterKeyBackupAgeMs(){ + public Optional getMasterKeyBackupAgeMs() { Optional lastBackup = getKeyCategoryValueEntry(SEED_BACKUPSTATE); if (lastBackup.isPresent()) { return Optional.of(Calendar.getInstance().getTimeInMillis() - Long.valueOf(lastBackup.get())); - }else{ + } else { return Optional.absent(); } } @@ -196,8 +196,8 @@ public void setMasterSeedBackupState(BackupState state) { storeKeyCategoryValueEntry(SEED_BACKUPSTATE, state.toString()); // if this is the first verified backup, remember the date - if (state == BackupState.VERIFIED && getMasterSeedBackupState() != BackupState.VERIFIED){ - storeKeyCategoryValueEntry(SEED_BACKUPSTATE, String.valueOf(Calendar.getInstance().getTimeInMillis()) ); + if (state == BackupState.VERIFIED && getMasterSeedBackupState() != BackupState.VERIFIED) { + storeKeyCategoryValueEntry(SEED_BACKUPSTATE, String.valueOf(Calendar.getInstance().getTimeInMillis())); } } @@ -209,28 +209,28 @@ public void clearResetPinStartBlockheight() { deleteByKeyCategory(PIN_RESET_BLOCKHEIGHT); } - public Optional getResetPinStartBlockHeight(){ + public Optional getResetPinStartBlockHeight() { Optional resetIn = getKeyCategoryValueEntry(PIN_RESET_BLOCKHEIGHT); - if (resetIn.isPresent()){ + if (resetIn.isPresent()) { return Optional.of(Integer.valueOf(resetIn.get())); - }else{ + } else { return Optional.absent(); } } - public void setLastPinSetBlockheight(int blockChainHeight){ + public void setLastPinSetBlockheight(int blockChainHeight) { storeKeyCategoryValueEntry(PIN_BLOCKHEIGHT, String.valueOf(blockChainHeight)); } - public void clearLastPinSetBlockheight(){ + public void clearLastPinSetBlockheight() { deleteByKeyCategory(PIN_BLOCKHEIGHT); } - public Optional getLastPinSetBlockheight(){ + public Optional getLastPinSetBlockheight() { Optional lastSet = getKeyCategoryValueEntry(PIN_BLOCKHEIGHT); - if (lastSet.isPresent()){ + if (lastSet.isPresent()) { return Optional.of(Integer.valueOf(lastSet.get())); - }else{ + } else { return Optional.absent(); } } @@ -243,13 +243,21 @@ public void storeArchived(UUID uuid, boolean archived) { storeKeyCategoryValueEntry(ARCHIVED.of(uuid.toString()), archived ? "1" : "0"); } - public void storeCoinapultAddress(Address address) { - storeKeyCategoryValueEntry(COINAPULT.of("last"),address.toString()); + public void storeCoinapultCurrencies(String currencies) { + storeKeyCategoryValueEntry(COINAPULT.of("currencies"), currencies); } - public Optional
getCoinapultAddress() { - Optional last = getKeyCategoryValueEntry(COINAPULT.of("last")); - if (!last.isPresent()){ + public String getCoinapultCurrencies() { + return getKeyCategoryValueEntry(COINAPULT.of("currencies"), ""); + } + + public void storeCoinapultAddress(Address address, String forCurrency) { + storeKeyCategoryValueEntry(COINAPULT.of("last" + forCurrency), address.toString()); + } + + public Optional
getCoinapultAddress(String forCurrency) { + Optional last = getKeyCategoryValueEntry(COINAPULT.of("last" + forCurrency)); + if (!last.isPresent()) { return Optional.absent(); } return Optional.of(Address.fromString(last.get())); @@ -272,21 +280,23 @@ public enum BackupState { UNKNOWN(0), VERIFIED(1), IGNORED(2); private final int _index; + private BackupState(int index) { _index = index; } - public static BackupState fromString(String state){ + public static BackupState fromString(String state) { return fromInt(Integer.parseInt(state)); } - public String toString(){ + public String toString() { return Integer.toString(_index); } public int toInt() { return _index; } + public static BackupState fromInt(int integer) { switch (integer) { case 0: diff --git a/public/mbw/src/main/java/com/mycelium/wallet/persistence/PersistedOutput.java b/public/mbw/src/main/java/com/mycelium/wallet/persistence/PersistedOutput.java index 1a094668c1..2e62f06ca8 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/persistence/PersistedOutput.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/persistence/PersistedOutput.java @@ -54,7 +54,7 @@ public PersistedOutput(OutPoint outPoint, Address address, int height, long valu this.address = address; this.height = height; this.value = value; - this.script = script; + this.script = script.clone(); this.isCoinBase = isCoinBase; } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/persistence/TradeSessionDb.java b/public/mbw/src/main/java/com/mycelium/wallet/persistence/TradeSessionDb.java index dd48764521..85ec22ff12 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/persistence/TradeSessionDb.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/persistence/TradeSessionDb.java @@ -328,6 +328,7 @@ private static TradeSession sessionFromBlob(byte[] blob) { ObjectInputStream in = new ObjectInputStream(bin); TradeSession session; try { + // readObject might be dangerous if blob comes from an untrusted source session = (TradeSession) in.readObject(); } catch (ClassNotFoundException e) { return null; diff --git a/public/mbw/src/main/java/crl/android/pdfwriter/Page.java b/public/mbw/src/main/java/crl/android/pdfwriter/Page.java index d8def4eb50..b7cafc0503 100644 --- a/public/mbw/src/main/java/crl/android/pdfwriter/Page.java +++ b/public/mbw/src/main/java/crl/android/pdfwriter/Page.java @@ -32,28 +32,28 @@ public IndirectObject getIndirectObject() { } private String getFontReferences() { - String result = ""; + StringBuilder builder = new StringBuilder(); if (!mPageFonts.isEmpty()) { - result = " /Font <<\n"; + builder.append(" /Font <<\n"); int x = 0; for (IndirectObject lFont : mPageFonts) { - result += " /F" + Integer.toString(++x) + " " + lFont.getIndirectReference() + "\n"; + builder.append(" /F" + Integer.toString(++x) + " " + lFont.getIndirectReference() + "\n"); } - result += " >>\n"; + builder.append(" >>\n"); } - return result; + return builder.toString(); } private String getXObjectReferences() { - String result = ""; + StringBuilder builder = new StringBuilder(""); if (!mXObjects.isEmpty()) { - result = " /XObject <<\n"; + builder.append(" /XObject <<\n"); for (XObjectImage xObj : mXObjects) { - result += " " + xObj.asXObjectReference() + "\n"; + builder.append(" ").append(xObj.asXObjectReference()).append("\n"); } - result += " >>\n"; + builder.append(" >>\n"); } - return result; + return builder.toString(); } public void render(String pagesIndirectReference) { diff --git a/public/mbw/src/main/res/drawable-hdpi/lt_local_only_warning.png b/public/mbw/src/main/res/drawable-hdpi/lt_local_only_warning.png new file mode 100644 index 0000000000..d57a9d66c0 Binary files /dev/null and b/public/mbw/src/main/res/drawable-hdpi/lt_local_only_warning.png differ diff --git a/public/mbw/src/main/res/drawable-ldpi/lt_local_only_warning.png b/public/mbw/src/main/res/drawable-ldpi/lt_local_only_warning.png new file mode 100644 index 0000000000..f2c807a17a Binary files /dev/null and b/public/mbw/src/main/res/drawable-ldpi/lt_local_only_warning.png differ diff --git a/public/mbw/src/main/res/drawable-mdpi/lt_local_only_warning.png b/public/mbw/src/main/res/drawable-mdpi/lt_local_only_warning.png new file mode 100644 index 0000000000..8062ffd524 Binary files /dev/null and b/public/mbw/src/main/res/drawable-mdpi/lt_local_only_warning.png differ diff --git a/public/mbw/src/main/res/drawable-xhdpi/lt_local_only_warning.png b/public/mbw/src/main/res/drawable-xhdpi/lt_local_only_warning.png new file mode 100644 index 0000000000..b3584b9612 Binary files /dev/null and b/public/mbw/src/main/res/drawable-xhdpi/lt_local_only_warning.png differ diff --git a/public/mbw/src/main/res/drawable-xxhdpi/lt_local_only_warning.png b/public/mbw/src/main/res/drawable-xxhdpi/lt_local_only_warning.png new file mode 100644 index 0000000000..6ccbf39345 Binary files /dev/null and b/public/mbw/src/main/res/drawable-xxhdpi/lt_local_only_warning.png differ diff --git a/public/mbw/src/main/res/layout/add_account_activity.xml b/public/mbw/src/main/res/layout/add_account_activity.xml index acdd225301..ae54717017 100644 --- a/public/mbw/src/main/res/layout/add_account_activity.xml +++ b/public/mbw/src/main/res/layout/add_account_activity.xml @@ -65,7 +65,7 @@ android:gravity="center" android:paddingLeft="10dp" android:paddingRight="10dp" - android:text="@string/AddCoinapultNotes" + android:text="@string/coinapult_account_notes" android:id="@+id/textView" android:layout_marginBottom="20dp" /> @@ -77,7 +77,7 @@ android:gravity="center" android:paddingLeft="30dp" android:paddingRight="30dp" - android:text="Add Coinapult USD Account" + android:text="@string/coinapult_add_locks_account" android:layout_marginBottom="40dp" /> diff --git a/public/mbw/src/main/res/layout/add_coinapult_account_activity.xml b/public/mbw/src/main/res/layout/add_coinapult_account_activity.xml new file mode 100644 index 0000000000..9dfa980313 --- /dev/null +++ b/public/mbw/src/main/res/layout/add_coinapult_account_activity.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + +