1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-03 08:23:04 +02:00

Compare commits

...

53 Commits

Author SHA1 Message Date
m2049r
b978396a38 fix versionCode 2020-03-08 20:46:23 +01:00
m2049r
c6aa04e986 update heights & fix date parsing bug (#654) 2020-03-08 20:33:34 +01:00
m2049r
6c17b8bd87 update gradle 2020-02-29 10:43:13 +01:00
m2049r
cac32f660c v1.12.11 (monero v0.15.0.1) 2019-11-25 23:30:28 +01:00
m2049r
8e70004bf2 v1.12.10 2019-11-24 17:17:54 +01:00
m2049r
c3a466c392 remove payment id everywhere (#646) 2019-11-24 17:17:25 +01:00
m2049r
e076c19e3e v1.12.9 2019-11-21 10:42:45 +01:00
m2049r
35b717756d Fix amount bugs (#645)
* fix rounding error on send

* fix check funds bug
2019-11-21 10:42:16 +01:00
m2049r
c14486306e v1.12.8 2019-11-20 07:34:25 +01:00
m2049r
c2ef25c377 change error message to 16 for payment ids (#643) 2019-11-20 07:33:29 +01:00
m2049r
b7164ef200 fix XMR missing in open wallet (#642) 2019-11-20 07:33:07 +01:00
m2049r
f94a366d51 v1.12.7 2019-11-19 22:57:43 +01:00
m2049r
286a04b5ef add XMR to receive spinners (again) (#640) 2019-11-19 22:56:28 +01:00
m2049r
1209295a8c v1.12.6 2019-11-18 12:01:49 +01:00
m2049r
037b019d4d xmrto payment through subaddress (#639)
* use subaddress for xmrto only

* fix exchange rate
2019-11-18 12:00:39 +01:00
m2049r
7a1d788f2a UI tweaks (#638)
* fix amount entry field

* password entry on own line

* remove errors when typing; field spacing
2019-11-17 10:31:19 +01:00
m2049r
87d9a8cd95 use kraken for EURXMR exchange rate (#637)
combine with ECB rates for other fiat conversions
2019-11-17 00:42:57 +01:00
m2049r
f637d7f617 update block heights (#636) 2019-11-10 23:54:40 +01:00
m2049r
a4b9a7c6fb fix spend amount & new version (#635) 2019-11-10 15:23:33 +01:00
m2049r
9f01155cb7 v1.12.1 2019-11-10 12:14:52 +01:00
m2049r
08e8a48138 scan only for 0.15 nodes (#634) 2019-11-10 12:13:06 +01:00
m2049r
551c3b9fb6 scan only for 0.15 nodes (#633) 2019-11-10 12:11:01 +01:00
m2049r
2258cb7096 v1.12.0 (#632) 2019-11-10 11:08:19 +01:00
m2049r
6d61841cf3 textfields are filled (#631) 2019-11-10 11:06:34 +01:00
m2049r
c65508d288 upgrade to monero v0.15 (#630) 2019-11-09 22:45:54 +01:00
m2049r
2c3f582672 paste bip70 (#621) 2019-09-22 10:09:18 +02:00
m2049r
f46ba75771 update gradle version (#614) 2019-08-16 19:07:12 +02:00
m2049r
0ce5f2b6ca create ExchangeEditText & use without numpad (#613) 2019-08-04 13:19:42 +02:00
m2049r
110057c294 bump version to 1.11.13 2019-07-14 13:18:27 +02:00
m2049r
7553d3c5f4 Merge pull request #611 from m2049r/fix_heightfix
reduce restore height some more for new wallets
2019-07-14 12:53:24 +02:00
m2049r
317976b34a decrease restore height on new wallet 2019-07-14 12:52:43 +02:00
m2049r
6ad423567f Merge pull request #610 from m2049r/feature_showheight
show restore height
2019-07-14 12:52:21 +02:00
m2049r
d497158856 show restore height 2019-07-14 12:35:00 +02:00
m2049r
f4cada5fa1 Merge pull request #608 from m2049r/feature_nanox
Support for Nano X
2019-07-13 18:56:23 +02:00
m2049r
352f0ad09c update height for 2019-07-01 (#609) 2019-07-13 18:55:35 +02:00
m2049r
ff1a9c1570 verify monero app is running on nano 2019-07-13 18:44:56 +02:00
m2049r
fa811a39a2 accept Nano X over USB 2019-07-13 13:00:24 +02:00
m2049r
cf5018be33 kick 'Hintergrunddienst' (#607) 2019-06-23 21:26:29 +02:00
m2049r
8ec027f9d4 bump version 2019-06-21 08:47:22 +02:00
m2049r
f28428e677 update june restore height (#606) 2019-06-21 08:45:09 +02:00
m2049r
294084bec5 double size of node bookmark icon (#605) 2019-06-21 08:17:08 +02:00
m2049r
4349907627 setNode blocks => call it async (#604) 2019-06-18 08:49:51 +02:00
m2049r
f7cef24a83 fix NPE (#603) 2019-06-18 08:48:28 +02:00
m2049r
2774f99b15 bump version v1.11.10 2019-06-16 23:39:24 +02:00
m2049r
bc630fc445 Refactor secureflag (#600)
* one place to decide if screenshots enabled

* allow screenshots on alpha or debug
2019-06-16 23:28:52 +02:00
m2049r
895cf16d33 don't setDaemon when null (#601) 2019-06-16 23:28:34 +02:00
m2049r
7f1796b12e bump version v1.11.9 2019-06-16 21:49:11 +02:00
erciccione
abe5c8afab readme: update contact info of the localization workgroup (#593) 2019-06-16 21:48:32 +02:00
WiserB
47f79b5269 Serbian translation of strings.xml (sr - made by WiserB) (#595)
helps.xml and about.xml included but not translated
2019-06-16 21:33:52 +02:00
erciccione
c9c07eaa15 Catalan translation (by ambystomamex) (#596) 2019-06-16 21:28:42 +02:00
m2049r
a490e3af0c better status & error message handling (#599) 2019-06-16 21:22:56 +02:00
m2049r
64d5b3bdea tweaks for monero v0.14.1.0 (#598) 2019-06-16 21:13:01 +02:00
m2049r
bf91eaf22f deal with not finding any text on clipboard (#594) 2019-05-24 23:44:54 +02:00
109 changed files with 4002 additions and 2283 deletions

File diff suppressed because one or more lines are too long

View File

@@ -139,6 +139,10 @@ add_library(device STATIC IMPORTED)
set_target_properties(device PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libdevice.a)
add_library(device_trezor STATIC IMPORTED)
set_target_properties(device_trezor PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libdevice_trezor.a)
add_library(multisig STATIC IMPORTED)
set_target_properties(multisig PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libmultisig.a)
@@ -147,6 +151,22 @@ add_library(version STATIC IMPORTED)
set_target_properties(version PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libversion.a)
add_library(net STATIC IMPORTED)
set_target_properties(net PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libnet.a)
add_library(hardforks STATIC IMPORTED)
set_target_properties(hardforks PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libhardforks.a)
add_library(randomx STATIC IMPORTED)
set_target_properties(randomx PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/librandomx.a)
add_library(rpc_base STATIC IMPORTED)
set_target_properties(rpc_base PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/librpc_base.a)
#############
# System
#############
@@ -166,6 +186,7 @@ target_link_libraries( monerujo
mnemonics
ringct
ringct_basic
net
common
cncrypto
blockchain_db
@@ -176,8 +197,12 @@ target_link_libraries( monerujo
blocks
checkpoints
device
device_trezor
multisig
version
randomx
hardforks
rpc_base
boost_chrono
boost_date_time

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 28
versionCode 177
versionName "1.11.7 'Chernushka'"
versionCode 202
versionName "1.12.12 'Caerbannog'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {

View File

@@ -1,88 +0,0 @@
// Copyright (c) 2017-2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
#if defined(HAVE_MONERUJO)
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief LedgerFind - find Ledger Device and return it's name
* @param buffer - buffer for name of found device
* @param len - length of buffer
* @return 0 - success
* -1 - no device connected / found
* -2 - JVM not found
*/
int LedgerFind(char *buffer, size_t len);
/**
* @brief LedgerExchange - exchange data with Ledger Device
* @param command - buffer for data to send
* @param cmd_len - length of send to send
* @param response - buffer for received data
* @param max_resp_len - size of receive buffer
*
* @return length of received data in response or -1 if error
*/
int LedgerExchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len);
#ifdef __cplusplus
}
#endif
#include "device_io.hpp"
#pragma once
namespace hw {
namespace io {
class device_io_monerujo: device_io {
public:
device_io_monerujo() {};
~device_io_monerujo() {};
void init() {};
void release() {};
void connect(void *params) {};
void disconnect() {};
bool connected() const {return true;}; // monerujo is always connected before it gets here
// returns number of bytes read or -1 on error
int exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len) {
return LedgerExchange(command, cmd_len, response, max_resp_len);
}
};
};
};
#endif //#if defined(HAVE_MONERUJO)

View File

@@ -39,6 +39,7 @@ static jclass class_WalletListener;
static jclass class_TransactionInfo;
static jclass class_Transfer;
static jclass class_Ledger;
static jclass class_WalletStatus;
std::mutex _listenerMutex;
@@ -61,6 +62,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
jenv->FindClass("com/m2049r/xmrwallet/model/WalletListener")));
class_Ledger = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger")));
class_WalletStatus = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/model/Wallet$Status")));
return JNI_VERSION_1_6;
}
#ifdef __cplusplus
@@ -581,10 +584,25 @@ Java_com_m2049r_xmrwallet_model_Wallet_getStatusJ(JNIEnv *env, jobject instance)
return wallet->status();
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getErrorString(JNIEnv *env, jobject instance) {
jobject newWalletStatusInstance(JNIEnv *env, int status, const std::string &errorString) {
jmethodID init = env->GetMethodID(class_WalletStatus, "<init>",
"(ILjava/lang/String;)V");
jstring _errorString = env->NewStringUTF(errorString.c_str());
jobject instance = env->NewObject(class_WalletStatus, init, status, _errorString);
env->DeleteLocalRef(_errorString);
return instance;
}
JNIEXPORT jobject JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_statusWithErrorString(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return env->NewStringUTF(wallet->errorString().c_str());
int status;
std::string errorString;
wallet->statusWithErrorString(status, errorString);
return newWalletStatusInstance(env, status, errorString);
}
JNIEXPORT jboolean JNICALL
@@ -1292,7 +1310,6 @@ Java_com_m2049r_xmrwallet_model_PendingTransaction_getFirstTxIdJ(JNIEnv *env, jo
return nullptr;
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_PendingTransaction_getTxCount(JNIEnv *env, jobject instance) {
Bitmonero::PendingTransaction *tx = getHandle<Bitmonero::PendingTransaction>(env, instance);
@@ -1378,12 +1395,15 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jclass cl
Bitmonero::WalletManagerFactory::setLogLevel(level);
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_moneroVersion(JNIEnv *env, jclass clazz) {
return env->NewStringUTF(MONERO_VERSION);
}
//
// Ledger Stuff
//
#include "device_io_monerujo.hpp"
/**
* @brief LedgerExchange - exchange data with Ledger Device
* @param command - buffer for data to send
@@ -1417,7 +1437,7 @@ int LedgerExchange(
return -1;
}
jsize len = jenv->GetArrayLength(dataRecv);
LOGD("LedgerExchange SCARD_S_SUCCESS %ld/%d", cmd_len, len);
LOGD("LedgerExchange SCARD_S_SUCCESS %u/%d", cmd_len, len);
if (len <= max_resp_len) {
jenv->GetByteArrayRegion(dataRecv, 0, len, (jbyte *) response);
jenv->DeleteLocalRef(dataRecv);

View File

@@ -54,6 +54,8 @@ extern "C"
{
#endif
extern const char* const MONERO_VERSION; // the actual monero core version
// from monero-core crypto/hash-ops.h - avoid #including monero code here
enum {
HASH_SIZE = 32,

View File

@@ -46,10 +46,14 @@ public class BTChipTransportAndroidHID implements BTChipTransport {
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
for (UsbDevice device : deviceList.values()) {
Timber.d("%04X:%04X %s, %s", device.getVendorId(), device.getProductId(), device.getManufacturerName(), device.getProductName());
if ((device.getVendorId() == VID) && (device.getProductId() == PID_HID)) {
if (device.getVendorId() == VID) {
final int deviceProductId = device.getProductId();
for (int pid : PID_HIDS) {
if (deviceProductId == pid)
return device;
}
}
}
return null;
}
@@ -74,7 +78,7 @@ public class BTChipTransportAndroidHID implements BTChipTransport {
}
private static final int VID = 0x2C97;
private static final int PID_HID = 0x0001;
private static final int[] PID_HIDS = {0x0001, 0x0004};
private UsbDeviceConnection connection;
private UsbInterface dongleInterface;

View File

@@ -79,6 +79,23 @@ public class GenerateFragment extends Fragment {
private String type = null;
private void clearErrorOnTextEntry(final TextInputLayout textInputLayout) {
textInputLayout.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
textInputLayout.setError(null);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -110,6 +127,23 @@ public class GenerateFragment extends Fragment {
}
}
});
clearErrorOnTextEntry(etWalletName);
etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
checkPassword();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
etWalletMnemonic.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
@@ -118,6 +152,8 @@ public class GenerateFragment extends Fragment {
}
}
});
clearErrorOnTextEntry(etWalletMnemonic);
etWalletAddress.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
@@ -126,6 +162,8 @@ public class GenerateFragment extends Fragment {
}
}
});
clearErrorOnTextEntry(etWalletAddress);
etWalletViewKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
@@ -134,6 +172,8 @@ public class GenerateFragment extends Fragment {
}
}
});
clearErrorOnTextEntry(etWalletViewKey);
etWalletSpendKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
@@ -142,6 +182,7 @@ public class GenerateFragment extends Fragment {
}
}
});
clearErrorOnTextEntry(etWalletSpendKey);
Helper.showKeyboard(getActivity());
@@ -310,21 +351,6 @@ public class GenerateFragment extends Fragment {
}
});
etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
checkPassword();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
etWalletName.requestFocus();
initZxcvbn();
@@ -412,7 +438,7 @@ public class GenerateFragment extends Fragment {
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
} catch (ParseException ex) {
}
if (height <= 0)
if ((height <= 0) && (restoreHeight.length() == 8))
try {
// is it a date without dashes?
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");
@@ -682,8 +708,7 @@ public class GenerateFragment extends Fragment {
}
});
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
if (Helper.preventScreenshot()) {
ledgerDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}

View File

@@ -55,6 +55,8 @@ import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.NumberFormat;
import timber.log.Timber;
public class GenerateReviewFragment extends Fragment {
@@ -72,6 +74,7 @@ public class GenerateReviewFragment extends Fragment {
private TextView tvWalletPassword;
private TextView tvWalletAddress;
private TextView tvWalletMnemonic;
private TextView tvWalletHeight;
private TextView tvWalletViewKey;
private TextView tvWalletSpendKey;
private ImageButton bCopyAddress;
@@ -99,6 +102,7 @@ public class GenerateReviewFragment extends Fragment {
tvWalletViewKey = view.findViewById(R.id.tvWalletViewKey);
tvWalletSpendKey = view.findViewById(R.id.tvWalletSpendKey);
tvWalletMnemonic = view.findViewById(R.id.tvWalletMnemonic);
tvWalletHeight = view.findViewById(R.id.tvWalletHeight);
bCopyAddress = view.findViewById(R.id.bCopyAddress);
bAdvancedInfo = view.findViewById(R.id.bAdvancedInfo);
llAdvancedInfo = view.findViewById(R.id.llAdvancedInfo);
@@ -188,11 +192,12 @@ public class GenerateReviewFragment extends Fragment {
private class AsyncShow extends AsyncTask<String, Void, Boolean> {
String name;
String address;
long height;
String seed;
String viewKey;
String spendKey;
boolean isWatchOnly;
Wallet.Status status;
Wallet.Status walletStatus;
boolean dialogOpened = false;
@@ -224,14 +229,15 @@ public class GenerateReviewFragment extends Fragment {
closeWallet = true;
}
name = wallet.getName();
status = wallet.getStatus();
if (status != Wallet.Status.Status_Ok) {
Timber.e(wallet.getErrorString());
walletStatus = wallet.getStatus();
if (!walletStatus.isOk()) {
Timber.e(walletStatus.getErrorString());
if (closeWallet) wallet.close();
return false;
}
address = wallet.getAddress();
height = wallet.getRestoreHeight();
seed = wallet.getSeed();
switch (wallet.getDeviceType()) {
case Device_Ledger:
@@ -264,6 +270,7 @@ public class GenerateReviewFragment extends Fragment {
llPassword.setVisibility(View.VISIBLE);
tvWalletPassword.setText(getPassword());
tvWalletAddress.setText(address);
tvWalletHeight.setText(NumberFormat.getInstance().format(height));
if (!seed.isEmpty()) {
llMnemonic.setVisibility(View.VISIBLE);
tvWalletMnemonic.setText(seed);
@@ -287,10 +294,11 @@ public class GenerateReviewFragment extends Fragment {
GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK);
} else {
// TODO show proper error message and/or end the fragment?
tvWalletAddress.setText(status.toString());
tvWalletMnemonic.setText(status.toString());
tvWalletViewKey.setText(status.toString());
tvWalletSpendKey.setText(status.toString());
tvWalletAddress.setText(walletStatus.toString());
tvWalletHeight.setText(walletStatus.toString());
tvWalletMnemonic.setText(walletStatus.toString());
tvWalletViewKey.setText(walletStatus.toString());
tvWalletSpendKey.setText(walletStatus.toString());
}
hideProgress();
}
@@ -414,12 +422,13 @@ public class GenerateReviewFragment extends Fragment {
}
boolean ok = false;
if (wallet.getStatus() == Wallet.Status.Status_Ok) {
Wallet.Status walletStatus = wallet.getStatus();
if (walletStatus.isOk()) {
wallet.setPassword(newPassword);
wallet.store();
ok = true;
} else {
Timber.e(wallet.getErrorString());
Timber.e(walletStatus.getErrorString());
}
if (closeWallet) wallet.close();
return ok;
@@ -621,8 +630,8 @@ public class GenerateReviewFragment extends Fragment {
return false;
}
});
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
if (Helper.preventScreenshot()) {
openDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}

View File

@@ -918,6 +918,16 @@ public class LoginActivity extends BaseActivity
}
boolean checkAndCloseWallet(Wallet aWallet) {
Wallet.Status walletStatus = aWallet.getStatus();
if (!walletStatus.isOk()) {
Timber.e(walletStatus.getErrorString());
toast(walletStatus.getErrorString());
}
aWallet.close();
return walletStatus.isOk();
}
@Override
public void onGenerate(final String name, final String password) {
createWallet(name, password,
@@ -930,17 +940,12 @@ public class LoginActivity extends BaseActivity
@Override
public boolean createWallet(File aFile, String password) {
NodeInfo currentNode = getNode();
// get it from the connected node if we have one, and go back ca. 4 days
final long restoreHeight =
(currentNode != null) ? currentNode.getHeight() - 20 : -1;
(currentNode != null) ? currentNode.getHeight() - 2000 : -1;
Wallet newWallet = WalletManager.getInstance()
.createWallet(aFile, password, MNEMONIC_LANGUAGE, restoreHeight);
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) {
Timber.e(newWallet.getErrorString());
toast(newWallet.getErrorString());
}
newWallet.close();
return success;
return checkAndCloseWallet(newWallet);
}
});
}
@@ -959,13 +964,7 @@ public class LoginActivity extends BaseActivity
public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance()
.recoveryWallet(aFile, password, seed, restoreHeight);
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) {
Timber.e(newWallet.getErrorString());
toast(newWallet.getErrorString());
}
newWallet.close();
return success;
return checkAndCloseWallet(newWallet);
}
});
}
@@ -985,13 +984,7 @@ public class LoginActivity extends BaseActivity
Wallet newWallet = WalletManager.getInstance()
.createWalletFromDevice(aFile, password,
restoreHeight, "Ledger");
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) {
Timber.e(newWallet.getErrorString());
toast(newWallet.getErrorString());
}
newWallet.close();
return success;
return checkAndCloseWallet(newWallet);
}
});
}
@@ -1012,13 +1005,7 @@ public class LoginActivity extends BaseActivity
Wallet newWallet = WalletManager.getInstance()
.createWalletWithKeys(aFile, password, MNEMONIC_LANGUAGE, restoreHeight,
address, viewKey, spendKey);
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) {
Timber.e(newWallet.getErrorString());
toast(newWallet.getErrorString());
}
newWallet.close();
return success;
return checkAndCloseWallet(newWallet);
}
});
}
@@ -1373,6 +1360,18 @@ public class LoginActivity extends BaseActivity
if (Ledger.ENABLED)
try {
Ledger.connect(usbManager, usbDevice);
if (!Ledger.check()) {
Ledger.disconnect();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_start_app, usbDevice.getProductName()),
Toast.LENGTH_SHORT)
.show();
}
});
} else {
registerDetachReceiver();
onLedgerAction();
runOnUiThread(new Runnable() {
@@ -1384,6 +1383,7 @@ public class LoginActivity extends BaseActivity
.show();
}
});
}
} catch (IOException ex) {
runOnUiThread(new Runnable() {
@Override

View File

@@ -439,18 +439,20 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
Collections.sort(nodesToTest, NodeInfo.BestNodeComparator);
NodeInfo bestNode = nodesToTest.get(0);
if (bestNode.isValid())
if (bestNode.isValid()) {
activityCallback.setNode(bestNode);
return bestNode;
else
} else {
activityCallback.setNode(null);
return null;
}
}
@Override
protected void onPostExecute(NodeInfo result) {
if (!isAdded()) return;
pbNode.setVisibility(View.INVISIBLE);
llNode.setVisibility(View.VISIBLE);
activityCallback.setNode(result);
if (result != null) {
Timber.d("found a good node %s", result.toString());
showNode(result);

View File

@@ -266,6 +266,7 @@ public class NodeFragment extends Fragment
seedList.add(new NodeInfo(new InetSocketAddress("198.74.231.92", 18080)));
seedList.add(new NodeInfo(new InetSocketAddress("195.154.123.123", 18080)));
seedList.add(new NodeInfo(new InetSocketAddress("212.83.172.165", 18080)));
seedList.add(new NodeInfo(new InetSocketAddress("192.110.160.146", 18080)));
d.seedPeers(seedList);
d.awaitTermination(NODES_TO_FIND);
}
@@ -506,8 +507,8 @@ public class NodeFragment extends Fragment
});
}
});
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
if (Helper.preventScreenshot()) {
editDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}

View File

@@ -21,6 +21,7 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.LocaleHelper;
import static android.view.WindowManager.LayoutParams;
@@ -30,8 +31,7 @@ public abstract class SecureActivity extends AppCompatActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
if (Helper.preventScreenshot()) {
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
}
}

View File

@@ -628,23 +628,22 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
}
@Override
public void onWalletStarted(final Wallet.ConnectionStatus connStatus) {
public void onWalletStarted(final Wallet.Status walletStatus) {
runOnUiThread(new Runnable() {
public void run() {
dismissProgressDialog();
switch (connStatus) {
case ConnectionStatus_Disconnected:
if (walletStatus == null) {
// guess what went wrong
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show();
break;
case ConnectionStatus_WrongVersion:
} else {
if (Wallet.ConnectionStatus.ConnectionStatus_WrongVersion == walletStatus.getConnectionStatus())
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_wrongversion), Toast.LENGTH_LONG).show();
break;
case ConnectionStatus_Connected:
break;
else if (!walletStatus.isOk())
Toast.makeText(WalletActivity.this, walletStatus.getErrorString(), Toast.LENGTH_LONG).show();
}
}
});
if (connStatus != Wallet.ConnectionStatus.ConnectionStatus_Connected) {
if ((walletStatus == null) || (Wallet.ConnectionStatus.ConnectionStatus_Connected != walletStatus.getConnectionStatus())) {
finish();
} else {
haveWallet = true;

View File

@@ -50,9 +50,8 @@ import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import timber.log.Timber;
@@ -114,9 +113,12 @@ public class WalletFragment extends Fragment
ivSynced = view.findViewById(R.id.ivSynced);
sCurrency = view.findViewById(R.id.sCurrency);
ArrayAdapter currencyAdapter = ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner_balance);
currencyAdapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item);
sCurrency.setAdapter(currencyAdapter);
List<String> currencies = new ArrayList<>();
currencies.add(Helper.BASE_CRYPTO);
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(getContext(), R.layout.item_spinner_balance, currencies);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sCurrency.setAdapter(spinnerAdapter);
bSend = view.findViewById(R.id.bSend);
bReceive = view.findViewById(R.id.bReceive);
@@ -220,7 +222,7 @@ public class WalletFragment extends Fragment
// at this point selection is XMR in case of error
String displayB;
double amountA = Helper.getDecimalAmount(unlockedBalance).doubleValue();
if (!Helper.CRYPTO.equals(balanceCurrency)) { // not XMR
if (!Helper.BASE_CRYPTO.equals(balanceCurrency)) { // not XMR
double amountB = amountA * balanceRate;
displayB = Helper.getFormattedAmount(amountB, false);
} else { // XMR
@@ -229,7 +231,7 @@ public class WalletFragment extends Fragment
showBalance(displayB);
}
String balanceCurrency = Helper.CRYPTO;
String balanceCurrency = Helper.BASE_CRYPTO;
double balanceRate = 1.0;
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
@@ -245,7 +247,7 @@ public class WalletFragment extends Fragment
Timber.d(currency);
if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) {
showExchanging();
exchangeApi.queryExchangeRate(Helper.CRYPTO, currency,
exchangeApi.queryExchangeRate(Helper.BASE_CRYPTO, currency,
new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
@@ -301,10 +303,10 @@ public class WalletFragment extends Fragment
public void exchange(final ExchangeRate exchangeRate) {
hideExchanging();
if (!Helper.CRYPTO.equals(exchangeRate.getBaseCurrency())) {
if (!Helper.BASE_CRYPTO.equals(exchangeRate.getBaseCurrency())) {
Timber.e("Not XMR");
sCurrency.setSelection(0, true);
balanceCurrency = Helper.CRYPTO;
balanceCurrency = Helper.BASE_CRYPTO;
balanceRate = 1.0;
} else {
int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency());

View File

@@ -59,42 +59,36 @@ public class BarcodeData {
final public Asset asset;
final public String address;
final public String addressName;
final public String paymentId;
final public String amount;
final public String description;
final public Security security;
final public String bip70;
public BarcodeData(Asset asset, String address) {
this(asset, address, null, null, null, null, Security.NORMAL);
this(asset, address, null, null, null, Security.NORMAL);
}
public BarcodeData(Asset asset, String address, String amount) {
this(asset, address, null, null, null, amount, Security.NORMAL);
this(asset, address, null, null, amount, Security.NORMAL);
}
public BarcodeData(Asset asset, String address, String amount, String description, Security security) {
this(asset, address, null, null, description, amount, security);
}
public BarcodeData(Asset asset, String address, String paymentId, String amount) {
this(asset, address, null, paymentId, null, amount, Security.NORMAL);
this(asset, address, null, description, amount, security);
}
public BarcodeData(Asset asset, String address, String paymentId, String description, String amount) {
this(asset, address, null, paymentId, description, amount, Security.NORMAL);
this(asset, address, null, description, amount, Security.NORMAL);
}
public BarcodeData(Asset asset, String address, String addressName, String paymentId, String description, String amount, Security security) {
this(asset, address, addressName, null, paymentId, description, amount, security);
public BarcodeData(Asset asset, String address, String addressName, String description, String amount, Security security) {
this(asset, address, addressName, null, description, amount, security);
}
public BarcodeData(Asset asset, String address, String addressName, String bip70, String paymentId, String description, String amount, Security security) {
public BarcodeData(Asset asset, String address, String addressName, String bip70, String description, String amount, Security security) {
this.asset = asset;
this.address = address;
this.bip70 = bip70;
this.addressName = addressName;
this.paymentId = paymentId;
this.description = description;
this.amount = amount;
this.security = security;
@@ -110,11 +104,6 @@ public class BarcodeData {
StringBuilder sb = new StringBuilder();
sb.append(BarcodeData.XMR_SCHEME).append(address);
boolean first = true;
if ((paymentId != null) && !paymentId.isEmpty()) {
sb.append("?");
first = false;
sb.append(BarcodeData.XMR_PAYMENTID).append('=').append(paymentId);
}
if ((description != null) && !description.isEmpty()) {
sb.append(first ? "?" : "&");
first = false;
@@ -185,8 +174,11 @@ public class BarcodeData {
String address = monero.getPath();
String paymentId = parms.get(XMR_PAYMENTID);
// deal with empty payment_id created by non-spec-conforming apps
if ((paymentId != null) && paymentId.isEmpty()) paymentId = null;
// no support for payment ids!
if (paymentId != null) {
Timber.e("no support for payment ids!");
return null;
}
String description = parms.get(XMR_DESCRIPTION);
String amount = parms.get(XMR_AMOUNT);
@@ -198,10 +190,6 @@ public class BarcodeData {
return null; // we have an amount but its not a number!
}
}
if ((paymentId != null) && !Wallet.isPaymentIdValid(paymentId)) {
Timber.d("paymentId invalid");
return null;
}
if (!Wallet.isAddressValid(address)) {
Timber.d("address invalid");
@@ -267,7 +255,7 @@ public class BarcodeData {
Timber.d("[%s] is not http url", bip70);
return null;
}
return new BarcodeData(BarcodeData.Asset.BTC, null, null, bip70, null, description, null, Security.NORMAL);
return new BarcodeData(BarcodeData.Asset.BTC, null, null, bip70, description, null, Security.NORMAL);
}
if (!BitcoinAddressValidator.validate(address)) {
Timber.d("BTC address (%s) invalid", address);

View File

@@ -235,8 +235,10 @@ public class NodeInfo extends Node {
String rpcVersion = json.getString("jsonrpc");
if (!RPC_VERSION.equals(rpcVersion))
return false;
final JSONObject header = json.getJSONObject(
"result").getJSONObject("block_header");
final JSONObject result = json.getJSONObject("result");
if (!result.has("credits")) // introduced in monero v0.15.0
return false;
final JSONObject header = result.getJSONObject("block_header");
height = header.getLong("height");
timestamp = header.getLong("timestamp");
majorVersion = header.getInt("major_version");

View File

@@ -29,19 +29,16 @@ public class TxData implements Parcelable {
public TxData(TxData txData) {
this.dstAddr = txData.dstAddr;
this.paymentId = txData.paymentId;
this.amount = txData.amount;
this.mixin = txData.mixin;
this.priority = txData.priority;
}
public TxData(String dstAddr,
String paymentId,
long amount,
int mixin,
PendingTransaction.Priority priority) {
this.dstAddr = dstAddr;
this.paymentId = paymentId;
this.amount = amount;
this.mixin = mixin;
this.priority = priority;
@@ -51,10 +48,6 @@ public class TxData implements Parcelable {
return dstAddr;
}
public String getPaymentId() {
return paymentId;
}
public long getAmount() {
return amount;
}
@@ -71,10 +64,6 @@ public class TxData implements Parcelable {
this.dstAddr = dstAddr;
}
public void setPaymentId(String paymentId) {
this.paymentId = paymentId;
}
public void setAmount(long amount) {
this.amount = amount;
}
@@ -96,7 +85,6 @@ public class TxData implements Parcelable {
}
private String dstAddr;
private String paymentId;
private long amount;
private int mixin;
private PendingTransaction.Priority priority;
@@ -106,7 +94,6 @@ public class TxData implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(dstAddr);
out.writeString(paymentId);
out.writeLong(amount);
out.writeInt(mixin);
out.writeInt(priority.getValue());
@@ -125,7 +112,6 @@ public class TxData implements Parcelable {
protected TxData(Parcel in) {
dstAddr = in.readString();
paymentId = in.readString();
amount = in.readLong();
mixin = in.readInt();
priority = PendingTransaction.Priority.fromInteger(in.readInt());
@@ -142,14 +128,12 @@ public class TxData implements Parcelable {
StringBuffer sb = new StringBuffer();
sb.append("dstAddr:");
sb.append(dstAddr);
sb.append(",paymentId:");
sb.append(paymentId);
sb.append(",amount:");
sb.append(amount);
sb.append(",mixin:");
sb.append(mixin);
sb.append(",priority:");
sb.append(String.valueOf(priority));
sb.append(priority);
return sb.toString();
}
}

View File

@@ -28,6 +28,7 @@ import android.widget.TextView;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.Helper;
import java.util.Locale;
@@ -76,8 +77,7 @@ public class ProgressDialog extends AlertDialog {
super.onCreate(savedInstanceState);
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
if (Helper.preventScreenshot()) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
}

View File

@@ -31,7 +31,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
@@ -86,12 +85,9 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private EditText etDummy;
private TextInputLayout etAddress;
private TextInputLayout etPaymentId;
private TextInputLayout etNotes;
private Button bPaymentId;
private CardView cvScan;
private View tvPaymentIdIntegrated;
private View llPaymentId;
private TextView tvXmrTo;
private View llXmrTo;
private ImageButton bPasteAddress;
@@ -114,7 +110,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
View view = inflater.inflate(R.layout.fragment_send_address, container, false);
tvPaymentIdIntegrated = view.findViewById(R.id.tvPaymentIdIntegrated);
llPaymentId = view.findViewById(R.id.llPaymentId);
llXmrTo = view.findViewById(R.id.llXmrTo);
tvXmrTo = view.findViewById(R.id.tvXmrTo);
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto)));
@@ -140,29 +135,13 @@ public class SendAddressWizardFragment extends SendWizardFragment {
next = null;
} else {
// maybe a bip72 or 70 URI
String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
final String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
if (bip70 != null) {
// looks good - resolve through xmr.to
processBip70(bip70);
next = null;
} else if (checkAddress()) {
if (llPaymentId.getVisibility() == View.VISIBLE) {
next = etPaymentId;
} else {
next = etNotes;
}
}
}
if (next != null) {
final View focus = next;
etAddress.post(new Runnable() {
@Override
public void run() {
focus.requestFocus();
}
});
}
}
}
});
etAddress.getEditText().addTextChangedListener(new TextWatcher() {
@@ -174,8 +153,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
etAddress.setError(null);
if (isIntegratedAddress()) {
Timber.d("isIntegratedAddress");
etPaymentId.getEditText().getText().clear();
llPaymentId.setVisibility(View.INVISIBLE);
etAddress.setError(getString(R.string.info_paymentid_integrated));
tvPaymentIdIntegrated.setVisibility(View.VISIBLE);
llXmrTo.setVisibility(View.INVISIBLE);
sendListener.setMode(SendFragment.Mode.XMR);
@@ -184,7 +162,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
setBtcMode();
} else {
Timber.d("isStandardAddress or other");
llPaymentId.setVisibility(View.VISIBLE);
tvPaymentIdIntegrated.setVisibility(View.INVISIBLE);
llXmrTo.setVisibility(View.INVISIBLE);
sendListener.setMode(SendFragment.Mode.XMR);
@@ -208,47 +185,21 @@ public class SendAddressWizardFragment extends SendWizardFragment {
if (clip == null) return;
// clean it up
final String address = clip.replaceAll("[^0-9A-Z-a-z]", "");
if (Wallet.isAddressValid(address) || BitcoinAddressValidator.validate(address))
etAddress.getEditText().setText(address);
else
if (Wallet.isAddressValid(address) || BitcoinAddressValidator.validate(address)) {
final EditText et = etAddress.getEditText();
et.setText(address);
et.setSelection(et.getText().length());
etAddress.requestFocus();
} else {
final String bip70 = PaymentProtocolHelper.getBip70(clip);
if (bip70 != null) {
final EditText et = etAddress.getEditText();
et.setText(clip);
et.setSelection(et.getText().length());
processBip70(bip70);
} else
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
}
});
etPaymentId = view.findViewById(R.id.etPaymentId);
etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (checkPaymentId()) {
etNotes.requestFocus();
}
return true;
}
return false;
}
});
etPaymentId.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
etPaymentId.setError(null);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
bPaymentId = view.findViewById(R.id.bPaymentId);
bPaymentId.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
etPaymentId.getEditText().setText((Wallet.generatePaymentId()));
}
});
@@ -259,7 +210,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
etDummy.requestFocus();
Helper.hideKeyboard(getActivity());
return true;
}
return false;
@@ -277,7 +227,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
etDummy = view.findViewById(R.id.etDummy);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etDummy.requestFocus();
Helper.hideKeyboard(getActivity());
View tvNfc = view.findViewById(R.id.tvNfc);
NfcManager manager = (NfcManager) getContext().getSystemService(Context.NFC_SERVICE);
@@ -289,8 +238,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private void setBtcMode() {
Timber.d("setBtcMode");
etPaymentId.getEditText().getText().clear();
llPaymentId.setVisibility(View.INVISIBLE);
tvPaymentIdIntegrated.setVisibility(View.INVISIBLE);
llXmrTo.setVisibility(View.VISIBLE);
sendListener.setMode(SendFragment.Mode.BTC);
@@ -345,7 +292,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
final BarcodeData barcodeData =
new BarcodeData(BarcodeData.Asset.BTC, address, null,
resolvedBip70, null, null, String.valueOf(amount),
resolvedBip70, null, String.valueOf(amount),
BarcodeData.Security.BIP70);
etNotes.post(new Runnable() {
@Override
@@ -406,22 +353,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
return BitcoinAddressValidator.validate(address);
}
private boolean checkPaymentId() {
String paymentId = etPaymentId.getEditText().getText().toString();
boolean ok = paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId);
if (!ok) {
etPaymentId.setError(getString(R.string.receive_paymentid_invalid));
} else {
if (!paymentId.isEmpty() && isIntegratedAddress()) {
ok = false;
etPaymentId.setError(getString(R.string.receive_integrated_paymentid_invalid));
} else {
etPaymentId.setError(null);
}
}
return ok;
}
private void shakeAddress() {
etAddress.startAnimation(Helper.getShakeAnimation(getContext()));
}
@@ -444,11 +375,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
return false;
}
if (!checkPaymentId()) {
etPaymentId.startAnimation(Helper.getShakeAnimation(getContext()));
return false;
}
if (sendListener != null) {
TxData txData = sendListener.getTxData();
if (txData instanceof TxDataBtc) {
@@ -462,10 +388,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
((TxDataBtc) txData).setBip70(null);
}
txData.setDestinationAddress(null);
txData.setPaymentId("");
} else {
txData.setDestinationAddress(etAddress.getEditText().getText().toString());
txData.setPaymentId(etPaymentId.getEditText().getText().toString());
}
txData.setUserNotes(new UserNotes(etNotes.getEditText().getText().toString()));
txData.setPriority(PendingTransaction.Priority.Priority_Default);
@@ -528,14 +452,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
etAddress.setError(null);
}
String scannedPaymentId = barcodeData.paymentId;
if (scannedPaymentId != null) {
etPaymentId.getEditText().setText(scannedPaymentId);
checkPaymentId();
} else {
etPaymentId.getEditText().getText().clear();
etPaymentId.setError(null);
}
String scannedNotes = barcodeData.description;
if (scannedNotes != null) {
etNotes.getEditText().setText(scannedNotes);
@@ -551,7 +467,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
public void onResumeFragment() {
super.onResumeFragment();
Timber.d("onResumeFragment()");
Helper.hideKeyboard(getActivity());
etDummy.requestFocus();
}

View File

@@ -21,8 +21,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
@@ -30,8 +28,7 @@ import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.widget.ExchangeTextView;
import com.m2049r.xmrwallet.widget.NumberPadView;
import com.m2049r.xmrwallet.widget.ExchangeEditText;
import timber.log.Timber;
@@ -59,8 +56,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
}
private TextView tvFunds;
private ExchangeTextView evAmount;
private View llAmount;
private ExchangeEditText etAmount;
private View rlSweep;
private ImageButton ibSweep;
@@ -75,12 +71,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
View view = inflater.inflate(R.layout.fragment_send_amount, container, false);
tvFunds = view.findViewById(R.id.tvFunds);
evAmount = view.findViewById(R.id.evAmount);
((NumberPadView) view.findViewById(R.id.numberPad)).setListener(evAmount);
etAmount = view.findViewById(R.id.etAmount);
rlSweep = view.findViewById(R.id.rlSweep);
llAmount = view.findViewById(R.id.llAmount);
view.findViewById(R.id.ivSweep).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -97,8 +90,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
}
});
Helper.hideKeyboard(getActivity());
etAmount.requestFocus();
return view;
}
@@ -107,11 +99,11 @@ public class SendAmountWizardFragment extends SendWizardFragment {
private void sweepAll(boolean spendAllMode) {
if (spendAllMode) {
ibSweep.setVisibility(View.INVISIBLE);
llAmount.setVisibility(View.GONE);
etAmount.setVisibility(View.GONE);
rlSweep.setVisibility(View.VISIBLE);
} else {
ibSweep.setVisibility(View.VISIBLE);
llAmount.setVisibility(View.VISIBLE);
etAmount.setVisibility(View.VISIBLE);
rlSweep.setVisibility(View.GONE);
}
this.spendAllMode = spendAllMode;
@@ -124,12 +116,12 @@ public class SendAmountWizardFragment extends SendWizardFragment {
sendListener.getTxData().setAmount(Wallet.SWEEP_ALL);
}
} else {
if (!evAmount.validate(maxFunds)) {
if (!etAmount.validate(maxFunds, 0)) {
return false;
}
if (sendListener != null) {
String xmr = evAmount.getAmount();
String xmr = etAmount.getNativeAmount();
if (xmr != null) {
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
} else {
@@ -146,7 +138,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
public void onResumeFragment() {
super.onResumeFragment();
Timber.d("onResumeFragment()");
Helper.hideKeyboard(getActivity());
Helper.showKeyboard(getActivity());
final long funds = getTotalFunds();
maxFunds = 1.0 * funds / 1000000000000L;
if (!sendListener.getActivityCallback().isStreetMode()) {
@@ -156,11 +148,11 @@ public class SendAmountWizardFragment extends SendWizardFragment {
tvFunds.setText(getString(R.string.send_available,
getString(R.string.unknown_amount)));
}
// getAmount is null if exchange is in progress
if ((evAmount.getAmount() != null) && evAmount.getAmount().isEmpty()) {
// getNativeAmount is null if exchange is in progress
if ((etAmount.getNativeAmount() != null) && etAmount.getNativeAmount().isEmpty()) {
final BarcodeData data = sendListener.popBarcodeData();
if ((data != null) && (data.amount != null)) {
evAmount.setAmount(data.amount);
etAmount.setAmount(data.amount);
}
}
}

View File

@@ -31,8 +31,8 @@ import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.widget.ExchangeBtcTextView;
import com.m2049r.xmrwallet.widget.NumberPadView;
import com.m2049r.xmrwallet.widget.ExchangeEditText;
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
import com.m2049r.xmrwallet.widget.SendProgressView;
import com.m2049r.xmrwallet.xmrto.XmrToError;
import com.m2049r.xmrwallet.xmrto.XmrToException;
@@ -62,8 +62,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
}
private TextView tvFunds;
private ExchangeBtcTextView evAmount;
private NumberPadView numberPad;
private ExchangeOtherEditText etAmount;
private TextView tvXmrToParms;
private SendProgressView evParams;
@@ -86,24 +85,20 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
tvXmrToParms = view.findViewById(R.id.tvXmrToParms);
evAmount = view.findViewById(R.id.evAmount);
numberPad = view.findViewById(R.id.numberPad);
numberPad.setListener(evAmount);
Helper.hideKeyboard(getActivity());
etAmount = view.findViewById(R.id.etAmount);
etAmount.requestFocus();
return view;
}
@Override
public boolean onValidateFields() {
if (!evAmount.validate(maxBtc, minBtc)) {
if (!etAmount.validate(maxBtc, minBtc)) {
return false;
}
if (sendListener != null) {
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
String btcString = evAmount.getAmount();
String btcString = etAmount.getNativeAmount();
if (btcString != null) {
try {
double btc = Double.parseDouble(btcString);
@@ -122,10 +117,12 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
private void setBip70Mode() {
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
if (txDataBtc.getBip70() != null) {
numberPad.setVisibility(View.INVISIBLE);
if (txDataBtc.getBip70() == null) {
etAmount.setEditable(true);
Helper.showKeyboard(getActivity());
} else {
numberPad.setVisibility(View.VISIBLE);
etAmount.setEditable(false);
Helper.hideKeyboard(getActivity());
}
}
@@ -141,7 +138,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
public void onResumeFragment() {
super.onResumeFragment();
Timber.d("onResumeFragment()");
Helper.hideKeyboard(getActivity());
final long funds = getTotalFunds();
if (!sendListener.getActivityCallback().isStreetMode()) {
tvFunds.setText(getString(R.string.send_available,
@@ -153,7 +149,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
final BarcodeData data = sendListener.popBarcodeData();
if (data != null) {
if (data.amount != null) {
evAmount.setAmount(data.amount);
etAmount.setAmount(data.amount);
}
}
setBip70Mode();
@@ -171,7 +167,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
getView().post(new Runnable() {
@Override
public void run() {
evAmount.setRate(1.0d / orderParameters.getPrice());
etAmount.setExchangeRate(1.0d / orderParameters.getPrice());
NumberFormat df = NumberFormat.getInstance(Locale.US);
df.setMaximumFractionDigits(6);
String min = df.format(orderParameters.getLowerLimit());
@@ -211,7 +207,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
}
private void processOrderParmsError(final Exception ex) {
evAmount.setRate(0);
etAmount.setExchangeRate(0);
orderParameters = null;
maxBtc = 0;
minBtc = 0;

View File

@@ -437,8 +437,8 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
return false;
}
});
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
if (Helper.preventScreenshot()) {
passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
@@ -457,8 +457,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
}
showProgress(3, getString(R.string.label_send_progress_create_tx));
TxData txData = sendListener.getTxData();
txData.setDestinationAddress(xmrtoStatus.getXmrReceivingAddress());
txData.setPaymentId(xmrtoStatus.getXmrRequiredPaymentIdShort());
txData.setDestinationAddress(xmrtoStatus.getXmrReceivingSubaddress());
txData.setAmount(Wallet.getAmountFromDouble(xmrtoStatus.getXmrAmountTotal()));
getActivityCallback().onPrepareSend(xmrtoStatus.getUuid(), txData);
}

View File

@@ -138,12 +138,6 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
btcData = (TxDataBtc) sendListener.getTxData();
tvTxAddress.setText(btcData.getDestinationAddress());
String paymentId = btcData.getPaymentId();
if ((paymentId != null) && (!paymentId.isEmpty())) {
tvTxPaymentId.setText(btcData.getPaymentId());
} else {
tvTxPaymentId.setText("-");
}
final PendingTx committedTx = sendListener.getCommittedTx();
if (committedTx != null) {

View File

@@ -70,7 +70,6 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
}
private TextView tvTxAddress;
private TextView tvTxPaymentId;
private TextView tvTxNotes;
private TextView tvTxAmount;
private TextView tvTxFee;
@@ -90,7 +89,6 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
R.layout.fragment_send_confirm, container, false);
tvTxAddress = view.findViewById(R.id.tvTxAddress);
tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
tvTxNotes = view.findViewById(R.id.tvTxNotes);
tvTxAmount = view.findViewById(R.id.tvTxAmount);
tvTxFee = view.findViewById(R.id.tvTxFee);
@@ -192,12 +190,6 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
final TxData txData = sendListener.getTxData();
tvTxAddress.setText(txData.getDestinationAddress());
String paymentId = txData.getPaymentId();
if ((paymentId != null) && (!paymentId.isEmpty())) {
tvTxPaymentId.setText(txData.getPaymentId());
} else {
tvTxPaymentId.setText("-");
}
UserNotes notes = sendListener.getTxData().getUserNotes();
if ((notes != null) && (!notes.note.isEmpty())) {
tvTxNotes.setText(notes.note);
@@ -324,8 +316,8 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
return false;
}
});
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
if (Helper.preventScreenshot()) {
passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}

View File

@@ -111,12 +111,6 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
final TxData txData = sendListener.getTxData();
tvTxAddress.setText(txData.getDestinationAddress());
String paymentId = txData.getPaymentId();
if ((paymentId != null) && (!paymentId.isEmpty())) {
tvTxPaymentId.setText(txData.getPaymentId());
} else {
tvTxPaymentId.setText("-");
}
final PendingTx committedTx = sendListener.getCommittedTx();
if (committedTx != null) {

View File

@@ -134,6 +134,10 @@ public enum Instruction {
return value;
}
public byte getByteValue() {
return (byte) (value & 0xFF);
}
private int value;
Instruction(int value) {

View File

@@ -27,9 +27,11 @@ import com.btchip.BTChipException;
import com.btchip.comm.BTChipTransport;
import com.btchip.comm.android.BTChipTransportAndroidHID;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import timber.log.Timber;
@@ -40,9 +42,11 @@ public class Ledger {
static public final int LOOKAHEAD_SUBADDRESSES = 20;
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
private static final byte PROTOCOL_VERSION = 0x02;
public static final int SW_OK = 0x9000;
public static final int SW_INS_NOT_SUPPORTED = 0x6D00;
public static final int OK[] = {SW_OK};
public static final int MINIMUM_LEDGER_VERSION = (1 << 16) + (3 << 8) + (1); // 1.3.1
public static UsbDevice findDevice(UsbManager usbManager) {
if (!ENABLED) return null;
@@ -89,6 +93,21 @@ public class Ledger {
}
}
static public boolean check() {
if (Instance == null) return false;
byte[] moneroVersion = WalletManager.moneroVersion().getBytes(StandardCharsets.US_ASCII);
try {
byte[] resp = Instance.exchangeApduNoOpt(Instruction.INS_RESET, moneroVersion, OK);
int deviceVersion = (resp[0] << 16) + (resp[1] << 8) + (resp[2]);
if (deviceVersion < MINIMUM_LEDGER_VERSION)
return false;
} catch (BTChipException ex) { // comm error - probably wrong app started on device
return false;
}
return true;
}
final private BTChipTransport transport;
final private String name;
private int lastSW = 0;
@@ -112,7 +131,7 @@ public class Ledger {
synchronized private byte[] exchangeRaw(byte[] apdu) {
if (transport == null)
throw new IllegalStateException("No transport (probably closed previously)");
Timber.i("exchangeRaw %02x", apdu[1]);
Timber.d("exchangeRaw %02x", apdu[1]);
Instruction ins = Instruction.fromByte(apdu[1]);
if (listener != null) listener.onInstructionSend(ins, apdu);
sniffOut(ins, apdu);
@@ -120,7 +139,6 @@ public class Ledger {
if (listener != null) listener.onInstructionReceive(ins, data);
sniffIn(data);
return data;
}
private byte[] exchange(byte[] apdu) throws BTChipException {
@@ -148,68 +166,19 @@ public class Ledger {
throw new BTChipException("Invalid status", lastSW);
}
private byte[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
byte[] apdu = new byte[data.length + 5];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte) (data.length);
System.arraycopy(data, 0, apdu, 5, data.length);
private byte[] exchangeApduNoOpt(Instruction instruction, byte[] data, int acceptedSW[])
throws BTChipException {
byte[] apdu = new byte[data.length + 6];
apdu[0] = PROTOCOL_VERSION;
apdu[1] = instruction.getByteValue();
apdu[2] = 0; // p1
apdu[3] = 0; // p2
apdu[4] = (byte) (data.length + 1); // +1 because the opt byte is part of the data
apdu[5] = 0; // opt
System.arraycopy(data, 0, apdu, 6, data.length);
return exchangeCheck(apdu, acceptedSW);
}
private byte[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, int length, int acceptedSW[]) throws BTChipException {
byte[] apdu = new byte[5];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte) (length);
return exchangeCheck(apdu, acceptedSW);
}
private byte[] exchangeApduSplit(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
int offset = 0;
byte[] result = null;
while (offset < data.length) {
int blockLength = ((data.length - offset) > 255 ? 255 : data.length - offset);
byte[] apdu = new byte[blockLength + 5];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte) (blockLength);
System.arraycopy(data, offset, apdu, 5, blockLength);
result = exchangeCheck(apdu, acceptedSW);
offset += blockLength;
}
return result;
}
private byte[] exchangeApduSplit2(byte cla, byte ins, byte p1, byte p2, byte[] data, byte[] data2, int acceptedSW[]) throws BTChipException {
int offset = 0;
byte[] result = null;
int maxBlockSize = 255 - data2.length;
while (offset < data.length) {
int blockLength = ((data.length - offset) > maxBlockSize ? maxBlockSize : data.length - offset);
boolean lastBlock = ((offset + blockLength) == data.length);
byte[] apdu = new byte[blockLength + 5 + (lastBlock ? data2.length : 0)];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte) (blockLength + (lastBlock ? data2.length : 0));
System.arraycopy(data, offset, apdu, 5, blockLength);
if (lastBlock) {
System.arraycopy(data2, 0, apdu, 5 + blockLength, data2.length);
}
result = exchangeCheck(apdu, acceptedSW);
offset += blockLength;
}
return result;
}
public interface Listener {
void onInstructionSend(Instruction ins, byte[] apdu);
@@ -251,7 +220,6 @@ public class Ledger {
if (ins == Instruction.INS_GET_KEY) {
snoopKey = (apdu[2] == 2);
}
}
private void sniffIn(byte[] data) {

View File

@@ -16,6 +16,9 @@
package com.m2049r.xmrwallet.model;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.m2049r.xmrwallet.data.TxData;
import java.io.File;
@@ -32,6 +35,47 @@ public class Wallet {
System.loadLibrary("monerujo");
}
static public class Status {
Status(int status, String errorString) {
this.status = StatusEnum.values()[status];
this.errorString = errorString;
}
final private StatusEnum status;
final private String errorString;
@Nullable
private ConnectionStatus connectionStatus; // optional
public StatusEnum getStatus() {
return status;
}
public String getErrorString() {
return errorString;
}
public void setConnectionStatus(@Nullable ConnectionStatus connectionStatus) {
this.connectionStatus = connectionStatus;
}
@Nullable
public ConnectionStatus getConnectionStatus() {
return connectionStatus;
}
public boolean isOk() {
return (getStatus() == StatusEnum.Status_Ok)
&& ((getConnectionStatus() == null) ||
(getConnectionStatus() == ConnectionStatus.ConnectionStatus_Connected));
}
@Override
@NonNull
public String toString() {
return "Wallet.Status: (" + status + "/" + errorString + ", " + connectionStatus;
}
}
private int accountIndex = 0;
public int getAccountIndex() {
@@ -66,7 +110,7 @@ public class Wallet {
Device_Ledger
}
public enum Status {
public enum StatusEnum {
Status_Ok,
Status_Error,
Status_Critical
@@ -85,12 +129,16 @@ public class Wallet {
public native void setSeedLanguage(String language);
public Status getStatus() {
return Wallet.Status.values()[getStatusJ()];
return statusWithErrorString();
}
private native int getStatusJ();
public Status getFullStatus() {
Wallet.Status walletStatus = statusWithErrorString();
walletStatus.setConnectionStatus(getConnectionStatus());
return walletStatus;
}
public native String getErrorString();
private native Status statusWithErrorString();
public native boolean setPassword(String password);
@@ -250,22 +298,21 @@ public class Wallet {
public PendingTransaction createTransaction(TxData txData) {
return createTransaction(
txData.getDestinationAddress(),
txData.getPaymentId(),
txData.getAmount(),
txData.getMixin(),
txData.getPriority());
}
public PendingTransaction createTransaction(String dst_addr, String payment_id,
public PendingTransaction createTransaction(String dst_addr,
long amount, int mixin_count,
PendingTransaction.Priority priority) {
disposePendingTransaction();
int _priority = priority.getValue();
long txHandle =
(amount == SWEEP_ALL ?
createSweepTransaction(dst_addr, payment_id, mixin_count, _priority,
createSweepTransaction(dst_addr, "", mixin_count, _priority,
accountIndex) :
createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority,
createTransactionJ(dst_addr, "", amount, mixin_count, _priority,
accountIndex));
pendingTransaction = new PendingTransaction(txHandle);
return pendingTransaction;

View File

@@ -95,7 +95,7 @@ public class WalletManager {
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, getNetworkType().getValue());
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet);
if (wallet.getStatus() == Wallet.Status.Status_Ok) {
if (wallet.getStatus().isOk()) {
// (Re-)Estimate restore height based on what we know
final long oldHeight = wallet.getRestoreHeight();
final long restoreHeight =
@@ -287,7 +287,8 @@ public class WalletManager {
this.daemonAddress = null;
this.daemonUsername = "";
this.daemonPassword = "";
setDaemonAddressJ("");
//setDaemonAddressJ(""); // don't disconnect as monero code blocks for many seconds!
//TODO: need to do something about that later
}
}
@@ -353,4 +354,6 @@ public class WalletManager {
static public native void logWarning(String category, String message);
static public native void logError(String category, String message);
static public native String moneroVersion();
}

View File

@@ -31,6 +31,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
@@ -220,7 +221,7 @@ public class WalletService extends Service {
void onSendTransactionFailed(String error);
void onWalletStarted(Wallet.ConnectionStatus walletStatus);
void onWalletStarted(Wallet.Status walletStatus);
void onWalletOpen(Wallet.Device device);
}
@@ -287,9 +288,9 @@ public class WalletService extends Service {
if (walletId != null) {
showProgress(getString(R.string.status_wallet_loading));
showProgress(10);
Wallet.ConnectionStatus connStatus = start(walletId, walletPw);
if (observer != null) observer.onWalletStarted(connStatus);
if (connStatus != Wallet.ConnectionStatus.ConnectionStatus_Connected) {
Wallet.Status walletStatus = start(walletId, walletPw);
if (observer != null) observer.onWalletStarted(walletStatus);
if ((walletStatus == null) || !walletStatus.isOk()) {
errorState = true;
stop();
}
@@ -300,7 +301,7 @@ public class WalletService extends Service {
boolean rc = myWallet.store();
Timber.d("wallet stored: %s with rc=%b", myWallet.getName(), rc);
if (!rc) {
Timber.w("Wallet store failed: %s", myWallet.getErrorString());
Timber.w("Wallet store failed: %s", myWallet.getStatus().getErrorString());
}
if (observer != null) observer.onWalletStored(rc);
} else if (cmd.equals(REQUEST_CMD_TX)) {
@@ -362,7 +363,7 @@ public class WalletService extends Service {
boolean rc = myWallet.store();
Timber.d("wallet stored: %s with rc=%b", myWallet.getName(), rc);
if (!rc) {
Timber.w("Wallet store failed: %s", myWallet.getErrorString());
Timber.w("Wallet store failed: %s", myWallet.getStatus().getErrorString());
}
if (observer != null) observer.onWalletStored(rc);
listener.updated = true;
@@ -464,7 +465,8 @@ public class WalletService extends Service {
return true; // true is important so that onUnbind is also called next time
}
private Wallet.ConnectionStatus start(String walletName, String walletPassword) {
@Nullable
private Wallet.Status start(String walletName, String walletPassword) {
Timber.d("start()");
startNotfication();
showProgress(getString(R.string.status_wallet_loading));
@@ -472,11 +474,11 @@ public class WalletService extends Service {
if (listener == null) {
Timber.d("start() loadWallet");
Wallet aWallet = loadWallet(walletName, walletPassword);
Wallet.ConnectionStatus connStatus = Wallet.ConnectionStatus.ConnectionStatus_Disconnected;
if (aWallet != null) connStatus = aWallet.getConnectionStatus();
if (connStatus != Wallet.ConnectionStatus.ConnectionStatus_Connected) {
if (aWallet != null) aWallet.close();
return connStatus;
if (aWallet == null) return null;
Wallet.Status walletStatus = aWallet.getFullStatus();
if (!walletStatus.isOk()) {
aWallet.close();
return walletStatus;
}
listener = new MyWalletListener();
listener.start();
@@ -487,7 +489,7 @@ public class WalletService extends Service {
// if we try to refresh the history here we get occasional segfaults!
// doesnt matter since we update as soon as we get a new block anyway
Timber.d("start() done");
return Wallet.ConnectionStatus.ConnectionStatus_Connected;
return getWallet().getFullStatus();
}
public void stop() {
@@ -532,10 +534,9 @@ public class WalletService extends Service {
wallet = walletMgr.openWallet(path, walletPassword);
showProgress(60);
Timber.d("wallet opened");
Wallet.Status status = wallet.getStatus();
Timber.d("wallet status is %s", status);
if (status != Wallet.Status.Status_Ok) {
Timber.d("wallet status is %s", status);
Wallet.Status walletStatus = wallet.getStatus();
if (!walletStatus.isOk()) {
Timber.d("wallet status is %s", walletStatus);
WalletManager.getInstance().close(wallet); // TODO close() failed?
wallet = null;
// TODO what do we do with the progress??

View File

@@ -0,0 +1,215 @@
/*
* Copyright (c) 2019 m2049r@monerujo.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// https://developer.android.com/training/basics/network-ops/xml
package com.m2049r.xmrwallet.service.exchange.ecb;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeException;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class ExchangeApiImpl implements ExchangeApi {
@NonNull
private final OkHttpClient okHttpClient;
@NonNull
private final HttpUrl baseUrl;
//so we can inject the mockserver url
@VisibleForTesting
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, @NonNull final HttpUrl baseUrl) {
this.okHttpClient = okHttpClient;
this.baseUrl = baseUrl;
}
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
this(okHttpClient, HttpUrl.parse("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"));
// data is daily and is refreshed around 16:00 CET every working day
}
public static boolean isSameDay(Calendar calendar, Calendar anotherCalendar) {
return (calendar.get(Calendar.YEAR) == anotherCalendar.get(Calendar.YEAR)) &&
(calendar.get(Calendar.DAY_OF_YEAR) == anotherCalendar.get(Calendar.DAY_OF_YEAR));
}
@Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) {
if (!baseCurrency.equals("EUR")) {
callback.onError(new IllegalArgumentException("Only EUR supported as base"));
return;
}
if (baseCurrency.equals(quoteCurrency)) {
callback.onSuccess(new ExchangeRateImpl(quoteCurrency, 1.0, new Date()));
return;
}
if (fetchDate != null) { // we have data
boolean useCache = false;
// figure out if we can use the cached values
// data is daily and is refreshed around 16:00 CET every working day
Calendar now = Calendar.getInstance(TimeZone.getTimeZone("CET"));
int fetchWeekday = fetchDate.get(Calendar.DAY_OF_WEEK);
int fetchDay = fetchDate.get(Calendar.DAY_OF_YEAR);
int fetchHour = fetchDate.get(Calendar.HOUR_OF_DAY);
int today = now.get(Calendar.DAY_OF_YEAR);
int nowHour = now.get(Calendar.HOUR_OF_DAY);
if (
// was it fetched today before 16:00? assume no new data iff now < 16:00 as well
((today == fetchDay) && (fetchHour < 16) && (nowHour < 16))
// was it fetched after, 17:00? we can assume there is no newer data
|| ((today == fetchDay) && (fetchHour > 17))
|| ((today == fetchDay + 1) && (fetchHour > 17) && (nowHour < 16))
// is the data itself from today? there can be no newer data
|| (fxDate.get(Calendar.DAY_OF_YEAR) == today)
// was it fetched Sat/Sun? we can assume there is no newer data
|| ((fetchWeekday == Calendar.SATURDAY) || (fetchWeekday == Calendar.SUNDAY))
) { // return cached rate
try {
callback.onSuccess(getRate(quoteCurrency));
} catch (ExchangeException ex) {
callback.onError(ex);
}
return;
}
}
final Request httpRequest = createHttpRequest(baseUrl);
okHttpClient.newCall(httpRequest).enqueue(new okhttp3.Callback() {
@Override
public void onFailure(final Call call, final IOException ex) {
callback.onError(ex);
}
@Override
public void onResponse(final Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(response.body().byteStream());
doc.getDocumentElement().normalize();
parse(doc);
try {
callback.onSuccess(getRate(quoteCurrency));
} catch (ExchangeException ex) {
callback.onError(ex);
}
} catch (ParserConfigurationException | SAXException ex) {
Timber.w(ex);
callback.onError(new ExchangeException(ex.getLocalizedMessage()));
}
} else {
callback.onError(new ExchangeException(response.code(), response.message()));
}
}
});
}
private Request createHttpRequest(final HttpUrl url) {
return new Request.Builder()
.url(url)
.get()
.build();
}
final private Map<String, Double> fxEntries = new HashMap<>();
private Calendar fxDate = null;
private Calendar fetchDate = null;
synchronized private ExchangeRate getRate(String currency) throws ExchangeException {
Timber.e("Getting %s", currency);
final Double rate = fxEntries.get(currency);
if (rate == null) throw new ExchangeException(404, "Currency not supported: " + currency);
return new ExchangeRateImpl(currency, rate, fxDate.getTime());
}
private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
{
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private void parse(final Document xmlRootDoc) {
final Map<String, Double> entries = new HashMap<>();
Calendar date = Calendar.getInstance(TimeZone.getTimeZone("CET"));
try {
NodeList cubes = xmlRootDoc.getElementsByTagName("Cube");
for (int i = 0; i < cubes.getLength(); i++) {
Node node = cubes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element cube = (Element) node;
if (cube.hasAttribute("time")) { // a time Cube
final Date time = DATE_FORMAT.parse(cube.getAttribute("time"));
date.setTime(time);
} else if (cube.hasAttribute("currency")
&& cube.hasAttribute("rate")) { // a rate Cube
String currency = cube.getAttribute("currency");
double rate = Double.valueOf(cube.getAttribute("rate"));
entries.put(currency, rate);
} // else an empty Cube - ignore
}
}
} catch (ParseException ex) {
Timber.d(ex);
}
synchronized (this) {
if (date != null) {
fetchDate = Calendar.getInstance(TimeZone.getTimeZone("CET"));
fxDate = date;
fxEntries.clear();
fxEntries.putAll(entries);
}
// else don't change what we have
}
}
}

Some files were not shown because too many files have changed in this diff Show More