From fd156f09c16f9b17dec82f189b12a1ace9a2eb6b Mon Sep 17 00:00:00 2001 From: Daniel Weigl Date: Wed, 21 Oct 2015 21:54:06 +0200 Subject: [PATCH] 2.5.5 --- README.md | 12 +- .../java/com/mrd/bitlib/crypto/PublicKey.java | 9 +- .../com/mrd/bitlib/crypto/Signatures.java | 26 + .../mrd/bitlib/model/hdpath/HdKeyPath.java | 11 +- public/mbw/build.gradle | 4 +- .../com/mycelium/wallet/CoinapultManager.java | 46 +- .../java/com/mycelium/wallet/MbwManager.java | 17 +- .../mycelium/wallet/StringHandleConfig.java | 2 +- .../main/java/com/mycelium/wallet/Utils.java | 12 +- .../wallet/activity/AddAccountActivity.java | 55 +- .../activity/EnterWordListActivity.java | 33 +- .../activity/HdAccountSelectorActivity.java | 14 +- .../activity/InstantMasterseedActivity.java | 23 +- .../wallet/activity/main/BalanceFragment.java | 1 + .../main/TransactionArrayAdapter.java | 240 ++++---- .../main/TransactionHistoryFragment.java | 5 +- .../activity/modern/AccountsFragment.java | 3 + .../wallet/activity/modern/ModernMain.java | 3 +- .../wallet/activity/pop/PopActivity.java | 511 +++++++++--------- .../pop/PopSelectTransactionActivity.java | 307 +++++++---- .../wallet/activity/pop/PopUtils.java | 30 +- .../send/ColdStorageSummaryActivity.java | 8 - .../activity/send/InstantWalletActivity.java | 32 +- .../send/SendInitializationActivity.java | 5 + .../activity/settings/SettingsActivity.java | 125 +++-- .../util/AbstractAccountScanManager.java | 90 ++- .../activity/util/MasterseedScanManager.java | 44 +- .../cashila/activity/CashilaNewFragment.java | 19 +- .../external/cashila/api/CashilaService.java | 3 +- .../mycelium/wallet/ledger/LedgerManager.java | 17 +- .../wallet/ledger/MyceliumKeyRecovery.java | 25 +- .../activity/LedgerAccountImportActivity.java | 17 +- .../wallet/persistence/MetadataStorage.java | 1 + .../mycelium/wallet/trezor/TrezorManager.java | 17 +- .../activity/InstantTrezorActivity.java | 9 +- .../activity/TrezorAccountImportActivity.java | 15 +- .../holo_dark_ic_action_new_usd_account.png | Bin 0 -> 1023 bytes .../backup_verification_warning_dialog.xml | 3 +- .../res/layout/balance_master_fragment.xml | 11 +- .../res/layout/instant_wallet_activity.xml | 41 +- .../mbw/src/main/res/layout/pop_activity.xml | 159 ++++-- .../pop_select_transaction_activity.xml | 9 +- .../res/menu/record_options_menu_global.xml | 25 +- public/mbw/src/main/res/values-cs/strings.xml | 2 - public/mbw/src/main/res/values-da/strings.xml | 2 - public/mbw/src/main/res/values-de/strings.xml | 10 +- public/mbw/src/main/res/values-el/strings.xml | 99 +++- public/mbw/src/main/res/values-es/strings.xml | 2 - public/mbw/src/main/res/values-fr/strings.xml | 8 +- public/mbw/src/main/res/values-it/strings.xml | 2 - public/mbw/src/main/res/values-iw/strings.xml | 2 - public/mbw/src/main/res/values-ja/strings.xml | 5 +- public/mbw/src/main/res/values-ko/strings.xml | 1 - public/mbw/src/main/res/values-nl/strings.xml | 2 - public/mbw/src/main/res/values-pl/strings.xml | 2 - public/mbw/src/main/res/values-pt/strings.xml | 1 - public/mbw/src/main/res/values-ru/strings.xml | 2 - public/mbw/src/main/res/values-sk/strings.xml | 2 - public/mbw/src/main/res/values-sl/strings.xml | 3 - public/mbw/src/main/res/values-sq/strings.xml | 47 +- public/mbw/src/main/res/values-sv/strings.xml | 2 - public/mbw/src/main/res/values-vi/strings.xml | 2 - .../src/main/res/values-zh-rTW/strings.xml | 2 - public/mbw/src/main/res/values-zh/strings.xml | 2 - public/mbw/src/main/res/values/strings.xml | 19 +- .../src/main/res/values/strings_nolocale.xml | 2 + .../mbw/src/main/res/xml/changelog_master.xml | 8 + .../mycelium/wapi/wallet/AbstractAccount.java | 6 +- .../wapi/wallet/AccountScanManager.java | 12 +- .../wallet/currency/CurrencyBasedBalance.java | 1 - .../wallet/single/SingleAddressAccount.java | 11 +- 71 files changed, 1409 insertions(+), 889 deletions(-) create mode 100644 public/mbw/src/main/res/drawable-hdpi/holo_dark_ic_action_new_usd_account.png diff --git a/README.md b/README.md index d1be855957..dff11d31d5 100644 --- a/README.md +++ b/README.md @@ -107,9 +107,15 @@ Authors Credits ======= -Thanks to Jethro for tirelessly testing the app during beta development. +Thanks to all collaborators who provided us with code or helped us with integrations! +Just to name a few: + - Nicolas Bacca from Ledger + - Sipa, Marek and others from Trezor + - Jani and Aleš from Cashila + - Kalle Rosenbaum, Bip120/121 + - (if you think you should be mentioned here, just notify us) +Thanks to Jethro for tirelessly testing the app during beta development. Thanks to our numerous volunteer translators who provide high-quality translations in many languages. Your name should be listed here, please contact me so I know you want to be included. - Thanks to Johannes Zweng for his testing and providing pull requests for fixes. -Thanks to all beta testers to provide early feedback. +Thanks to all beta testers to provide early feedback. \ No newline at end of file diff --git a/public/bitlib/src/main/java/com/mrd/bitlib/crypto/PublicKey.java b/public/bitlib/src/main/java/com/mrd/bitlib/crypto/PublicKey.java index c7475466b8..088c1d4512 100644 --- a/public/bitlib/src/main/java/com/mrd/bitlib/crypto/PublicKey.java +++ b/public/bitlib/src/main/java/com/mrd/bitlib/crypto/PublicKey.java @@ -77,7 +77,7 @@ public String toString() { return HexUtils.toHex(_pubKeyBytes); } - public boolean verifyStandardBitcoinSignature(Sha256Hash data, byte[] signature) { + public boolean verifyStandardBitcoinSignature(Sha256Hash data, byte[] signature, boolean forceLowS) { // Decode parameters r and s ByteReader reader = new ByteReader(signature); Signature params = Signatures.decodeSignatureParameters(reader); @@ -88,7 +88,12 @@ public boolean verifyStandardBitcoinSignature(Sha256Hash data, byte[] signature) if (reader.available() != 1) { return false; } - return Signatures.verifySignature(data.getBytes(), params, getQ()); + if (forceLowS) { + return Signatures.verifySignatureLowS(data.getBytes(), params, getQ()); + } else { + return Signatures.verifySignature(data.getBytes(), params, getQ()); + } + } // same as verifyStandardBitcoinSignature, but dont enforce the hash-type check diff --git a/public/bitlib/src/main/java/com/mrd/bitlib/crypto/Signatures.java b/public/bitlib/src/main/java/com/mrd/bitlib/crypto/Signatures.java index 368f669df2..5a5359a7e3 100644 --- a/public/bitlib/src/main/java/com/mrd/bitlib/crypto/Signatures.java +++ b/public/bitlib/src/main/java/com/mrd/bitlib/crypto/Signatures.java @@ -109,6 +109,28 @@ private static byte[] makePositive(byte[] bytes) { return bytes; } + // checks the signature and enforces a Low-S Value - to counter the bitcoin + // transaction malleability problem, according to Bip62 + // https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#New_rules, pt5 + static boolean verifySignatureLowS(byte[] message, Signature signature, Point Q) { + BigInteger n = Parameters.n; + BigInteger e = calculateE(n, message); + BigInteger r = signature.r; + BigInteger s = signature.s; + + // r in the range [1,n-1] + if (r.compareTo(BigInteger.ONE) < 0 || r.compareTo(n) >= 0) { + return false; + } + + // s in the range [1,n/2] + if (s.compareTo(BigInteger.ONE) < 0 || s.compareTo(Parameters.MAX_SIG_S) > 0) { + return false; + } + + return checkSignature(Q, n, e, r, s); + } + static boolean verifySignature(byte[] message, Signature signature, Point Q) { BigInteger n = Parameters.n; BigInteger e = calculateE(n, message); @@ -125,6 +147,10 @@ static boolean verifySignature(byte[] message, Signature signature, Point Q) { return false; } + return checkSignature(Q, n, e, r, s); + } + + private static boolean checkSignature(Point Q, BigInteger n, BigInteger e, BigInteger r, BigInteger s) { BigInteger c = s.modInverse(n); BigInteger u1 = e.multiply(c).mod(n); diff --git a/public/bitlib/src/main/java/com/mrd/bitlib/model/hdpath/HdKeyPath.java b/public/bitlib/src/main/java/com/mrd/bitlib/model/hdpath/HdKeyPath.java index bb48028e78..6bf08074fe 100644 --- a/public/bitlib/src/main/java/com/mrd/bitlib/model/hdpath/HdKeyPath.java +++ b/public/bitlib/src/main/java/com/mrd/bitlib/model/hdpath/HdKeyPath.java @@ -21,6 +21,7 @@ public class HdKeyPath implements Serializable { public static final HdKeyPath ROOT = new HdKeyPath(); public static final Bip44Purpose BIP44 = ROOT.getBip44Purpose(); + public static final HdKeyPath BIP32_ROOT = ROOT.getHardenedChild(0); public static final Bip44CoinType BIP44_TESTNET = BIP44.getCoinTypeBitcoinTestnet(); public static final Bip44CoinType BIP44_PRODNET = BIP44.getCoinTypeBitcoin(); @@ -42,7 +43,7 @@ private HdKeyPath getChild(Iterator path){ if (!path.hasNext()) return this; String ak = path.next(); - int index = Integer.valueOf(ak.replace(HARDENED_MARKER,"")); + int index = Integer.valueOf(ak.replace(HARDENED_MARKER, "")); if (ak.endsWith(HARDENED_MARKER)){ return this.getHardenedChild(index).getChild(path); @@ -111,6 +112,7 @@ public Bip44Purpose getBip44Purpose(){ return new Bip44Purpose(this, UnsignedInteger.valueOf(44), true); } + private int getValue(){ return index.intValue() | (isHardened() ? 1<<31 : 0); } @@ -154,8 +156,13 @@ public int hashCode() { return result; } - public int getLastIndex() { + // returns the raw index (including the hardened bit, if set) of the last path element + public int getLastRawIndex() { return getValue(); } + // returns the index of the last path element + public int getLastIndex() { + return index.intValue(); + } } diff --git a/public/mbw/build.gradle b/public/mbw/build.gradle index 598635dd57..64ca10e8fc 100644 --- a/public/mbw/build.gradle +++ b/public/mbw/build.gradle @@ -64,8 +64,8 @@ android { buildToolsVersion androidSdkBuildVersion defaultConfig { - versionCode 25400 - versionName '2.5.4' + versionCode 25502 + versionName '2.5.5' multiDexEnabled true } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/CoinapultManager.java b/public/mbw/src/main/java/com/mycelium/wallet/CoinapultManager.java index 603e66074f..0fe2f11d2c 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/CoinapultManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/CoinapultManager.java @@ -1,5 +1,6 @@ package com.mycelium.wallet; +import android.os.AsyncTask; import android.os.Handler; import android.util.Log; import com.coinapult.api.httpclient.*; @@ -38,7 +39,7 @@ import java.security.NoSuchAlgorithmException; import java.util.*; -import static com.coinapult.api.httpclient.CoinapultClient.*; +import static com.coinapult.api.httpclient.CoinapultClient.CoinapultBackendException; public class CoinapultManager implements WalletAccount { @@ -126,7 +127,7 @@ private void initBalance() { try { String address; Optional
lastCoinapultAddress = metadataStorage.getCoinapultAddress(); - if (!lastCoinapultAddress.isPresent()){ + if (!lastCoinapultAddress.isPresent()) { //requesting fresh address address = coinapultClient.getBitcoinAddress().address; } else { @@ -136,7 +137,7 @@ private void initBalance() { criteria.put("to", lastCoinapultAddress.get().toString()); com.coinapult.api.httpclient.Transaction.Json search = coinapultClient.search(criteria); boolean alreadyUsed = search.containsKey("transaction_id"); - if (alreadyUsed){ + if (alreadyUsed) { // get a new one address = coinapultClient.getBitcoinAddress().address; } else { @@ -336,7 +337,6 @@ private boolean isSending(com.coinapult.api.httpclient.Transaction.Json input) { if (isInvoice) { return false; } - // other unexpected tx type - but ignore it return false; } @@ -383,7 +383,26 @@ public void activateAccount() { @Override public void dropCachedData() { + // try to recreate the coinapult account, this fails if it already exists + // otherwise it will fix a locked state where the wallet thinks it has an account + // but it does not + new AddCoinapultAsyncTask().execute(); + } + + private class AddCoinapultAsyncTask extends AsyncTask { + @Override + protected UUID doInBackground(Void... params) { + try { + addUSD(Optional.absent()); + } catch (CoinapultClient.CoinapultBackendException e) { + return null; + } + return getId(); + } + @Override + protected void onPostExecute(UUID account) { + } } @Override @@ -410,7 +429,7 @@ public boolean broadcastOutgoingTransactions() { public boolean isMine(Address address) { // there might be more, but currently we only know about this one... Optional
receivingAddress = getReceivingAddress(); - return receivingAddress.isPresent() && receivingAddress.get().equals(address); + return receivingAddress.isPresent() && receivingAddress.get().equals(address); } @Override @@ -591,14 +610,6 @@ public PreparedCoinapult prepareCoinapultTx(Receiver receiver) throws StandardTr } } - 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 broadcast(PreparedCoinapult preparedCoinapult) { try { final com.coinapult.api.httpclient.Transaction.Json send; @@ -635,6 +646,13 @@ public CurrencyBasedBalance getCurrencyBasedBalance() { 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; @@ -652,7 +670,7 @@ public boolean setMail(Optional mail) { public boolean verifyMail(String link, String email) { try { EmailAddress.Json result = coinapultClient.verifyMail(link, email); - if (!result.verified){ + if (!result.verified) { logger.logError("Coinapult email error: " + result.error); } return result.verified; 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 5958dbba06..35345bc826 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/MbwManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/MbwManager.java @@ -260,7 +260,7 @@ private MbwManager(Context evilContext) { _walletManager.addObserver(_eventTranslator); _exchangeRateManager.subscribe(_eventTranslator); _coinapultManager = createCoinapultManager(); - _walletManager.setExtraAccount(_coinapultManager); + setExtraAccount(_coinapultManager); migrateOldKeys(); createTempWalletManager(); @@ -271,8 +271,13 @@ private MbwManager(Context evilContext) { _environment.getBlockExplorerList().get(0).getIdentifier())); } + public void setExtraAccount(Optional coinapultManager) { + _walletManager.setExtraAccount(coinapultManager); + _hasUsdAccount = null; // invalidate cache + } + private Optional createCoinapultManager() { - if (_walletManager.hasBip32MasterSeed() && _storage.isPairedService("coinapult")) { + if (_walletManager.hasBip32MasterSeed() && _storage.isPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT)) { BitidKeyDerivation derivation = new BitidKeyDerivation() { @Override public InMemoryPrivateKey deriveKey(int accountIndex, String site) { @@ -1211,4 +1216,12 @@ public Cache getBackgroundObjectsCache() { public void switchServer() { _environment.getWapiEndpoints().switchToNextEndpoint(); } + + Boolean _hasUsdAccount = null; + public boolean hasUsdAccount() { + if (_hasUsdAccount == null){ + _hasUsdAccount = getMetadataStorage().isPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT); + } + return _hasUsdAccount; + } } 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 d1059f04d2..b8e0c633fc 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/StringHandleConfig.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/StringHandleConfig.java @@ -855,7 +855,7 @@ public boolean handle(StringHandlerActivity handlerActivity, String content) { if (!Bip39.isValidWordList(words)) { return false; } - InstantMasterseedActivity.callMe(handlerActivity, words); + InstantMasterseedActivity.callMe(handlerActivity, words, null); handlerActivity.finishOk(); return true; } 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 dad98b6877..2a5553482b 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/Utils.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/Utils.java @@ -48,6 +48,7 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Build; +import android.support.annotation.StringRes; import android.text.ClipboardManager; import android.text.format.DateFormat; import android.view.LayoutInflater; @@ -78,8 +79,8 @@ import com.mrd.bitlib.model.Address; import com.mrd.bitlib.model.NetworkParameters; import com.mrd.bitlib.util.CoinUtil; -import com.mycelium.wallet.activity.BackupWordListActivity; import com.mycelium.wallet.activity.AdditionalBackupWarningActivity; +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; @@ -99,7 +100,12 @@ import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; -import java.util.*; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Locale; @SuppressWarnings("deprecation") public class Utils { @@ -263,7 +269,7 @@ 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, 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); 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 4d5a0a9c78..6adcc0ab68 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 @@ -72,7 +72,12 @@ 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); } @@ -96,7 +101,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("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 { @@ -104,6 +109,9 @@ public void onCreate(Bundle savedInstanceState) { } _progress = new ProgressDialog(this); + if (getIntent().getBooleanExtra("coinapult", false)){ + createCoinapultAccountProtected(); + } } View.OnClickListener advancedClickListener = new View.OnClickListener() { @@ -134,23 +142,26 @@ public void run() { View.OnClickListener createCoinapultAccount = new View.OnClickListener() { @Override public void onClick(View view) { - - _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(); - } - }); - } - }); - + createCoinapultAccountProtected(); } }; + 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); // at this point, we have to have a master seed, since we created one on startup @@ -222,15 +233,20 @@ 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("coinapult", true); + _mbwManager.getMetadataStorage().setPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT, true); coinapultManager = _mbwManager.getCoinapultManager(); try { coinapultManager.addUSD(mail); @@ -241,15 +257,14 @@ protected UUID doInBackground(Void... params) { } catch (CoinapultClient.CoinapultBackendException e) { return null; } - // at this point, we have to have a master seed, since we created one on startup return coinapultManager.getId(); } @Override protected void onPostExecute(UUID account) { - _progress.dismiss(); + progressDialog.dismiss(); if (account != null) { - _mbwManager.getWalletManager(false).setExtraAccount(Optional.of(coinapultManager)); + _mbwManager.setExtraAccount(Optional.of(coinapultManager)); bus.post(new AccountChanged(account)); Intent result = new Intent(); result.putExtra(RESULT_KEY, coinapultManager.getId()); @@ -258,7 +273,7 @@ protected void onPostExecute(UUID account) { } 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("coinapult", false); + _mbwManager.getMetadataStorage().setPairedService(MetadataStorage.PAIRED_SERVICE_COINAPULT, false); } } } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/EnterWordListActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/EnterWordListActivity.java index bf3b872695..f04caece37 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/EnterWordListActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/EnterWordListActivity.java @@ -60,10 +60,23 @@ import java.util.UUID; public class EnterWordListActivity extends ActionBarActivity implements WordAutoCompleterFragment.WordAutoCompleterListener { + private static final String ONLY_SEED = "onlySeed"; + public static final String MASTERSEED = "masterseed"; + public static final String PASSWORD = "password"; + + private boolean _seedOnly; public static void callMe(Activity activity, int requestCode) { Intent intent = new Intent(activity, EnterWordListActivity.class); + intent.putExtra(ONLY_SEED, false); + activity.startActivityForResult(intent, requestCode); + } + + // only return the masterseed as string, dont try to create a new account based on it + public static void callMe(Activity activity, int requestCode, boolean returnMasterseedOnly) { + Intent intent = new Intent(activity, EnterWordListActivity.class); + intent.putExtra(ONLY_SEED, returnMasterseedOnly); activity.startActivityForResult(intent, requestCode); } @@ -93,7 +106,7 @@ public void onCreate(Bundle savedInstanceState) { UsKeyboardFragment keyboard = (UsKeyboardFragment) getSupportFragmentManager().findFragmentById(R.id.usKeyboard); keyboard.setListener(_wordAutoCompleter); currentWordNum = 1; - + _seedOnly = getIntent().getBooleanExtra(ONLY_SEED, false); if (savedInstanceState == null) { //only ask if we are not recreating the activity, because of rotation for example @@ -221,11 +234,19 @@ private boolean checksumMatches() { } private void calculateSeed(String password) { - _progress.setCancelable(false); - _progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); - _progress.setMessage(getString(R.string.importing_master_seed_from_wordlist)); - _progress.show(); - new MasterSeedFromWordsAsyncTask(_mbwManager.getEventBus(), enteredWords, password).execute(); + if (_seedOnly){ + Intent result = new Intent(); + result.putStringArrayListExtra(MASTERSEED, new ArrayList(enteredWords)); + result.putExtra(PASSWORD, password); + setResult(RESULT_OK, result); + finish(); + } else { + _progress.setCancelable(false); + _progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); + _progress.setMessage(getString(R.string.importing_master_seed_from_wordlist)); + _progress.show(); + new MasterSeedFromWordsAsyncTask(_mbwManager.getEventBus(), enteredWords, password).execute(); + } } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/HdAccountSelectorActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/HdAccountSelectorActivity.java index f64fb0f8b7..b881f46c91 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/HdAccountSelectorActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/HdAccountSelectorActivity.java @@ -45,6 +45,7 @@ import android.widget.*; import com.google.common.collect.Iterables; import com.mrd.bitlib.crypto.HdKeyNode; +import com.mrd.bitlib.model.hdpath.HdKeyPath; import com.mycelium.wallet.MbwManager; import com.mycelium.wallet.R; import com.mycelium.wallet.Utils; @@ -70,6 +71,7 @@ public abstract class HdAccountSelectorActivity extends Activity implements Mast protected AbstractAccountScanManager masterseedScanManager; + private ListView lvAccounts; protected TextView txtStatus; @@ -106,7 +108,7 @@ public UUID checkForTransactions(AbstractAccountScanManager.HdKeyNodeWrapper acc UUID id = masterseedScanManager.createOnTheFlyAccount( account.accountRoot, walletManager, - account.accountIndex); + account.keyPath.getLastIndex()); Bip44Account tempAccount = (Bip44Account) walletManager.getAccount(id); tempAccount.synchronize(false); @@ -197,7 +199,7 @@ public void setPassphrase(String passphrase){ protected class HdAccountWrapper implements Serializable { public UUID id; - public int accountIndex; + public HdKeyPath accountHdKeyPath; public HdKeyNode xPub; public String name; @@ -274,8 +276,12 @@ public void onStatusChanged(AccountScanManager.OnStatusChanged event){ public void onAccountFound(AccountScanManager.OnAccountFound event){ HdAccountWrapper acc = new HdAccountWrapper(); acc.id = event.account.accountId; - acc.accountIndex = event.account.accountIndex; - acc.name = String.format(getString(R.string.account_number), event.account.accountIndex + 1); + acc.accountHdKeyPath = event.account.keyPath; + if (event.account.keyPath.equals(HdKeyPath.BIP32_ROOT)) { + acc.name = getString(R.string.bip32_root_account); + } else { + acc.name = String.format(getString(R.string.account_number), event.account.keyPath.getLastIndex() + 1); + } acc.xPub = event.account.accountRoot; if (!accounts.contains(acc)) { accountsAdapter.add(acc); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/InstantMasterseedActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/InstantMasterseedActivity.java index 6906b80d24..1b2fb42c60 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/InstantMasterseedActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/InstantMasterseedActivity.java @@ -42,8 +42,7 @@ import com.mrd.bitlib.crypto.Bip39; import com.mycelium.wallet.MbwManager; import com.mycelium.wallet.R; -import com.mycelium.wallet.Utils; -import com.mycelium.wallet.activity.send.SendMainActivity; +import com.mycelium.wallet.activity.send.SendInitializationActivity; import com.mycelium.wallet.activity.util.AbstractAccountScanManager; import com.mycelium.wallet.activity.util.MasterseedScanManager; import com.mycelium.wapi.wallet.AccountScanManager; @@ -52,26 +51,33 @@ public class InstantMasterseedActivity extends HdAccountSelectorActivity { + public static final String PASSWORD = "password"; + public static final String WORDS = "words"; + public static final String MASTERSEED = "masterseed"; private Bip39.MasterSeed masterSeed; private String[] words; + private String password; - public static void callMe(Activity currentActivity, String[] masterSeedWords) { + // if password is null, the scan manager will ask the user for a password later on + public static void callMe(Activity currentActivity, String[] masterSeedWords, String password) { Intent intent = new Intent(currentActivity, InstantMasterseedActivity.class); - intent.putExtra("words", masterSeedWords); + intent.putExtra(WORDS, masterSeedWords); + intent.putExtra(PASSWORD, password); currentActivity.startActivity(intent); } public static void callMe(Activity currentActivity, int requestCode, Bip39.MasterSeed masterSeed) { Intent intent = new Intent(currentActivity, InstantMasterseedActivity.class); - intent.putExtra("masterseed", masterSeed); + intent.putExtra(MASTERSEED, masterSeed); currentActivity.startActivityForResult(intent, requestCode); } @Override protected void onCreate(Bundle savedInstanceState) { - masterSeed = (Bip39.MasterSeed) getIntent().getSerializableExtra("masterseed"); + masterSeed = (Bip39.MasterSeed) getIntent().getSerializableExtra(MASTERSEED); if (masterSeed == null){ - words = getIntent().getStringArrayExtra("words"); + words = getIntent().getStringArrayExtra(WORDS); + password = getIntent().getStringExtra(PASSWORD); } super.onCreate(savedInstanceState); } @@ -105,6 +111,7 @@ protected AbstractAccountScanManager initMasterseedManager() { this, mbwManager.getNetwork(), words, + password, mbwManager.getEventBus()); } } @@ -117,7 +124,7 @@ protected AdapterView.OnItemClickListener accountClickListener() { @Override public void onItemClick(AdapterView adapterView, View view, int i, long l) { HdAccountWrapper item = (HdAccountWrapper) adapterView.getItemAtPosition(i); - Intent intent = SendMainActivity.getIntent(InstantMasterseedActivity.this, item.id, true); + Intent intent = SendInitializationActivity.getIntent(InstantMasterseedActivity.this, item.id, true); InstantMasterseedActivity.this.startActivityForResult(intent, REQUEST_SEND); } }; 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 cdb970b239..7ac89ad934 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 @@ -168,6 +168,7 @@ private void updateUi() { _mbwManager.reportIgnoredException(ex); balance = CurrencyBasedBalance.ZERO_BITCOIN_BALANCE; } + if (account.canSpend()) { // Show spend button _root.findViewById(R.id.btSend).setVisibility(View.VISIBLE); 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 f7ac07077b..ffa626541f 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 @@ -23,127 +23,141 @@ import java.util.Map; public class TransactionArrayAdapter extends ArrayAdapter { - private final MetadataStorage _storage; - private Context _context; - private DateFormat dateFormat; - private MbwManager _mbwManager; - private Fragment containerFragment; - private Map _addressBook; - - public TransactionArrayAdapter(Context context, List transactions, Map addressBook) { - this(context, transactions, null, addressBook); - } - - public TransactionArrayAdapter(Context context, List transactions, Fragment containerFragment, Map addressBook) { - super(context, R.layout.transaction_row, transactions); - _context = context; - dateFormat = new AdaptiveDateFormat(context); - _mbwManager = MbwManager.getInstance(context); - this.containerFragment = containerFragment; - _storage = _mbwManager.getMetadataStorage(); - _addressBook = addressBook; - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - // Only inflate a new view if we are not reusing an old one - View rowView = convertView; - if (rowView == null) { - LayoutInflater inflater = (LayoutInflater) _context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - rowView = Preconditions.checkNotNull(inflater.inflate(R.layout.transaction_row, parent, false)); - } - - // Make sure we are still added - if (containerFragment != null && !containerFragment.isAdded()) { - // We have observed that the fragment can be disconnected at this - // point - return rowView; - } - - final TransactionSummary record = getItem(position); - - // Determine Color - int color; - if (record.value < 0) { - color = _context.getResources().getColor(R.color.red); - } else { - color = _context.getResources().getColor(R.color.green); - } - - // Set Date - Date date = new Date(record.time * 1000L); - TextView tvDate = (TextView) rowView.findViewById(R.id.tvDate); - tvDate.setText(dateFormat.format(date)); - - // Set value - TextView tvAmount = (TextView) rowView.findViewById(R.id.tvAmount); - tvAmount.setText(_mbwManager.getBtcValueString(record.value)); - tvAmount.setTextColor(color); - - // Set fiat value - TextView tvFiat = (TextView) rowView.findViewById(R.id.tvFiatAmount); - Double rate = _mbwManager.getCurrencySwitcher().getExchangeRatePrice(); - if (_mbwManager.hasFiatCurrency() && rate == null) { - _mbwManager.getExchangeRateManager().requestRefresh(); - } - if (!_mbwManager.hasFiatCurrency() || rate == null) { - tvFiat.setVisibility(View.GONE); - } 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); - } - - - - // 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); - - if (record.destinationAddress.isPresent() && _addressBook.containsKey(record.destinationAddress.get())) { + private final MetadataStorage _storage; + protected Context _context; + private final boolean _alwaysShowAddress; + private DateFormat _dateFormat; + private MbwManager _mbwManager; + private Fragment _containerFragment; + private Map _addressBook; + + public TransactionArrayAdapter(Context context, List transactions, Map addressBook) { + this(context, transactions, null, addressBook, true); + } + + public TransactionArrayAdapter(Context context, + List transactions, + Fragment containerFragment, + Map addressBook, + boolean alwaysShowAddress) { + super(context, R.layout.transaction_row, transactions); + _context = context; + _alwaysShowAddress = alwaysShowAddress; + _dateFormat = new AdaptiveDateFormat(context); + _mbwManager = MbwManager.getInstance(context); + _containerFragment = containerFragment; + _storage = _mbwManager.getMetadataStorage(); + _addressBook = addressBook; + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + // Only inflate a new view if we are not reusing an old one + View rowView = convertView; + if (rowView == null) { + LayoutInflater inflater = (LayoutInflater) _context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + rowView = Preconditions.checkNotNull(inflater.inflate(R.layout.transaction_row, parent, false)); + } + + // Make sure we are still added + if (_containerFragment != null && !_containerFragment.isAdded()) { + // We have observed that the fragment can be disconnected at this + // point + return rowView; + } + + final TransactionSummary record = getItem(position); + + // Determine Color + int color; + if (record.value < 0) { + color = _context.getResources().getColor(R.color.red); + } else { + color = _context.getResources().getColor(R.color.green); + } + + // Set Date + Date date = new Date(record.time * 1000L); + TextView tvDate = (TextView) rowView.findViewById(R.id.tvDate); + tvDate.setText(_dateFormat.format(date)); + + // Set value + TextView tvAmount = (TextView) rowView.findViewById(R.id.tvAmount); + tvAmount.setText(_mbwManager.getBtcValueString(record.value)); + tvAmount.setTextColor(color); + + // Set fiat value + TextView tvFiat = (TextView) rowView.findViewById(R.id.tvFiatAmount); + Double rate = _mbwManager.getCurrencySwitcher().getExchangeRatePrice(); + if (_mbwManager.hasFiatCurrency() && rate == null) { + _mbwManager.getExchangeRateManager().requestRefresh(); + } + if (!_mbwManager.hasFiatCurrency() || rate == null) { + tvFiat.setVisibility(View.GONE); + } 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); + } + + + // 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); + + + if (record.destinationAddress.isPresent()) { + if (_addressBook.containsKey(record.destinationAddress.get())) { tvDestAddress.setText(record.destinationAddress.get().getShortAddress()); tvAddressLabel.setText(String.format(_context.getString(R.string.transaction_to_address_prefix), _addressBook.get(record.destinationAddress.get()))); tvDestAddress.setVisibility(View.VISIBLE); tvAddressLabel.setVisibility(View.VISIBLE); - }else { + } else if (_alwaysShowAddress) { + tvDestAddress.setText(record.destinationAddress.get().getShortAddress()); + tvDestAddress.setVisibility(View.VISIBLE); + } else { tvDestAddress.setVisibility(View.GONE); tvAddressLabel.setVisibility(View.GONE); - } - - // Show confirmations indicator - int confirmations = record.confirmations; - TransactionConfirmationsDisplay tcdConfirmations = (TransactionConfirmationsDisplay) rowView.findViewById(R.id.tcdConfirmations); - if (record.isQueuedOutgoing) { - // Outgoing, not broadcasted - tcdConfirmations.setNeedsBroadcast(); - } else { - tcdConfirmations.setConfirmations(confirmations); - } - - // Show label or confirmations - TextView tvLabel = (TextView) rowView.findViewById(R.id.tvTransactionLabel); - String label = _storage.getLabelByTransaction(record.txid); - if (label.length() == 0) { - // if we have no txLabel show the confirmation state instead - to keep they layout ballanced - String confirmationsText; - if (record.isQueuedOutgoing) { - confirmationsText = _context.getResources().getString(R.string.transaction_not_broadcasted_info); + } + } else { + tvDestAddress.setVisibility(View.GONE); + tvAddressLabel.setVisibility(View.GONE); + } + + // Show confirmations indicator + int confirmations = record.confirmations; + TransactionConfirmationsDisplay tcdConfirmations = (TransactionConfirmationsDisplay) rowView.findViewById(R.id.tcdConfirmations); + if (record.isQueuedOutgoing) { + // Outgoing, not broadcasted + tcdConfirmations.setNeedsBroadcast(); + } else { + tcdConfirmations.setConfirmations(confirmations); + } + + // Show label or confirmations + TextView tvLabel = (TextView) rowView.findViewById(R.id.tvTransactionLabel); + String label = _storage.getLabelByTransaction(record.txid); + if (label.length() == 0) { + // if we have no txLabel show the confirmation state instead - to keep they layout ballanced + String confirmationsText; + if (record.isQueuedOutgoing) { + confirmationsText = _context.getResources().getString(R.string.transaction_not_broadcasted_info); + } else { + if (confirmations > 6) { + confirmationsText = _context.getResources().getString(R.string.confirmed); } else { - if (confirmations > 6){ - confirmationsText = _context.getResources().getString(R.string.confirmed); - } else { - confirmationsText = _context.getResources().getString(R.string.confirmations, confirmations); - } + confirmationsText = _context.getResources().getString(R.string.confirmations, confirmations); } - tvLabel.setText(confirmationsText); - } else { - tvLabel.setText(label); - } + } + tvLabel.setText(confirmationsText); + } else { + tvLabel.setText(label); + } - rowView.setTag(record); - return rowView; - } + rowView.setTag(record); + return rowView; + } } 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 4a5d3024c0..50211a56c4 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 @@ -60,7 +60,6 @@ import com.mycelium.wallet.activity.modern.Toaster; import com.mycelium.wallet.activity.send.BroadcastTransactionActivity; import com.mycelium.wallet.activity.util.EnterAddressLabelUtil; -import com.mycelium.wallet.activity.util.TransactionConfirmationsDisplay; import com.mycelium.wallet.event.AddressBookChanged; import com.mycelium.wallet.event.ExchangeRatesRefreshed; import com.mycelium.wallet.event.SelectedCurrencyChanged; @@ -206,11 +205,9 @@ private void finishActionMode() { } private class TransactionHistoryAdapter extends TransactionArrayAdapter { - private Context _context; public TransactionHistoryAdapter(Context context, List transactions) { - super(context, transactions, TransactionHistoryFragment.this, _addressBook); - _context = context; + super(context, transactions, TransactionHistoryFragment.this, _addressBook, false); } @Override 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 b4073abd71..125aedffea 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 @@ -893,6 +893,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); + return true; } else if (item.getItemId() == R.id.miLockKeys) { lock(); return true; 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 e1590c4eb1..0fc1f4bc30 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 @@ -38,7 +38,6 @@ import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.PixelFormat; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Bundle; @@ -207,6 +206,7 @@ private void addEnglishSetting(MenuItem settingsItem) { } } + // controlling the behavior here is the safe but slightly slower responding // way of doing this. // controlling the visibility from the individual fragments is a bug-ridden @@ -222,6 +222,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()); // Lock menu final boolean hasPin = _mbwManager.isPinProtected(); 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 015f858354..1bc7298314 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,11 +62,7 @@ import com.mycelium.wapi.model.TransactionDetails; import com.mycelium.wapi.model.TransactionSummary; import com.mycelium.wapi.wallet.WalletAccount; -import com.squareup.okhttp.MediaType; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.RequestBody; -import com.squareup.okhttp.Response; +import com.squareup.okhttp.*; import java.io.IOException; import java.net.MalformedURLException; @@ -76,273 +72,274 @@ import java.util.List; public class PopActivity extends Activity { - private PopRequest popRequest; - private MbwManager _mbwManager; - private Sha256Hash txidToProve; - private static final int SIGN_TRANSACTION_REQUEST_CODE = 6; - - @Override - protected void onCreate(Bundle savedInstanceState) { - this.requestWindowFeature(Window.FEATURE_NO_TITLE); - super.onCreate(savedInstanceState); - setContentView(R.layout.pop_activity); - _mbwManager = MbwManager.getInstance(getApplication()); - - if (savedInstanceState != null) { - popRequest = (PopRequest)savedInstanceState.getSerializable("popRequest"); - txidToProve = (Sha256Hash)savedInstanceState.getSerializable("txidToProve"); - updateUi(_mbwManager.getSelectedAccount().getTransactionSummary(txidToProve)); + private PopRequest popRequest; + private MbwManager _mbwManager; + private Sha256Hash txidToProve; + private static final int SIGN_TRANSACTION_REQUEST_CODE = 6; + + @Override + protected void onCreate(Bundle savedInstanceState) { + this.requestWindowFeature(Window.FEATURE_NO_TITLE); + super.onCreate(savedInstanceState); + setContentView(R.layout.pop_activity); + _mbwManager = MbwManager.getInstance(getApplication()); + + if (savedInstanceState != null) { + popRequest = (PopRequest) savedInstanceState.getSerializable("popRequest"); + txidToProve = (Sha256Hash) savedInstanceState.getSerializable("txidToProve"); + updateUi(_mbwManager.getSelectedAccount().getTransactionSummary(txidToProve)); + return; + } + + popRequest = (PopRequest) getIntent().getSerializableExtra("popRequest"); + if (popRequest == null) { + finish(); + } + + Sha256Hash userSelectedTransaction = (Sha256Hash) getIntent().getSerializableExtra("selectedTransactionToProve"); + TransactionSummary txToProve; + if (userSelectedTransaction != null) { + txidToProve = userSelectedTransaction; + txToProve = _mbwManager.getSelectedAccount().getTransactionSummary(txidToProve); + } else { + // Get history ordered by block height descending + List transactionHistory = _mbwManager.getSelectedAccount().getTransactionHistory(0, 10000); + TransactionSummary matchingTransaction = findFirstMatchingTransaction(popRequest, transactionHistory); + if (matchingTransaction == null) { + launchSelectTransactionActivity(); return; - } - - popRequest = (PopRequest)getIntent().getSerializableExtra("popRequest"); - if (popRequest == null) { - finish(); - } - - Sha256Hash userSelectedTransaction = (Sha256Hash) getIntent().getSerializableExtra("selectedTransactionToProve"); - TransactionSummary txToProve; - if (userSelectedTransaction != null) { - txidToProve = userSelectedTransaction; - txToProve = _mbwManager.getSelectedAccount().getTransactionSummary(txidToProve); - } else { - // Get history ordered by block height descending - List transactionHistory = _mbwManager.getSelectedAccount().getTransactionHistory(0, 10000); - TransactionSummary matchingTransaction = findFirstMatchingTransaction(popRequest, transactionHistory); - if (matchingTransaction == null) { - launchSelectTransactionActivity(); - return; - } - txidToProve = matchingTransaction.txid; - txToProve = matchingTransaction; - } - - updateUi(txToProve); - } - - private void launchSelectTransactionActivity() { - Intent intent = new Intent(this, PopSelectTransactionActivity.class); - intent.putExtra("popRequest", popRequest); - startActivity(intent); - finish(); - } - - private TransactionSummary findFirstMatchingTransaction(PopRequest popRequest, List transactions) { - MetadataStorage metadataStorage = _mbwManager.getMetadataStorage(); - for (TransactionSummary transactionSummary : transactions) { - if (PopUtils.matches(popRequest, metadataStorage, transactionSummary)) { - return transactionSummary; - } - } - return null; - } - - private void setText(int viewId, String value) { - TextView textView = (TextView) findViewById(viewId); - textView.setText(value); - } - - private long getFee(TransactionDetails tx) { - long inputs = sum(tx.inputs); - long outputs = sum(tx.outputs); - return inputs - outputs; - } - - private long sum(TransactionDetails.Item[] items) { - long sum = 0; - for (TransactionDetails.Item item : items) { - sum += item.value; - } - return sum; - } - - private void updateUi(TransactionSummary transactionSummary) { - MetadataStorage metadataStorage = _mbwManager.getMetadataStorage(); - - // Set Date - Date date = new Date(transactionSummary.time * 1000L); - DateFormat dateFormat = new AdaptiveDateFormat(getApplicationContext()); - setText(R.id.pop_transaction_date, dateFormat.format(date)); - - // Set amount - long amountSatoshis = getPaymentAmountSatoshis(transactionSummary); - String value = _mbwManager.getBtcValueString(amountSatoshis); - String fiatValue = _mbwManager.getCurrencySwitcher().getFormattedFiatValue(amountSatoshis, true); - String fiatAppendment = ""; - if (!Strings.isNullOrEmpty(fiatValue)) { - fiatAppendment = " (" + fiatValue + ")"; - } - setText(R.id.pop_transaction_amount, value + fiatAppendment); - - // Set label - String label = metadataStorage.getLabelByTransaction(transactionSummary.txid); - setText(R.id.pop_transaction_label, label); - - URL url = getUrl(popRequest.getP()); - if (url == null) { - Toast.makeText(this, "Invalid URL:" + popRequest.getP(), Toast.LENGTH_LONG).show(); - finish(); - return; - } - - TextView textView = (TextView) findViewById(R.id.pop_recipient_host); - textView.setText(url.getHost()); - String protocol = url.getProtocol(); - if ("https".equals(protocol)) { - textView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.holo_dark_ic_action_secure, 0, 0, 0); - } else if ("http".equals(protocol)) { - textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } else { - Toast.makeText(this, "Unsupported protocol:" + url.getProtocol(), Toast.LENGTH_LONG).show(); - finish(); - } - } - - private URL getUrl(String pParam) { - URL url; - try { - url = new URL(pParam); - } catch (MalformedURLException e) { - Toast.makeText(this, "Not a proper destination URL:" + pParam, Toast.LENGTH_LONG).show(); - finish(); - return null; - } - return url; - } - - private long getPaymentAmountSatoshis(TransactionSummary transactionSummary) { - long amountSatoshis = transactionSummary.value; - if (amountSatoshis < 0) { - amountSatoshis = -amountSatoshis; - } - TransactionDetails transactionDetails = _mbwManager.getSelectedAccount().getTransactionDetails(transactionSummary.txid); - amountSatoshis -= getFee(transactionDetails); - return amountSatoshis; - } - - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - super.onSaveInstanceState(savedInstanceState); - savedInstanceState.putSerializable("popRequest", popRequest); - savedInstanceState.putSerializable("txidToProve", txidToProve); - } - - - public void sendPop(View view) { - try { - if (txidToProve == null) { - Toast.makeText(this, R.string.pop_no_transaction_selected, Toast.LENGTH_LONG).show(); - } - WalletAccount account = _mbwManager.getSelectedAccount(); - - final UnsignedTransaction unsignedPop = account.createUnsignedPop(txidToProve, popRequest.getN()); - - _mbwManager.runPinProtectedFunction(PopActivity.this, new Runnable() { - - @Override - public void run() { - disableButtons(); - SignTransactionActivity.callMe(PopActivity.this, _mbwManager.getSelectedAccount().getId(), - false, unsignedPop, SIGN_TRANSACTION_REQUEST_CODE); - } - }); - } catch (Exception e) { - Toast.makeText(this, "An internal error occurred:" + e.getMessage(), Toast.LENGTH_LONG).show(); - } - } - - public void selectOtherTransaction(View view) { - launchSelectTransactionActivity(); - } - - private void disableButtons() { - findViewById(R.id.btSend).setEnabled(false); - findViewById(R.id.btSelectOther).setEnabled(false); - } - - public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { - if (requestCode == SIGN_TRANSACTION_REQUEST_CODE) { - if (resultCode == RESULT_OK) { - Transaction pop = (Transaction) Preconditions.checkNotNull(intent.getSerializableExtra("signedTx")); - ConnectivityManager connMgr = (ConnectivityManager) - getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); - if (networkInfo != null && networkInfo.isConnected()) { - new SendPopTask().execute(pop); - } else { - Toast.makeText(this, "No network available", Toast.LENGTH_LONG).show(); - } + } + txidToProve = matchingTransaction.txid; + txToProve = matchingTransaction; + } + + updateUi(txToProve); + } + + private void launchSelectTransactionActivity() { + Intent intent = new Intent(this, PopSelectTransactionActivity.class); + intent.putExtra("popRequest", popRequest); + startActivity(intent); + finish(); + } + + private TransactionSummary findFirstMatchingTransaction(PopRequest popRequest, List transactions) { + MetadataStorage metadataStorage = _mbwManager.getMetadataStorage(); + for (TransactionSummary transactionSummary : transactions) { + if (PopUtils.matches(popRequest, metadataStorage, transactionSummary)) { + return transactionSummary; + } + } + return null; + } + + private void setText(int viewId, String value) { + TextView textView = (TextView) findViewById(viewId); + textView.setText(value); + } + + private long getFee(TransactionDetails tx) { + long inputs = sum(tx.inputs); + long outputs = sum(tx.outputs); + return inputs - outputs; + } + + private long sum(TransactionDetails.Item[] items) { + long sum = 0; + for (TransactionDetails.Item item : items) { + sum += item.value; + } + return sum; + } + + private void updateUi(TransactionSummary transactionSummary) { + MetadataStorage metadataStorage = _mbwManager.getMetadataStorage(); + + // Set Date + Date date = new Date(transactionSummary.time * 1000L); + DateFormat dateFormat = new AdaptiveDateFormat(getApplicationContext()); + setText(R.id.pop_transaction_date, dateFormat.format(date)); + + // Set amount + long amountSatoshis = getPaymentAmountSatoshis(transactionSummary); + String value = _mbwManager.getBtcValueString(amountSatoshis); + String fiatValue = _mbwManager.getCurrencySwitcher().getFormattedFiatValue(amountSatoshis, true); + String fiatAppendment = ""; + if (!Strings.isNullOrEmpty(fiatValue)) { + fiatAppendment = " (" + fiatValue + ")"; + } + setText(R.id.pop_transaction_amount, value + fiatAppendment); + + // Set label + String label = metadataStorage.getLabelByTransaction(transactionSummary.txid); + setText(R.id.pop_transaction_label, label); + + URL url = getUrl(popRequest.getP()); + if (url == null) { + Toast.makeText(this, "Invalid URL:" + popRequest.getP(), Toast.LENGTH_LONG).show(); + finish(); + return; + } + + TextView textView = (TextView) findViewById(R.id.pop_recipient_host); + textView.setText(url.getHost()); + String protocol = url.getProtocol(); + if ("https".equals(protocol)) { + textView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.holo_dark_ic_action_secure, 0, 0, 0); + } else if ("http".equals(protocol)) { + textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } else { + Toast.makeText(this, "Unsupported protocol:" + url.getProtocol(), Toast.LENGTH_LONG).show(); + finish(); + } + } + + private URL getUrl(String pParam) { + URL url; + try { + url = new URL(pParam); + } catch (MalformedURLException e) { + Toast.makeText(this, "Not a proper destination URL:" + pParam, Toast.LENGTH_LONG).show(); + finish(); + return null; + } + return url; + } + + private long getPaymentAmountSatoshis(TransactionSummary transactionSummary) { + long amountSatoshis = transactionSummary.value; + if (amountSatoshis < 0) { + amountSatoshis = -amountSatoshis; + } + TransactionDetails transactionDetails = _mbwManager.getSelectedAccount().getTransactionDetails(transactionSummary.txid); + amountSatoshis -= getFee(transactionDetails); + return amountSatoshis; + } + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + savedInstanceState.putSerializable("popRequest", popRequest); + savedInstanceState.putSerializable("txidToProve", txidToProve); + } + + + public void sendPop(View view) { + try { + if (txidToProve == null) { + Toast.makeText(this, R.string.pop_no_transaction_selected, Toast.LENGTH_LONG).show(); + } + WalletAccount account = _mbwManager.getSelectedAccount(); + + final UnsignedTransaction unsignedPop = account.createUnsignedPop(txidToProve, popRequest.getN()); + + _mbwManager.runPinProtectedFunction(PopActivity.this, new Runnable() { + + @Override + public void run() { + disableButtons(); + SignTransactionActivity.callMe(PopActivity.this, _mbwManager.getSelectedAccount().getId(), + false, unsignedPop, SIGN_TRANSACTION_REQUEST_CODE); } - } else { - super.onActivityResult(requestCode, resultCode, intent); - } - } - - private class SendPopTask extends AsyncTask { - @Override - protected String doInBackground(Transaction... pop) { - - // params comes from the execute() call: params[0] is the url. - return sendPop(pop[0]); - } - - // onPostExecute displays the results of the AsyncTask. - @Override - protected void onPostExecute(String result) { - if (result.equals("valid")) { - Toast.makeText(PopActivity.this, R.string.pop_success, Toast.LENGTH_LONG).show(); - finish(); + }); + } catch (Exception e) { + Toast.makeText(this, "An internal error occurred:" + e.getMessage(), Toast.LENGTH_LONG).show(); + } + } + + public void selectOtherTransaction(View view) { + launchSelectTransactionActivity(); + } + + private void disableButtons() { + findViewById(R.id.btSend).setEnabled(false); + findViewById(R.id.btSelectOther).setEnabled(false); + } + + public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { + if (requestCode == SIGN_TRANSACTION_REQUEST_CODE) { + if (resultCode == RESULT_OK) { + Transaction pop = (Transaction) Preconditions.checkNotNull(intent.getSerializableExtra("signedTx")); + ConnectivityManager connMgr = (ConnectivityManager) + getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); + if (networkInfo != null && networkInfo.isConnected()) { + new SendPopTask().execute(pop); } else { - String serverMessage = result; - String prefix = "invalid\n"; - if (result.startsWith(prefix)) { - serverMessage = result.substring(prefix.length()); - } - String message = s(R.string.pop_invalid_pop) + " " + s(R.string.pop_message_from_server) + "\n" + serverMessage; - Utils.showSimpleMessageDialog(PopActivity.this, message, new Runnable() { - @Override - public void run() { - launchSelectTransactionActivity(); - } - }, R.string.pop_select_other_tx, null); + Toast.makeText(this, "No network available", Toast.LENGTH_LONG).show(); } - } - } - - private String s(@StringRes int resId) { - return getResources().getText(resId).toString(); - } - - private String sendPop(Transaction tx) { - RequestBody requestBody = RequestBody.create(MediaType.parse("application/bitcoin-pop"), tx.toBytes()); - - URL url; - try { + } + } else { + super.onActivityResult(requestCode, resultCode, intent); + } + } + + private class SendPopTask extends AsyncTask { + @Override + protected String doInBackground(Transaction... pop) { + // params comes from the execute() call: params[0] is the url. + return sendPop(pop[0]); + } + + private String sendPop(Transaction tx) { + RequestBody requestBody = RequestBody.create(MediaType.parse("application/bitcoin-pop"), tx.toBytes()); + + URL url; + try { url = new URL(popRequest.getP()); if (!"http".equals(url.getProtocol()) && !"https".equals(url.getProtocol())) { - return "Invalid Url, expected protocol http or https: " + popRequest.getP(); + return "Invalid Url, expected protocol http or https: " + popRequest.getP(); } - } catch (MalformedURLException e) { + } catch (MalformedURLException e) { return "Invalid Url: " + popRequest.getP(); - } + } - Request request = new Request.Builder().url(url).post(requestBody).build(); + Request request = new Request.Builder().url(url).post(requestBody).build(); - OkHttpClient httpClient = new OkHttpClient(); - if (_mbwManager.getTorMode() == ServerEndpointType.Types.ONLY_TOR && _mbwManager.getTorManager() != null){ - httpClient =_mbwManager.getTorManager().setupClient(httpClient); - } + OkHttpClient httpClient = new OkHttpClient(); + if (_mbwManager.getTorMode() == ServerEndpointType.Types.ONLY_TOR && _mbwManager.getTorManager() != null) { + httpClient = _mbwManager.getTorManager().setupClient(httpClient); + } - try { + try { Response response = httpClient.newCall(request).execute(); if (response.isSuccessful()) { - return response.body().string(); + return response.body().string(); } else { - return "Error occurred: " + response.code(); + return "Error occurred: " + response.code(); } - } catch (IOException e) { + } catch (IOException e) { return "Cannot communicate with server: " + e.getMessage(); - } + } + + } + + // onPostExecute displays the results of the AsyncTask. + @Override + protected void onPostExecute(String result) { + if (result.equals("valid")) { + Toast.makeText(PopActivity.this, R.string.pop_success, Toast.LENGTH_LONG).show(); + finish(); + } else { + String serverMessage = result; + String prefix = "invalid\n"; + if (result.startsWith(prefix)) { + serverMessage = result.substring(prefix.length()); + } + String message = s(R.string.pop_invalid_pop) + " " + s(R.string.pop_message_from_server) + "\n" + serverMessage; + Utils.showSimpleMessageDialog(PopActivity.this, message, new Runnable() { + @Override + public void run() { + launchSelectTransactionActivity(); + } + }, R.string.pop_select_other_tx, null); + } + } + } + + private String s(@StringRes int resId) { + return getResources().getText(resId).toString(); + } + - } } 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 9237aa2e94..b7ef109dff 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 @@ -37,16 +37,15 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.app.ListFragment; +import android.support.v4.app.*; import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarActivity; import android.view.View; import android.view.ViewGroup; -import android.view.Window; +import android.view.inputmethod.InputMethodManager; import com.mrd.bitlib.model.Address; +import com.mrd.bitlib.util.Sha256Hash; import com.mycelium.wallet.MbwManager; import com.mycelium.wallet.R; import com.mycelium.wallet.activity.main.TransactionArrayAdapter; @@ -58,118 +57,190 @@ import java.util.List; import java.util.Map; -public class PopSelectTransactionActivity extends FragmentActivity { - private PopRequest popRequest; - private MbwManager mbwManager; - @Override - protected void onCreate(Bundle savedInstanceState) { - this.requestWindowFeature(Window.FEATURE_NO_TITLE); - super.onCreate(savedInstanceState); - setContentView(R.layout.pop_select_transaction_activity); - - PopRequest popRequest = (PopRequest) getIntent().getSerializableExtra("popRequest"); - if (popRequest == null) { - finish(); - } - this.popRequest = popRequest; - - - mbwManager = MbwManager.getInstance(getApplicationContext()); - WalletAccount account = mbwManager.getSelectedAccount(); - if (account.isArchived()) { - return; - } - HistoryPagerAdapter pagerAdapter = new HistoryPagerAdapter(getSupportFragmentManager()); - ViewPager viewPager = (ViewPager) findViewById(R.id.pager); - viewPager.setAdapter(pagerAdapter); - - } - - public class HistoryPagerAdapter extends FragmentPagerAdapter { - - private ListFragment matchingTransactionsFragment = null; - private ListFragment nonMatchingTransactionsFragment = null; - - public HistoryPagerAdapter(FragmentManager fm) { - super(fm); - - WalletAccount account = mbwManager.getSelectedAccount(); - - List history = account.getTransactionHistory(0, 1000); - List matchingTransactions = new ArrayList(); - List nonMatchingTransactions = new ArrayList(); - - for (TransactionSummary transactionSummary : history) { - if (transactionSummary.value >= 0L) { - // We are only interested in payments - continue; - } - if (PopUtils.matches(popRequest, mbwManager.getMetadataStorage(), transactionSummary)) { - matchingTransactions.add(transactionSummary); - } else { - nonMatchingTransactions.add(transactionSummary); - } +public class PopSelectTransactionActivity extends ActionBarActivity implements ActionBar.TabListener { + private PopRequest popRequest; + private MbwManager mbwManager; + private ViewPager viewPager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.pop_select_transaction_activity); + + PopRequest popRequest = (PopRequest) getIntent().getSerializableExtra("popRequest"); + if (popRequest == null) { + finish(); + } + this.popRequest = popRequest; + + + mbwManager = MbwManager.getInstance(getApplicationContext()); + WalletAccount account = mbwManager.getSelectedAccount(); + if (account.isArchived()) { + return; + } + // Set up the action bar. + final ActionBar actionBar = getSupportActionBar(); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + + viewPager = (ViewPager) findViewById(R.id.pager); + HistoryPagerAdapter pagerAdapter = new HistoryPagerAdapter(getSupportFragmentManager()); + viewPager.setAdapter(pagerAdapter); + + // When swiping between different sections, select the corresponding + // tab. We can also use ActionBar.Tab#select() to do this if we have + // a reference to the Tab. + viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + actionBar.setSelectedNavigationItem(position); + // Hide the keyboard. + ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(viewPager.getWindowToken(), 0); + } + }); + + + actionBar.addTab( + actionBar.newTab() + .setText(getString(R.string.pop_matching_transactions).toUpperCase()) + .setTabListener(this)); + + actionBar.addTab( + actionBar.newTab() + .setText(getString(R.string.pop_non_matching_transactions).toUpperCase()) + .setTabListener(this)); + + } + + @Override + public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + // When the given tab is selected, switch to the corresponding page in + // the ViewPager. + viewPager.setCurrentItem(tab.getPosition()); + } + + @Override + public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + + } + + @Override + public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) { + + } + + public class HistoryPagerAdapter extends FragmentPagerAdapter { + + private ListFragment matchingTransactionsFragment = null; + private ListFragment nonMatchingTransactionsFragment = null; + + public HistoryPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int i) { + switch (i) { + case 0: + return TransactionListFragment.init(popRequest, true); + case 1: + return TransactionListFragment.init(popRequest, false); + default: + throw new RuntimeException("Unknown fragment id " + i); + } + } + + @Override + public int getCount() { + return 2; + } + + /*@Override + public CharSequence getPageTitle(int position) { + return getString(position == 0 ? R.string.pop_matching_transactions : R.string.pop_non_matching_transactions); + }*/ + } + + public static class TransactionListFragment extends ListFragment { + private PopRequest popRequest; + private TransactionHistoryAdapter transactionHistoryAdapter; + + static TransactionListFragment init(PopRequest popRequest, boolean showMatching) { + TransactionListFragment list = new TransactionListFragment(); + + Bundle args = new Bundle(); + args.putSerializable("pop", popRequest); + args.putBoolean("match", showMatching); + list.setArguments(args); + + return list; + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + popRequest = (PopRequest) getArguments().getSerializable("pop"); + boolean showMatching = getArguments().getBoolean("match"); + + MbwManager mbwManager = MbwManager.getInstance(getActivity()); + WalletAccount account = mbwManager.getSelectedAccount(); + + List history = account.getTransactionHistory(0, 1000); + List list = new ArrayList(); + + for (TransactionSummary transactionSummary : history) { + if (transactionSummary.value >= 0L) { + // We are only interested in payments + continue; } - - Map addressBook = mbwManager.getMetadataStorage().getAllAddressLabels(); - - matchingTransactionsFragment = new ListFragment() { - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setEmptyText(getText(R.string.pop_no_matching_transactions)); - } - }; - matchingTransactionsFragment.setListAdapter(new TransactionHistoryAdapter(PopSelectTransactionActivity.this, matchingTransactions, addressBook)); - - nonMatchingTransactionsFragment = new ListFragment(); - nonMatchingTransactionsFragment.setListAdapter(new TransactionHistoryAdapter(PopSelectTransactionActivity.this, nonMatchingTransactions, addressBook)); - } - - @Override - public Fragment getItem(int i) { - switch (i) { - case 0: - return matchingTransactionsFragment; - case 1: - return nonMatchingTransactionsFragment; - default: - throw new RuntimeException("Unknown fragemt id " + i); + if (PopUtils.matches(popRequest, mbwManager.getMetadataStorage(), transactionSummary) == showMatching) { + list.add(transactionSummary); } - } - - @Override - public int getCount() { - return 2; - } - - @Override - public CharSequence getPageTitle(int position) { - return getString(position == 0 ? R.string.pop_matching_transactions : R.string.pop_non_matching_transactions); - } - } - - private class TransactionHistoryAdapter extends TransactionArrayAdapter { - - public TransactionHistoryAdapter(Context context, List objects, Map addressBook) { - super(context, objects, addressBook); - } - - @Override - public View getView(final int position, View convertView, ViewGroup parent) { - View view = super.getView(position, convertView, parent); - view.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent signPopIntent = new Intent(PopSelectTransactionActivity.this, PopActivity.class); - signPopIntent.putExtra("popRequest", popRequest); - signPopIntent.putExtra("selectedTransactionToProve", getItem(position).txid); - startActivity(signPopIntent); - finish(); - } - }); - return view; - } - } - + } + + Map addressBook = mbwManager.getMetadataStorage().getAllAddressLabels(); + transactionHistoryAdapter = new TransactionHistoryAdapter(getActivity(), list, addressBook); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setEmptyText(getText(R.string.pop_no_matching_transactions)); + setListShown(true); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setListAdapter(transactionHistoryAdapter); + } + } + + public static class TransactionHistoryAdapter extends TransactionArrayAdapter { + + public TransactionHistoryAdapter(Context context, List objects, Map addressBook) { + super(context, objects, addressBook); + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + View view = super.getView(position, convertView, parent); + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ((PopSelectTransactionActivity) getContext()).onTxClick(getItem(position).txid); + } + }); + return view; + } + } + + protected void onTxClick(Sha256Hash txid) { + Intent signPopIntent = new Intent(PopSelectTransactionActivity.this, PopActivity.class); + signPopIntent.putExtra("popRequest", popRequest); + signPopIntent.putExtra("selectedTransactionToProve", txid); + startActivity(signPopIntent); + finish(); + } } 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 e4485b7403..d508b0ff2d 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 @@ -39,20 +39,20 @@ import com.mycelium.wapi.model.TransactionSummary; class PopUtils { - public static boolean matches(PopRequest popRequest, MetadataStorage metadataStorage, TransactionSummary transactionSummary) { - if (popRequest.getTxid() != null && !transactionSummary.txid.equals(popRequest.getTxid())) { + public static boolean matches(PopRequest popRequest, MetadataStorage metadataStorage, TransactionSummary transactionSummary) { + if (popRequest.getTxid() != null && !transactionSummary.txid.equals(popRequest.getTxid())) { + return false; + } + Long amountSatoshis = popRequest.getAmountSatoshis(); + if (amountSatoshis != null && amountSatoshis != -transactionSummary.value) { + return false; + } + if (popRequest.getLabel() != null) { + String label = metadataStorage.getLabelByTransaction(transactionSummary.txid); + if (!popRequest.getLabel().equals(label)) { return false; - } - Long amountSatoshis = popRequest.getAmountSatoshis(); - if (amountSatoshis != null && amountSatoshis != -transactionSummary.value) { - return false; - } - if (popRequest.getLabel() != null) { - String label = metadataStorage.getLabelByTransaction(transactionSummary.txid); - if (!popRequest.getLabel().equals(label)) { - return false; - } - } - return true; - } + } + } + return true; + } } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/send/ColdStorageSummaryActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/send/ColdStorageSummaryActivity.java index 309a671d36..ea053bd320 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/send/ColdStorageSummaryActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/send/ColdStorageSummaryActivity.java @@ -178,12 +178,4 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); } } - - @Override - public void onBackPressed() { - //delete temporary accounts keys so we do not keep scanned private keys in memory when user presses back - _mbwManager.forgetColdStorageWalletManager(); - super.onBackPressed(); - } - } \ 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 100743770e..59f9e877fe 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 @@ -44,16 +44,17 @@ import com.mrd.bitlib.model.Address; import com.mycelium.wallet.*; -import com.mycelium.wallet.activity.ScanActivity; -import com.mycelium.wallet.activity.StringHandlerActivity; +import com.mycelium.wallet.activity.*; import com.mycelium.wallet.trezor.activity.InstantTrezorActivity; +import java.util.ArrayList; import java.util.UUID; public class InstantWalletActivity extends Activity { public static final int REQUEST_SCAN = 0; private static final int REQUEST_TREZOR = 1; + private static final int IMPORT_WORDLIST = 2; public static void callMe(Activity currentActivity) { Intent intent = new Intent(currentActivity, InstantWalletActivity.class); @@ -73,10 +74,15 @@ public void onCreate(Bundle savedInstanceState) { findViewById(R.id.btClipboard).setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { - Intent intent = StringHandlerActivity.getIntent(InstantWalletActivity.this, - StringHandleConfig.spendFromColdStorage(), - Utils.getClipboardString(InstantWalletActivity.this)); - InstantWalletActivity.this.startActivityForResult(intent, REQUEST_SCAN); + handleString(Utils.getClipboardString(InstantWalletActivity.this)); + } + }); + + findViewById(R.id.btMasterseed).setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View arg0) { + EnterWordListActivity.callMe(InstantWalletActivity.this, IMPORT_WORDLIST, true); } }); @@ -96,6 +102,13 @@ public void onClick(View arg0) { }); } + private void handleString(String str) { + Intent intent = StringHandlerActivity.getIntent(InstantWalletActivity.this, + StringHandleConfig.spendFromColdStorage(), + str); + InstantWalletActivity.this.startActivityForResult(intent, REQUEST_SCAN); + } + @Override protected void onResume() { super.onResume(); @@ -123,6 +136,13 @@ public void onActivityResult(final int requestCode, final int resultCode, final if (resultCode == RESULT_OK) { finish(); } + } else if (requestCode == IMPORT_WORDLIST){ + if (resultCode == RESULT_OK) { + ArrayList wordList = intent.getStringArrayListExtra(EnterWordListActivity.MASTERSEED); + String password = intent.getStringExtra(EnterWordListActivity.PASSWORD); + InstantMasterseedActivity.callMe(this, wordList.toArray(new String[wordList.size()]), password); + + } } else { throw new IllegalStateException("unknown return codes after scanning... " + requestCode + " " + resultCode); } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/activity/send/SendInitializationActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/send/SendInitializationActivity.java index 9c1cf44276..41aa6ef33e 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/send/SendInitializationActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/send/SendInitializationActivity.java @@ -36,6 +36,7 @@ import android.app.Activity; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.view.View; @@ -71,6 +72,10 @@ public static void callMe(Activity currentActivity, UUID account, boolean isCold currentActivity.startActivity(intent); } + public static Intent getIntent(Activity currentActivity, UUID account, boolean isColdStorage) { + return prepareSendingIntent(currentActivity, account, (BitcoinUri)null, isColdStorage); + } + public static void callMeWithResult(Activity currentActivity, UUID account, BitcoinUri uri, boolean isColdStorage, int request) { Intent intent = prepareSendingIntent(currentActivity, account, uri, isColdStorage); currentActivity.startActivityForResult(intent, request); 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 2d22361c28..7cd333be9f 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 @@ -49,13 +49,12 @@ import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; - import com.google.common.annotations.VisibleForTesting; import com.google.common.base.*; import com.google.common.collect.ImmutableMap; import com.ledger.tbase.comm.LedgerTransportTEEProxyFactory; -import com.mrd.bitlib.util.HexUtils; import com.mrd.bitlib.util.CoinUtil.Denomination; +import com.mrd.bitlib.util.HexUtils; import com.mycelium.lt.api.model.TraderInfo; import com.mycelium.net.ServerEndpointType; import com.mycelium.wallet.*; @@ -119,9 +118,9 @@ public boolean onPreferenceChange(final Preference preference, Object o) { @Override public void run() { // toggle it here - boolean checked = !((CheckBoxPreference)preference).isChecked(); + boolean checked = !((CheckBoxPreference) preference).isChecked(); _mbwManager.setPinRequiredOnStartup(checked); - ((CheckBoxPreference)preference).setChecked(_mbwManager.getPinRequiredOnStartup()); + ((CheckBoxPreference) preference).setChecked(_mbwManager.getPinRequiredOnStartup()); } } ); @@ -177,56 +176,54 @@ public boolean onPreferenceClick(Preference preference) { }; private final OnPreferenceClickListener onClickLedgerNotificationDisableTee = new OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - CheckBoxPreference p = (CheckBoxPreference) preference; - _mbwManager.getLedgerManager().setDisableTEE(p.isChecked()); - return true; - } - }; + public boolean onPreferenceClick(Preference preference) { + CheckBoxPreference p = (CheckBoxPreference) preference; + _mbwManager.getLedgerManager().setDisableTEE(p.isChecked()); + return true; + } + }; private final OnPreferenceClickListener onClickLedgerSetUnpluggedAID = new OnPreferenceClickListener() { - private Button okButton; - private EditText aidEdit; - - public boolean onPreferenceClick(Preference preference) { - AlertDialog.Builder b = new AlertDialog.Builder(SettingsActivity.this); - b.setTitle(getString(R.string.ledger_set_unplugged_aid_title)); - b.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - byte[] aidBinary = null; - String aid = aidEdit.getText().toString(); - try { - aidBinary = HexUtils.toBytes(aid); - } - catch(Exception e) { - } - if (aidBinary == null) { - Utils.showSimpleMessageDialog(SettingsActivity.this, getString(R.string.ledger_check_unplugged_aid)); - } - else { - _mbwManager.getLedgerManager().setUnpluggedAID(aid); - } - } - }); - b.setNegativeButton(R.string.cancel, null); - - aidEdit = new EditText(SettingsActivity.this); - aidEdit.setInputType(InputType.TYPE_CLASS_TEXT); - aidEdit.setText(_mbwManager.getLedgerManager().getUnpluggedAID()); - LinearLayout llDialog = new LinearLayout(SettingsActivity.this); - llDialog.setOrientation(LinearLayout.VERTICAL); - llDialog.setPadding(10, 10, 10, 10); - TextView tvInfo = new TextView(SettingsActivity.this); - tvInfo.setText(getString(R.string.ledger_unplugged_aid)); - llDialog.addView(tvInfo); - llDialog.addView(aidEdit); - b.setView(llDialog); - AlertDialog dialog = b.show(); - return true; - } - }; - + private Button okButton; + private EditText aidEdit; + + public boolean onPreferenceClick(Preference preference) { + AlertDialog.Builder b = new AlertDialog.Builder(SettingsActivity.this); + b.setTitle(getString(R.string.ledger_set_unplugged_aid_title)); + b.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + byte[] aidBinary = null; + String aid = aidEdit.getText().toString(); + try { + aidBinary = HexUtils.toBytes(aid); + } catch (Exception e) { + } + if (aidBinary == null) { + Utils.showSimpleMessageDialog(SettingsActivity.this, getString(R.string.ledger_check_unplugged_aid)); + } else { + _mbwManager.getLedgerManager().setUnpluggedAID(aid); + } + } + }); + b.setNegativeButton(R.string.cancel, null); + + aidEdit = new EditText(SettingsActivity.this); + aidEdit.setInputType(InputType.TYPE_CLASS_TEXT); + aidEdit.setText(_mbwManager.getLedgerManager().getUnpluggedAID()); + LinearLayout llDialog = new LinearLayout(SettingsActivity.this); + llDialog.setOrientation(LinearLayout.VERTICAL); + llDialog.setPadding(10, 10, 10, 10); + TextView tvInfo = new TextView(SettingsActivity.this); + tvInfo.setText(getString(R.string.ledger_unplugged_aid)); + llDialog.addView(tvInfo); + llDialog.addView(aidEdit); + b.setView(llDialog); + AlertDialog dialog = b.show(); + return true; + } + }; + private ListPreference _bitcoinDenomination; private Preference _localCurrency; private ListPreference _exchangeSource; @@ -462,7 +459,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { return true; } }); - + _ledgerDisableTee = (CheckBoxPreference) findPreference("ledgerDisableTee"); _ledgerSetUnpluggedAID = (Preference) findPreference("ledgerUnpluggedAID"); @@ -471,9 +468,10 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { _ledgerDisableTee.setChecked(_mbwManager.getLedgerManager().getDisableTEE()); _ledgerDisableTee.setOnPreferenceClickListener(onClickLedgerNotificationDisableTee); } else { - getPreferenceScreen().removePreference(findPreference("ledgerDisableTee")); + PreferenceCategory ledger = (PreferenceCategory) findPreference("ledger"); + ledger.removePreference(_ledgerDisableTee); } - + _ledgerSetUnpluggedAID.setOnPreferenceClickListener(onClickLedgerSetUnpluggedAID); applyLocalTraderEnablement(); @@ -510,7 +508,9 @@ private void setupLocalTraderSettings() { private void showOrHideLegacyBackup() { List accounts = _mbwManager.getWalletManager(false).getSpendingAccounts(); Preference legacyPref = findPreference("legacyBackup"); - if (legacyPref == null) return; // it was already removed, don't remove it again. + if (legacyPref == null) { + return; // it was already removed, don't remove it again. + } PreferenceCategory legacyCat = (PreferenceCategory) findPreference("legacy"); for (WalletAccount account : accounts) { @@ -621,16 +621,16 @@ private String getMinerFeeSummary() { _mbwManager.getMinerFee().getNBlocks()); } - private String getBlockExplorerTitle(){ + private String getBlockExplorerTitle() { return getResources().getString(R.string.block_explorer_title, _mbwManager._blockExplorerManager.getBlockExplorer().getTitle()); } - private String getBlockExplorerSummary(){ + + private String getBlockExplorerSummary() { return getResources().getString(R.string.block_explorer_summary, _mbwManager._blockExplorerManager.getBlockExplorer().getTitle()); } - @SuppressWarnings("deprecation") private void updateClearPin() { Preference clearPin = findPreference("clearPin"); @@ -655,7 +655,6 @@ public SubscribeToServerResponse() { super(new Handler()); } - @Override public void onLtTraderInfoFetched(final TraderInfo info, GetTraderInfo request) { pleaseWait.dismiss(); @@ -667,7 +666,7 @@ public void onClick(DialogInterface dialog, int which) { String email = emailEdit.getText().toString(); _ltManager.makeRequest(new SetNotificationMail(email)); - if ((info.notificationEmail==null || !info.notificationEmail.equals(email)) && !Strings.isNullOrEmpty(email)) { + if ((info.notificationEmail == null || !info.notificationEmail.equals(email)) && !Strings.isNullOrEmpty(email)) { Utils.showSimpleMessageDialog(SettingsActivity.this, getString(R.string.lt_email_please_verify_message)); } } @@ -681,7 +680,7 @@ protected void onTextChanged(CharSequence text, int start, int lengthBefore, int super.onTextChanged(text, start, lengthBefore, lengthAfter); if (okButton != null) { //setText is also set before the alert is finished constructing boolean validMail = Strings.isNullOrEmpty(text.toString()) || //allow empty email, this removes email notifications - Utils.isValidEmailAddress(text.toString()); + Utils.isValidEmailAddress(text.toString()); okButton.setEnabled(validMail); } } @@ -709,4 +708,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/AbstractAccountScanManager.java b/public/mbw/src/main/java/com/mycelium/wallet/activity/util/AbstractAccountScanManager.java index 4601761e0f..aea53e73c4 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/activity/util/AbstractAccountScanManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/activity/util/AbstractAccountScanManager.java @@ -41,10 +41,13 @@ import com.google.common.base.Optional; import com.mrd.bitlib.crypto.HdKeyNode; import com.mrd.bitlib.model.NetworkParameters; +import com.mrd.bitlib.model.hdpath.Bip44CoinType; +import com.mrd.bitlib.model.hdpath.HdKeyPath; import com.mycelium.wapi.wallet.AccountScanManager; import com.mycelium.wapi.wallet.WalletManager; import com.squareup.otto.Bus; +import java.util.ArrayList; import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.LinkedBlockingQueue; @@ -52,14 +55,15 @@ public abstract class AbstractAccountScanManager implements AccountScanManager { protected final Context context; final private NetworkParameters network; - private AsyncTask scanAsyncTask = null; - private final TreeMap foundAccounts = new TreeMap(); + private AsyncTask scanAsyncTask = null; + private final ArrayList foundAccounts = new ArrayList(); protected final Bus eventBus; protected final LinkedBlockingQueue> passphraseSyncQueue = new LinkedBlockingQueue>(1); protected final Handler mainThreadHandler; public volatile AccountStatus currentAccountState = AccountStatus.unknown; public volatile Status currentState = Status.unableToScan; + private volatile Optional nextUnusedAccount = Optional.absent(); public AbstractAccountScanManager(Context context, NetworkParameters network, Bus eventBus) { this.context = context; @@ -95,27 +99,45 @@ public FoundAccountStatus(HdKeyNodeWrapper account) { public void startBackgroundAccountScan(final AccountCallback scanningCallback) { if (currentAccountState == AccountStatus.scanning || currentAccountState == AccountStatus.done) { // currently scanning or have already all account - just post the events for all already known accounts - for (HdKeyNodeWrapper a : foundAccounts.values()) { + for (HdKeyNodeWrapper a : foundAccounts) { eventBus.post(new OnAccountFound(a)); } } else { // start a background task which iterates over all accounts and calls the callback // to check if there was activity on it - scanAsyncTask = new AsyncTask() { + scanAsyncTask = new AsyncTask() { @Override - protected Integer doInBackground(Void... voids) { + protected Void doInBackground(Void... voids) { publishProgress(new ScanStatus(AccountScanManager.Status.initializing, AccountStatus.unknown)); if (onBeforeScan()) { publishProgress(new ScanStatus(AccountScanManager.Status.readyToScan, AccountStatus.scanning)); } else { - return 0; + return null; } // scan through the accounts, to find the first unused one - int accountIndex = 0; + Optional lastScannedPath = Optional.absent(); + Optional lastAccountPubKeyNode = Optional.absent(); + boolean wasUsed = false; do { HdKeyNode rootNode; - Optional accountPubKeyNode = AbstractAccountScanManager.this.getAccountPubKeyNode(accountIndex); + Optional accountPathToScan = + AbstractAccountScanManager.this.getAccountPathToScan(lastScannedPath, wasUsed); + + // we have scanned all accounts - get out of here... + if (!accountPathToScan.isPresent()) { + // remember the last xPub key as the next-unused one + nextUnusedAccount = lastAccountPubKeyNode; + break; + } + + Optional accountPubKeyNode = + AbstractAccountScanManager.this.getAccountPubKeyNode(accountPathToScan.get()); + lastAccountPubKeyNode = accountPubKeyNode; + + + + // unable to retrieve the account (eg. device unplugged) - cancel scan if (!accountPubKeyNode.isPresent()) { publishProgress(new ScanStatus(AccountScanManager.Status.initializing, AccountStatus.unknown)); break; @@ -124,20 +146,22 @@ protected Integer doInBackground(Void... voids) { rootNode = accountPubKeyNode.get(); // leave accountID empty for now - set it later if it is a already used account - HdKeyNodeWrapper acc = new HdKeyNodeWrapper(accountIndex, rootNode, null); + HdKeyNodeWrapper acc = new HdKeyNodeWrapper(accountPathToScan.get(), rootNode, null); UUID newAccount = scanningCallback.checkForTransactions(acc); + lastScannedPath = Optional.of(accountPathToScan.get()); + if (newAccount != null) { - HdKeyNodeWrapper foundAccount = new HdKeyNodeWrapper(accountIndex, rootNode, newAccount); + HdKeyNodeWrapper foundAccount = + new HdKeyNodeWrapper(accountPathToScan.get(), rootNode, newAccount); + publishProgress(new FoundAccountStatus(foundAccount)); + wasUsed = true; } else { - publishProgress(new ScanStatus(AccountScanManager.Status.initializing, AccountStatus.unknown)); - break; + wasUsed = false; } - accountIndex++; } while (!isCancelled()); publishProgress(new ScanStatus(AccountScanManager.Status.readyToScan, AccountStatus.done)); - - return accountIndex; + return null; } @@ -155,20 +179,11 @@ protected void onProgressUpdate(ScanStatus... stateInfo) { if (si instanceof FoundAccountStatus) { HdKeyNodeWrapper foundAccount = ((FoundAccountStatus) si).account; eventBus.post(new OnAccountFound(foundAccount)); - foundAccounts.put(foundAccount.accountIndex, foundAccount); + foundAccounts.add(foundAccount); } } } - @Override - protected void onCancelled() { - super.onCancelled(); - } - - @Override - protected void onCancelled(Integer integer) { - super.onCancelled(integer); - } }; scanAsyncTask.execute(); @@ -194,6 +209,12 @@ public void stopBackgroundAccountScan() { } } + @Override + public Optional getNextUnusedAccount(){ + return nextUnusedAccount; + } + + @Override public void forgetAccounts() { if (currentAccountState == AccountStatus.scanning) { @@ -242,4 +263,23 @@ public void setPassphrase(String passphrase) { abstract public UUID createOnTheFlyAccount(HdKeyNode accountRoot, WalletManager walletManager, int accountIndex); + // returns the next Bip44 account based on the last scanned account + @Override + public Optional getAccountPathToScan(Optional lastPath, boolean wasUsed){ + Bip44CoinType bip44CoinType = HdKeyPath.BIP44.getBip44CoinType(getNetwork()); + + // this is the first call - no lastPath given + if (!lastPath.isPresent()) { + return Optional.of(bip44CoinType.getAccount(0)); + } + + // otherwise use the next bip44 account, as long as the last one had activity on it + HdKeyPath last = lastPath.get(); + if (wasUsed){ + return Optional.of(bip44CoinType.getAccount(last.getLastIndex() + 1)); + } + + // if we are already at the bip44 branch and the last account had no activity, then we are done + return Optional.absent(); + } } 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 184501d0a1..8cc30b6c93 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 @@ -39,6 +39,7 @@ import com.mrd.bitlib.crypto.Bip39; import com.mrd.bitlib.crypto.HdKeyNode; import com.mrd.bitlib.model.*; +import com.mrd.bitlib.model.hdpath.Bip44CoinType; import com.mrd.bitlib.model.hdpath.HdKeyPath; import com.mycelium.wapi.wallet.WalletManager; import com.squareup.otto.Bus; @@ -48,6 +49,7 @@ public class MasterseedScanManager extends AbstractAccountScanManager { private Bip39.MasterSeed masterSeed; private final String[] words; + private final String password; private HdKeyNode accountsRoot = null; @@ -55,23 +57,29 @@ public MasterseedScanManager(Context context, NetworkParameters network, Bip39.M super(context, network, eventBus); this.masterSeed = masterSeed; this.words = null; + this.password = null; } - public MasterseedScanManager(Context context, NetworkParameters network, String[] words, Bus eventBus){ + public MasterseedScanManager(Context context, NetworkParameters network, String[] words, String password, Bus eventBus){ super(context, network, eventBus); this.words = words; + this.password = password; } - @Override protected boolean onBeforeScan() { - // if we only have a wordlist, query the user for a passphrase if (masterSeed == null){ - Optional passphrase = waitForPassphrase(); + // use the provided passphrase, if it is nor null, ... + Optional passphrase = Optional.fromNullable(password);; + if (!passphrase.isPresent()) { + // .. otherwise query the user for a passphrase + passphrase = waitForPassphrase(); + } if (passphrase.isPresent()){ this.masterSeed = Bip39.generateSeedFromWordList(words, passphrase.get()); return true; }else{ + return false; } } @@ -80,14 +88,11 @@ protected boolean onBeforeScan() { } @Override - public Optional getAccountPubKeyNode(int accountIndex){ + public Optional getAccountPubKeyNode(HdKeyPath keyPath){ // Generate the root private key + //todo: caching of intermediary path sections HdKeyNode root = HdKeyNode.fromSeed(masterSeed.getBip32Seed()); - if (accountsRoot == null) { - accountsRoot = root.createChildNode(HdKeyPath.BIP44.getBip44CoinType(getNetwork())); - } - HdKeyNode childNode = accountsRoot.createHardenedChildNode(accountIndex); - return Optional.of(childNode); + return Optional.of(root.createChildNode(keyPath)); } @Override @@ -102,4 +107,23 @@ public UUID createOnTheFlyAccount(HdKeyNode accountRoot, WalletManager walletMan return account; } + @Override + public Optional getAccountPathToScan(Optional lastPath, boolean wasUsed) { + // this is the first call - no lastPath given + if (!lastPath.isPresent()) { + return Optional.of(HdKeyPath.BIP32_ROOT); + } + + // if the lastPath was the Bip32, we dont care if it wasUsed - always scan the first Bip44 account + Bip44CoinType bip44CoinType = HdKeyPath.BIP44.getBip44CoinType(getNetwork()); + HdKeyPath last = lastPath.get(); + if (last.equals(HdKeyPath.BIP32_ROOT)){ + return Optional.of(bip44CoinType.getAccount(0)); + } + + // otherwise just return the normal bip44 accounts + return super.getAccountPathToScan(lastPath, wasUsed); + } + } + 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 4e81319c69..bef86cfe6b 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 @@ -113,18 +113,30 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, cs = ((CashilaPaymentsActivity) getActivity()).getCashilaService(); eventBus = mbw.getEventBus(); - getRecentRecipientsList(); + int selItem = 0; + if (savedInstanceState != null){ + selItem = savedInstanceState.getInt("spRecipient", 0); + } + + getRecentRecipientsList(selItem); bcd = (BcdCodedSepaData) getActivity().getIntent().getSerializableExtra("bcd"); if (bcd != null) { initFromBcd(bcd); } + return rootView; } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("spRecipient", spRecipients.getSelectedItemPosition()); + } + - private void getRecentRecipientsList() { + private void getRecentRecipientsList(final int selItem) { final ProgressDialog progressDialog = ProgressDialog.show(this.getActivity(), getResources().getString(R.string.cashila), getResources().getString(R.string.cashila_fetching), true); // ensure login and get the list of all recipients @@ -156,13 +168,14 @@ public void run() { } else { recipientArrayAdapter = new RecipientArrayAdapter(getActivity(), listCashilaResponse); spRecipients.setAdapter(recipientArrayAdapter); + spRecipients.setSelection(selItem); } } }); } public void refresh() { - getRecentRecipientsList(); + getRecentRecipientsList(spRecipients.getSelectedItemPosition()); } private CreateBillPay getBillPayFromBcdEntry(BcdCodedSepaData bcd) { diff --git a/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/api/CashilaService.java b/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/api/CashilaService.java index e1e2b1cb39..6c7a2dae54 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/api/CashilaService.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/external/cashila/api/CashilaService.java @@ -86,6 +86,7 @@ public class CashilaService { public static final String DEEP_LINK_DASHBOARD = "dashboard"; public static final String DEEP_LINK_ADD_RECIPIENT = "recipients/add"; public static final String CASHILA_CERT = "sha1/cl8wPAZF71fZyBWNmh5tvVV5UYM="; + public static final String CASHILA_CERT_STAGE = "sha1/xxFB1MhSNsKWMxub9YFki5Wm/XM="; private final String baseUrl; private final Bus eventBus; @@ -106,7 +107,7 @@ public CashilaService(String baseUrl, String apiVersion, Bus eventBus) { client.networkInterceptors().add(hmacInterceptor); CertificatePinner certPinner = new CertificatePinner.Builder() .add("cashila.com", CASHILA_CERT) - .add("cashila-staging.com", CASHILA_CERT) + .add("cashila-staging.com", CASHILA_CERT_STAGE) .build(); client.setCertificatePinner(certPinner); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/ledger/LedgerManager.java b/public/mbw/src/main/java/com/mycelium/wallet/ledger/LedgerManager.java index 84e9553944..1fd1c95aaa 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/ledger/LedgerManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/ledger/LedgerManager.java @@ -58,6 +58,7 @@ import com.mrd.bitlib.crypto.PublicKey; import com.mrd.bitlib.model.*; import com.mrd.bitlib.model.TransactionOutput.TransactionOutputParsingException; +import com.mrd.bitlib.model.hdpath.HdKeyPath; import com.mrd.bitlib.util.ByteReader; import com.mrd.bitlib.util.ByteWriter; import com.mrd.bitlib.util.CoinUtil; @@ -448,9 +449,11 @@ public UUID createOnTheFlyAccount(HdKeyNode accountRoot, } @Override - public Optional getAccountPubKeyNode(int accountIndex) { + public Optional getAccountPubKeyNode(HdKeyPath keyPath) { boolean isTEE = isTee(); - String keyPath = "44'/" + getNetwork().getBip44CoinType().getLastIndex() + "'/" + accountIndex + "'"; + // ledger needs it in the format "/44'/0'/0'" - our default toString format + // is with leading "m/" -> replace the "m" away + String keyPathString = keyPath.toString().replace("m/", ""); if (isTEE) { // Check if the PIN has been terminated - in this case, reinitialize try { @@ -475,7 +478,7 @@ public Optional getAccountPubKeyNode(int accountIndex) { try { BTChipDongle.BTChipPublicKey publicKey; try { - publicKey = dongle.getWalletPublicKey(keyPath); + publicKey = dongle.getWalletPublicKey(keyPathString); } catch (BTChipException e) { if (isTEE && (e.getSW() == SW_CONDITIONS_NOT_SATISFIED)) { LedgerTransportTEEProxy proxy = (LedgerTransportTEEProxy) getTransport().getTransport(); @@ -504,7 +507,7 @@ public Optional getAccountPubKeyNode(int accountIndex) { } catch (Exception ignore) { } } - publicKey = dongle.getWalletPublicKey(keyPath); + publicKey = dongle.getWalletPublicKey(keyPathString); } else if (e.getSW() == SW_PIN_NEEDED) { //if (dongle.hasScreenSupport()) { if (isTEE) { @@ -524,7 +527,7 @@ public Optional getAccountPubKeyNode(int accountIndex) { } catch (Exception ignore) { } } - publicKey = dongle.getWalletPublicKey(keyPath); + publicKey = dongle.getWalletPublicKey(keyPathString); } else { mainThreadHandler.post(new Runnable() { @Override @@ -544,7 +547,7 @@ public void run() { initialize(); Log.d(LOG_TAG, "Reinitialize transport done"); dongle.verifyPin(pin.getBytes()); - publicKey = dongle.getWalletPublicKey(keyPath); + publicKey = dongle.getWalletPublicKey(keyPathString); } catch (BTChipException e1) { if ((e1.getSW() & 0xfff0) == SW_INVALID_PIN) { postErrorMessage("Invalid PIN - " + (e1.getSW() - SW_INVALID_PIN) + " attempts remaining"); @@ -562,7 +565,7 @@ public void run() { } } PublicKey pubKey = new PublicKey(KeyUtils.compressPublicKey(publicKey.getPublicKey())); - HdKeyNode accountRootNode = new HdKeyNode(pubKey, publicKey.getChainCode(), 3, 0, accountIndex); + HdKeyNode accountRootNode = new HdKeyNode(pubKey, publicKey.getChainCode(), 3, 0, keyPath.getLastIndex()); return Optional.of(accountRootNode); } catch (Exception e) { Log.d(LOG_TAG, "Generic error", e); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/ledger/MyceliumKeyRecovery.java b/public/mbw/src/main/java/com/mycelium/wallet/ledger/MyceliumKeyRecovery.java index ff6da7aba3..2d93652da5 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/ledger/MyceliumKeyRecovery.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/ledger/MyceliumKeyRecovery.java @@ -48,17 +48,16 @@ // so the indirection via a signature and KeyRecovery is done public class MyceliumKeyRecovery implements BTChipKeyRecovery { - @Override - public byte[] recoverKey(int recId, byte[] signatureParam, byte[] hashValue) { - Signature signature = Signatures.decodeSignatureParameters(new ByteReader(signatureParam)); - Sha256Hash hash = new Sha256Hash(hashValue); - PublicKey key = SignedMessage.recoverFromSignature(recId, signature, hash, false); - if (key != null) { - return key.getPublicKeyBytes(); - } - else { - return null; - } - } - + @Override + public byte[] recoverKey(int recId, byte[] signatureParam, byte[] hashValue) { + Signature signature = Signatures.decodeSignatureParameters(new ByteReader(signatureParam)); + Sha256Hash hash = new Sha256Hash(hashValue); + PublicKey key = SignedMessage.recoverFromSignature(recId, signature, hash, false); + if (key != null) { + return key.getPublicKeyBytes(); + } else { + return null; + } + } + } diff --git a/public/mbw/src/main/java/com/mycelium/wallet/ledger/activity/LedgerAccountImportActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/ledger/activity/LedgerAccountImportActivity.java index 6ef818dc58..4f12463d2d 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/ledger/activity/LedgerAccountImportActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/ledger/activity/LedgerAccountImportActivity.java @@ -38,13 +38,12 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; import android.view.View; import android.widget.AdapterView; import android.widget.TextView; import com.google.common.base.Optional; import com.mrd.bitlib.crypto.HdKeyNode; +import com.mrd.bitlib.model.hdpath.HdKeyPath; import com.mycelium.wallet.*; import com.mycelium.wallet.activity.util.Pin; import com.mycelium.wallet.ledger.LedgerManager; @@ -54,7 +53,6 @@ import nordpol.android.TagDispatcher; import java.util.UUID; -import java.util.concurrent.LinkedBlockingQueue; public class LedgerAccountImportActivity extends LedgerAccountSelectorActivity { @@ -102,7 +100,10 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { MbwManager mbwManager = MbwManager.getInstance(LedgerAccountImportActivity.this); UUID acc = mbwManager.getWalletManager(false) - .createExternalSignatureAccount(item.xPub, (LedgerManager) masterseedScanManager, item.accountIndex); + .createExternalSignatureAccount( + item.xPub, + (LedgerManager) masterseedScanManager, + item.accountHdKeyPath.getLastIndex()); // Mark this account as backup warning ignored mbwManager.getMetadataStorage().setOtherAccountBackupState(acc, MetadataStorage.BackupState.IGNORED); @@ -144,13 +145,17 @@ public void run() { new Thread() { public void run() { - Optional nextAccount = masterseedScanManager.getAccountPubKeyNode(accounts.size()); + Optional nextAccount = masterseedScanManager.getNextUnusedAccount(); MbwManager mbwManager = MbwManager.getInstance(LedgerAccountImportActivity.this); if (nextAccount.isPresent()) { final UUID acc = mbwManager.getWalletManager(false) - .createExternalSignatureAccount(nextAccount.get(), (LedgerManager) masterseedScanManager, accounts.size()); + .createExternalSignatureAccount( + nextAccount.get(), + (LedgerManager) masterseedScanManager, + nextAccount.get().getIndex() + ); mbwManager.getMetadataStorage().setOtherAccountBackupState(acc, MetadataStorage.BackupState.IGNORED); 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 5efdc08d4b..22eaee5924 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 @@ -58,6 +58,7 @@ public class MetadataStorage extends GenericMetadataStorage { private static final MetadataKeyCategory PIN_RESET_BLOCKHEIGHT = new MetadataKeyCategory("pin", "reset_blockheight"); private static final MetadataKeyCategory PIN_BLOCKHEIGHT = new MetadataKeyCategory("pin", "blockheight"); public static final String EMAIL = "email"; + public static final String PAIRED_SERVICE_COINAPULT = "coinapult"; public MetadataStorage(Context context) { super(context); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/trezor/TrezorManager.java b/public/mbw/src/main/java/com/mycelium/wallet/trezor/TrezorManager.java index 57d7638208..c6dad2daa2 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/trezor/TrezorManager.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/trezor/TrezorManager.java @@ -44,6 +44,7 @@ import com.mrd.bitlib.crypto.HdKeyNode; import com.mrd.bitlib.crypto.PublicKey; import com.mrd.bitlib.model.*; +import com.mrd.bitlib.model.hdpath.HdKeyPath; import com.mrd.bitlib.util.ByteReader; import com.mrd.bitlib.util.ByteWriter; import com.mrd.bitlib.util.Sha256Hash; @@ -65,7 +66,7 @@ public class TrezorManager extends AbstractAccountScanManager implements ExternalSignatureProvider { private static final int MOST_RECENT_VERSION_MAJOR = 1; private static final int MOST_RECENT_VERSION_MINOR = 3; - private static final int MOST_RECENT_VERSION_PATCH = 3; + private static final int MOST_RECENT_VERSION_PATCH = 4; protected final int PRIME_DERIVATION_FLAG = 0x80000000; private static final String DEFAULT_LABEL = "Trezor"; @@ -369,12 +370,9 @@ private TrezorType.OutputScriptType mapScriptType(ScriptOutput script) { } @Override - public Optional getAccountPubKeyNode(int accountIndex) { - //44'/0'/0'/0/1 + public Optional getAccountPubKeyNode(HdKeyPath keyPath) { TrezorMessage.GetPublicKey msgGetPubKey = TrezorMessage.GetPublicKey.newBuilder() - .addAddressN(44 | PRIME_DERIVATION_FLAG) - .addAddressN(getNetwork().getBip44CoinType().getLastIndex() | PRIME_DERIVATION_FLAG) - .addAddressN(accountIndex | PRIME_DERIVATION_FLAG) + .addAllAddressN(keyPath.getAddressN()) .build(); try { @@ -382,7 +380,12 @@ public Optional getAccountPubKeyNode(int accountIndex) { if (resp != null && resp instanceof TrezorMessage.PublicKey) { TrezorMessage.PublicKey pubKeyNode = (TrezorMessage.PublicKey) resp; PublicKey pubKey = new PublicKey(pubKeyNode.getNode().getPublicKey().toByteArray()); - HdKeyNode accountRootNode = new HdKeyNode(pubKey, pubKeyNode.getNode().getChainCode().toByteArray(), 3, 0, accountIndex); + HdKeyNode accountRootNode = new HdKeyNode( + pubKey, + pubKeyNode.getNode().getChainCode().toByteArray(), + 3, 0, + keyPath.getLastIndex() + ); return Optional.of(accountRootNode); } else { return Optional.absent(); diff --git a/public/mbw/src/main/java/com/mycelium/wallet/trezor/activity/InstantTrezorActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/trezor/activity/InstantTrezorActivity.java index 9db3cd94b8..66ce4ba4fa 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/trezor/activity/InstantTrezorActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/trezor/activity/InstantTrezorActivity.java @@ -38,13 +38,8 @@ import android.content.Intent; import android.view.View; import android.widget.AdapterView; -import com.mycelium.wallet.LedgerPinDialog; -import com.mycelium.wallet.PinDialog; import com.mycelium.wallet.R; -import com.mycelium.wallet.TrezorPinDialog; -import com.mycelium.wallet.activity.send.SendMainActivity; -import com.mycelium.wallet.activity.util.Pin; -import com.mycelium.wallet.ledger.LedgerManager; +import com.mycelium.wallet.activity.send.SendInitializationActivity; import com.mycelium.wallet.trezor.TrezorManager; import com.mycelium.wapi.wallet.AccountScanManager; import com.squareup.otto.Subscribe; @@ -67,7 +62,7 @@ protected AdapterView.OnItemClickListener accountClickListener() { @Override public void onItemClick(AdapterView adapterView, View view, int i, long l) { HdAccountWrapper item = (HdAccountWrapper) adapterView.getItemAtPosition(i); - Intent intent = SendMainActivity.getIntent(InstantTrezorActivity.this, item.id, true); + Intent intent = SendInitializationActivity.getIntent(InstantTrezorActivity.this, item.id, true); InstantTrezorActivity.this.startActivityForResult(intent, REQUEST_SEND); } }; diff --git a/public/mbw/src/main/java/com/mycelium/wallet/trezor/activity/TrezorAccountImportActivity.java b/public/mbw/src/main/java/com/mycelium/wallet/trezor/activity/TrezorAccountImportActivity.java index f253a693df..b2a0007090 100644 --- a/public/mbw/src/main/java/com/mycelium/wallet/trezor/activity/TrezorAccountImportActivity.java +++ b/public/mbw/src/main/java/com/mycelium/wallet/trezor/activity/TrezorAccountImportActivity.java @@ -41,6 +41,7 @@ import android.widget.TextView; import com.google.common.base.Optional; import com.mrd.bitlib.crypto.HdKeyNode; +import com.mrd.bitlib.model.hdpath.HdKeyPath; import com.mycelium.wallet.MbwManager; import com.mycelium.wallet.R; import com.mycelium.wallet.Utils; @@ -69,7 +70,11 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { MbwManager mbwManager = MbwManager.getInstance(TrezorAccountImportActivity.this); UUID acc = mbwManager.getWalletManager(false) - .createExternalSignatureAccount(item.xPub, (TrezorManager) masterseedScanManager, item.accountIndex); + .createExternalSignatureAccount( + item.xPub, + (TrezorManager) masterseedScanManager, + item.accountHdKeyPath.getLastIndex() + ); // Mark this account as backup warning ignored mbwManager.getMetadataStorage().setOtherAccountBackupState(acc, MetadataStorage.BackupState.IGNORED); @@ -106,13 +111,17 @@ public void onClick(View view) { @Override public void run() { - Optional nextAccount = masterseedScanManager.getAccountPubKeyNode(accounts.size()); + Optional nextAccount = masterseedScanManager.getNextUnusedAccount(); MbwManager mbwManager = MbwManager.getInstance(TrezorAccountImportActivity.this); if (nextAccount.isPresent()) { UUID acc = mbwManager.getWalletManager(false) - .createExternalSignatureAccount(nextAccount.get(), (TrezorManager) masterseedScanManager, accounts.size()); + .createExternalSignatureAccount( + nextAccount.get(), + (TrezorManager) masterseedScanManager, + nextAccount.get().getIndex() + ); mbwManager.getMetadataStorage().setOtherAccountBackupState(acc, MetadataStorage.BackupState.IGNORED); diff --git a/public/mbw/src/main/res/drawable-hdpi/holo_dark_ic_action_new_usd_account.png b/public/mbw/src/main/res/drawable-hdpi/holo_dark_ic_action_new_usd_account.png new file mode 100644 index 0000000000000000000000000000000000000000..65e2bd3f852ed29b8bbbcb35a68e469e1b5ff7b6 GIT binary patch literal 1023 zcmViIak!zO5AV55j$6cjvYL=ctuBZ3CSPeeor3h{$@h@XSxVooal zz*`gqAs#{^UIbB$vVwvdqY#aF@F2PgF&mSR>}RH@%gY+p&1@WZ-JJ>9g1+?hr(RcA z)vF$WX)c)S0z~v?Vll+3LIYqk&H|&Oqb0ho*C67009b;Eb5g7^003`=5D#Qoe(5;? zmSycj#G{B9aB#I=$6~Q2UDqS7U!k_PHYS8P&lo%7MERsYtn&GMx5viD=D2PFA;d96 zJe;MIzhI1=bj<>ZL}CE|)MtG{h-*0KRjvV06lD)0N;W6^igSK2%|{xEC^l&m#%kU4 zfz>w3KW}euKcOf}Z)(3R%ON7VVY5b+Yd#=^2qB_Bwa+xoI#pG_*d?;@@$oXB&({h7 zy#UZFg!p6_Mz7!R|B(*>ayDb4(da;?!O5_>)k~RpIK3kf2yFC9CUyeggH2>P%{gB? zEdUVF6B_`2Ns_J$A+C8IKzDcdV=o*k}&10bTOHuQl> zl5{bVNG!@|R$1QdY+A=y^u1wc3)envz;Z9vRb6y+A@eA6Gidw4itS=Ipn*lSZ5 zJ+j%NA=$D9@Yg(GS=KJb*tr~YMGs@Feu^B@f`~1738Nhy9rx|7(myCaW*A13YjnD< zu1@s#_cswyC=2CBEXz8sY1%h8D>A68tV}dFH=hzhTp^-ShsuZI@%Vm4Q9gQtPXCsn zp&`Arv~)8fRs+CNL@Y~D`a1x;=l@Dn~>U0J8M~`9COF@c&|Ys|Shz tiU6jepqrbg)B$~n7>1%0r6@B`egXJejR#yE*`NRb002ovPDHLkV1hunu)F{O literal 0 HcmV?d00001 diff --git a/public/mbw/src/main/res/layout/backup_verification_warning_dialog.xml b/public/mbw/src/main/res/layout/backup_verification_warning_dialog.xml index ac4731b287..2053276833 100644 --- a/public/mbw/src/main/res/layout/backup_verification_warning_dialog.xml +++ b/public/mbw/src/main/res/layout/backup_verification_warning_dialog.xml @@ -47,7 +47,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:layout_marginBottom="5dp" android:layout_marginTop="5dp" android:paddingLeft="20dp" android:paddingRight="20dp" @@ -56,7 +55,7 @@ + android:layout_height="10dp" > diff --git a/public/mbw/src/main/res/layout/balance_master_fragment.xml b/public/mbw/src/main/res/layout/balance_master_fragment.xml index 03814eea2c..785dd98ae4 100644 --- a/public/mbw/src/main/res/layout/balance_master_fragment.xml +++ b/public/mbw/src/main/res/layout/balance_master_fragment.xml @@ -23,27 +23,22 @@ android:id="@+id/phFragmentBalance" android:layout_width="fill_parent" android:layout_height="wrap_content" + android:paddingBottom="5dp" tools:layout="@layout/main_balance_view"/> - - - - diff --git a/public/mbw/src/main/res/layout/instant_wallet_activity.xml b/public/mbw/src/main/res/layout/instant_wallet_activity.xml index 5b07dc9617..09dab33662 100644 --- a/public/mbw/src/main/res/layout/instant_wallet_activity.xml +++ b/public/mbw/src/main/res/layout/instant_wallet_activity.xml @@ -1,14 +1,16 @@ + android:layout_height="fill_parent"> + android:orientation="vertical" + android:padding="10dp" + > + android:textSize="24sp"/> + android:textSize="15sp"/> + android:textSize="20sp"/> @@ -51,30 +51,41 @@ android:paddingRight="10dp" android:layout_marginBottom="5dp" android:text="@string/qr_code" - android:textSize="20sp" /> + android:textSize="20sp"/> -