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

Compare commits

...

81 Commits

Author SHA1 Message Date
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
m2049r
2c3e73b540 bump version v1.11.7 2019-05-22 08:30:11 +02:00
m2049r
830d9dadb9 paste receiver address (#592) 2019-05-20 17:17:46 +02:00
m2049r
331d88ebba use daemon height for new wallets if available (#591) 2019-05-20 17:17:27 +02:00
m2049r
7cc2f6fafb bump version v1.11.6 2019-05-19 21:34:26 +02:00
m2049r
9a3ee0eda8 fix update of account balances (#590) 2019-05-19 20:27:35 +02:00
m2049r
6e898939a3 show account balances (#589)
and refactor displayAmount
2019-05-19 17:33:19 +02:00
m2049r
40ae39d647 update studio 2019-05-19 10:28:18 +02:00
jindouyunz
ca81e652e5 Update strings.xml_Chinese (#575)
* Update strings.xml

updated on 4/22/2019 version 2, Jindouyunz.

* Update string.xml_Chinese 2019-5-12

Hi, I made several pieces of modification based on lafudoci's review, thanks.

* Update string.xml-zh-rCN 2019-5-13

removed all lines with "translatable=false", and currency array
2019-05-18 08:40:31 +02:00
jindouyunz
796048be4e Update about.xml (#574)
updated on 4/21/2019, Jindouyunz.
2019-05-18 08:39:49 +02:00
jindouyunz
441bf995c8 Update help.xml_Chinese (#573)
* Update help.xml

updated on 4/21/2019, version 2, Jindouyunz

* Update help.xml 2019-5-11

Hi, I add changes on line 200, based on lafudoci's review, thanks.
2019-05-18 08:39:28 +02:00
m2049r
e8860ab8eb focus on password if no fingerprint (#587) 2019-05-12 00:17:21 +02:00
m2049r
525b38ff53 remove save button for notes (#586) 2019-05-12 00:17:07 +02:00
m2049r
ba79bf87aa Update rpc checks (#585)
* check rpc version

* update protocol version check
2019-05-11 21:15:35 +02:00
m2049r
3fe6571e7d update restore heights for 4 & 5.2019 (#583) 2019-05-11 11:41:16 +02:00
erciccione
364e6a8137 update values-it/strings.xml (Italian translation) (#580) 2019-05-11 11:40:20 +02:00
vp11
cb69ce99d6 update pt-BR translation (#581)
* update pt-BR translation

* missing block
2019-05-11 11:40:01 +02:00
m2049r
1f976872fc increase sanity check size limit (#576) 2019-04-23 20:52:43 +02:00
kic0
27f266b6f7 PT-PT translations updated (#569) 2019-04-20 11:03:57 +02:00
m2049r
168928d54a Update studio (#572)
* gradle 5.1

* remove ref to @id/ibBookmark
2019-04-19 15:51:25 +02:00
Leza89
b3f61072aa V1.11.4 - german translations (#556)
* Corrections on the translation without translating

* Added missing commas, hyphens
* replaced diverging terminology with Monero GUI (i.e. Remote Node → Drittanbieter-Node, Saldo → Guthaben, Daemon/Dienst → Hintergrunddienst)
* grammatical adjustments
* unifying the gender of certain words (i.e. "die" Wallet)

* Translations - Draft for peer review

* Corrections according to rodolfo912's review

en-dash as a symbol vs. unicode ;#8211; is yet to be resolved

* Reverting case of "Wallet" from "die" to "das"

As requested by m2049r the article of "Wallet" was reverted back from "die Wallet" to "das Wallet" and corrected in places where it was inconsistent before.
2019-04-19 14:57:49 +02:00
Jack Rogers Lewis
ccb64aded0 SK update help.xml (#568) 2019-04-19 14:49:25 +02:00
Jack Rogers Lewis
e98fa089f2 update new strings (#567)
podadresa = subadresa
2019-04-19 14:48:47 +02:00
Scott Anecito
884878b7a7 Update Japanese localization, fix English typo (#564) 2019-04-19 14:43:56 +02:00
v1docq47
4e23f0ef3a Update for Russian translation #444 (#558)
* Update for Russian translation #444

* fix

added translation of missing lines (help.xml / strings.xml)
2019-03-19 20:12:37 +01:00
0140454
6ea4e3d998 Update zh-rTW translation (#557)
* Update zh-rTW translation

* Update translation for "Using a payment link"
2019-03-19 20:12:16 +01:00
m2049r
971c90f35b Merge pull request #559 from m2049r/feature_enable_ledger
Feature enable ledger
2019-03-19 20:06:17 +01:00
m2049r
f0523c403c bump version 2019-03-19 19:34:04 +01:00
m2049r
966ed23b87 enable ledger again 2019-03-19 19:32:38 +01:00
m2049r
95f2ca74a6 clean code (#555) 2019-03-10 22:00:57 +01:00
m2049r
81d94478f2 update wallet api (#554) 2019-03-10 21:49:57 +01:00
el00ruobuob
16ff779ebc New strings & help update to FR (#552) 2019-03-09 21:58:10 +01:00
m2049r
6b7bb164f4 v1.11.3 (#550) 2019-03-09 00:07:37 +01:00
m2049r
da1d4ea1bf convert ledger seed dialog (#549) 2019-03-09 00:02:58 +01:00
m2049r
d5a967f690 upgrade gradle version (#548) 2019-03-06 16:46:52 +01:00
m2049r
de8de02f9f disable ledger support (#547) 2019-03-05 23:35:11 +01:00
m2049r
06456e33e4 v1.11.1 (#546)
for use with b087cbf995
2019-03-05 21:59:19 +01:00
124 changed files with 7575 additions and 2977 deletions

File diff suppressed because one or more lines are too long

View File

@@ -137,7 +137,11 @@ set_target_properties(checkpoints PROPERTIES IMPORTED_LOCATION
add_library(device STATIC IMPORTED)
set_target_properties(device PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libdevice.a)
${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
@@ -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 170
versionName "1.11.0 'Chernushka'"
versionCode 198
versionName "1.12.8 'Caerbannog'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
@@ -115,6 +115,10 @@ dependencies {
implementation 'org.slf4j:slf4j-nop:1.7.25'
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
// https://mvnrepository.com/artifact/com.github.aelstad/keccakj
implementation 'com.github.aelstad:keccakj:1.1.0'
testImplementation "junit:junit:$rootProject.ext.junitVersion"
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"

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,8 +46,12 @@ 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)) {
return device;
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

@@ -16,6 +16,7 @@
package com.m2049r.xmrwallet;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -33,6 +34,7 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.LinearLayout;
@@ -45,6 +47,7 @@ import com.m2049r.xmrwallet.util.FingerprintHelper;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.RestoreHeight;
import com.m2049r.xmrwallet.util.ledger.Monero;
import com.m2049r.xmrwallet.widget.Toolbar;
import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn;
@@ -76,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) {
@@ -107,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) {
@@ -115,6 +152,8 @@ public class GenerateFragment extends Fragment {
}
}
});
clearErrorOnTextEntry(etWalletMnemonic);
etWalletAddress.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
@@ -123,6 +162,8 @@ public class GenerateFragment extends Fragment {
}
}
});
clearErrorOnTextEntry(etWalletAddress);
etWalletViewKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
@@ -131,6 +172,8 @@ public class GenerateFragment extends Fragment {
}
}
});
clearErrorOnTextEntry(etWalletViewKey);
etWalletSpendKey.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
@@ -139,6 +182,7 @@ public class GenerateFragment extends Fragment {
}
}
});
clearErrorOnTextEntry(etWalletSpendKey);
Helper.showKeyboard(getActivity());
@@ -240,9 +284,7 @@ public class GenerateFragment extends Fragment {
}
});
etWalletAddress.setVisibility(View.VISIBLE);
etWalletAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener()
{
etWalletAddress.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)) {
@@ -274,9 +316,7 @@ public class GenerateFragment extends Fragment {
}
if (type.equals(TYPE_KEY)) {
etWalletSpendKey.setVisibility(View.VISIBLE);
etWalletSpendKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener()
{
etWalletSpendKey.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)) {
@@ -303,9 +343,7 @@ public class GenerateFragment extends Fragment {
}
});
}
bGenerate.setOnClickListener(new View.OnClickListener()
{
bGenerate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.hideKeyboard(getActivity());
@@ -313,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();
@@ -616,4 +639,79 @@ public class GenerateFragment extends Fragment {
}
super.onCreateOptionsMenu(menu, inflater);
}
AlertDialog ledgerDialog = null;
public void convertLedgerSeed() {
if (ledgerDialog != null) return;
final Activity activity = getActivity();
View promptsView = getLayoutInflater().inflate(R.layout.prompt_ledger_seed, null);
android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity);
alertDialogBuilder.setView(promptsView);
final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed);
final TextInputLayout etPassphrase = promptsView.findViewById(R.id.etPassphrase);
etSeed.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (etSeed.getError() != null) {
etSeed.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) {
}
});
alertDialogBuilder
.setCancelable(false)
.setPositiveButton(getString(R.string.label_ok), null)
.setNegativeButton(getString(R.string.label_cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Helper.hideKeyboardAlways(activity);
etWalletMnemonic.getEditText().getText().clear();
dialog.cancel();
ledgerDialog = null;
}
});
ledgerDialog = alertDialogBuilder.create();
ledgerDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String ledgerSeed = etSeed.getEditText().getText().toString();
String ledgerPassphrase = etPassphrase.getEditText().getText().toString();
String moneroSeed = Monero.convert(ledgerSeed, ledgerPassphrase);
if (moneroSeed != null) {
etWalletMnemonic.getEditText().setText(moneroSeed);
ledgerDialog.dismiss();
ledgerDialog = null;
} else {
etSeed.setError(getString(R.string.bad_ledger_seed));
}
}
});
}
});
if (Helper.preventScreenshot()) {
ledgerDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
ledgerDialog.show();
}
}

View File

@@ -33,6 +33,7 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.ImageButton;
@@ -54,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 {
@@ -71,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;
@@ -98,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);
@@ -187,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;
@@ -223,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:
@@ -263,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);
@@ -286,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();
}
@@ -413,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;
@@ -620,6 +630,11 @@ public class GenerateReviewFragment extends Fragment {
return false;
}
});
if (Helper.preventScreenshot()) {
openDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
return openDialog;
}

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,
@@ -929,15 +939,13 @@ 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() - 2000 : -1;
Wallet newWallet = WalletManager.getInstance()
.createWallet(aFile, password, MNEMONIC_LANGUAGE);
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) {
Timber.e(newWallet.getErrorString());
toast(newWallet.getErrorString());
}
newWallet.close();
return success;
.createWallet(aFile, password, MNEMONIC_LANGUAGE, restoreHeight);
return checkAndCloseWallet(newWallet);
}
});
}
@@ -956,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);
}
});
}
@@ -982,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);
}
});
}
@@ -1009,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);
}
});
}
@@ -1229,6 +1219,12 @@ public class LoginActivity extends BaseActivity
case R.id.action_language:
onChangeLocale();
return true;
case R.id.action_ledger_seed:
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (f instanceof GenerateFragment) {
((GenerateFragment) f).convertLedgerSeed();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
@@ -1361,30 +1357,44 @@ public class LoginActivity extends BaseActivity
};
private void connectLedger(UsbManager usbManager, final UsbDevice usbDevice) {
try {
Ledger.connect(usbManager, usbDevice);
registerDetachReceiver();
onLedgerAction();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
Toast.LENGTH_SHORT)
.show();
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() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
Toast.LENGTH_SHORT)
.show();
}
});
}
});
} catch (IOException ex) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.open_wallet_ledger_missing),
Toast.LENGTH_SHORT)
.show();
}
});
}
} catch (IOException ex) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.open_wallet_ledger_missing),
Toast.LENGTH_SHORT)
.show();
}
});
}
}
@Override

View File

@@ -439,10 +439,13 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
Collections.sort(nodesToTest, NodeInfo.BestNodeComparator);
NodeInfo bestNode = nodesToTest.get(0);
if (bestNode.isValid())
return nodesToTest.get(0);
else
if (bestNode.isValid()) {
activityCallback.setNode(bestNode);
return bestNode;
} else {
activityCallback.setNode(null);
return null;
}
}
@Override
@@ -450,7 +453,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
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

@@ -32,6 +32,7 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.TextView;
@@ -265,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,6 +508,10 @@ public class NodeFragment extends Fragment
}
});
if (Helper.preventScreenshot()) {
editDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
etNodePass.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {

View File

@@ -302,7 +302,7 @@ public class ReceiveFragment extends Fragment {
File imagePath = new File(getActivity().getCacheDir(), "images");
File png = new File(imagePath, "QR.png");
Uri contentUri = FileProvider.getUriForFile(getActivity(),
"com.m2049r.xmrwallet.fileprovider", png);
BuildConfig.APPLICATION_ID + ".fileprovider", png);
if (contentUri != null) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
@@ -574,6 +574,7 @@ public class ReceiveFragment extends Fragment {
@Override
public void onPause() {
Timber.d("onPause()");
Helper.hideKeyboard(getActivity());
super.onPause();
}

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

@@ -71,7 +71,6 @@ public class TxFragment extends Fragment {
private TextView tvTxFee;
private TextView tvTxTransfers;
private TextView etTxNotes;
private Button bTxNotes;
// XMRTO stuff
private View cvXmrTo;
@@ -102,21 +101,9 @@ public class TxFragment extends Fragment {
tvTxFee = view.findViewById(R.id.tvTxFee);
tvTxTransfers = view.findViewById(R.id.tvTxTransfers);
etTxNotes = view.findViewById(R.id.etTxNotes);
bTxNotes = view.findViewById(R.id.bTxNotes);
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
bTxNotes.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
info.notes = null; // force reload on next view
bTxNotes.setEnabled(false);
etTxNotes.setEnabled(false);
userNotes.setNote(etTxNotes.getText().toString());
activityCallback.onSetNote(info.hash, userNotes.txNotes);
}
});
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -131,14 +118,6 @@ public class TxFragment extends Fragment {
return view;
}
public void onNotesSet(boolean reload) {
bTxNotes.setEnabled(true);
etTxNotes.setEnabled(true);
if (reload) {
loadNotes(this.info);
}
}
void shareTxInfo() {
if (this.info == null) return;
StringBuffer sb = new StringBuffer();
@@ -315,7 +294,6 @@ public class TxFragment extends Fragment {
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -337,9 +315,9 @@ public class TxFragment extends Fragment {
String getTxNotes(String hash);
String getTxAddress(int major, int minor);
boolean setTxNotes(String txId, String txNotes);
void onSetNote(String txId, String notes);
String getTxAddress(int major, int minor);
void setToolbarButton(int type);
@@ -357,4 +335,16 @@ public class TxFragment extends Fragment {
+ " must implement Listener");
}
}
}
@Override
public void onPause() {
if (!etTxNotes.getText().toString().equals(userNotes.note)) { // notes have changed
// save them
userNotes.setNote(etTxNotes.getText().toString());
info.notes = userNotes.txNotes;
activityCallback.setTxNotes(info.hash, info.notes);
}
Helper.hideKeyboard(getActivity());
super.onPause();
}
}

View File

@@ -150,8 +150,13 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
final WalletFragment walletFragment = (WalletFragment)
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
if (walletFragment != null) walletFragment.resetDismissedTransactions();
updateAccountsBalance();
forceUpdate();
runOnUiThread(new Runnable() {
@Override
public void run() {
updateAccountsBalance();
}
});
}
@Override
@@ -174,6 +179,11 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
return getWallet().getUserNote(txId);
}
@Override
public boolean setTxNotes(String txId, String txNotes) {
return getWallet().setUserNote(txId, txNotes);
}
@Override
public String getTxAddress(int major, int minor) {
return getWallet().getSubaddress(major, minor);
@@ -618,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:
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show();
break;
case ConnectionStatus_WrongVersion:
if (walletStatus == null) {
// guess what went wrong
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show();
} 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;
@@ -713,26 +722,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
}
}
@Override
public void onSetNotes(final boolean success) {
try {
final TxFragment txFragment = (TxFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
runOnUiThread(new Runnable() {
public void run() {
if (!success) {
Toast.makeText(WalletActivity.this, getString(R.string.tx_notes_set_failed), Toast.LENGTH_LONG).show();
}
txFragment.onNotesSet(success);
}
});
} catch (ClassCastException ex) {
// not in tx fragment
Timber.d(ex.getLocalizedMessage());
// never mind
}
}
@Override
public void onProgress(final String text) {
try {
@@ -794,21 +783,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
}
@Override
public void onSetNote(String txId, String notes) {
if (mIsBound) { // no point in talking to unbound service
Intent intent = new Intent(getApplicationContext(), WalletService.class);
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SETNOTE);
intent.putExtra(WalletService.REQUEST_CMD_SETNOTE_TX, txId);
intent.putExtra(WalletService.REQUEST_CMD_SETNOTE_NOTES, notes);
startService(intent);
Timber.d("SET NOTE request sent");
} else {
Timber.e("Service not bound");
}
}
@Override
public void onPrepareSend(final String tag, final TxData txData) {
if (mIsBound) { // no point in talking to unbound service
@@ -1062,6 +1036,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} else {
tvBalance.setText(null);
}
updateAccountsList();
}
void updateAccountsHeader() {
@@ -1075,8 +1050,11 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
Menu menu = accountsView.getMenu();
menu.removeGroup(R.id.accounts_list);
final int n = wallet.getNumAccounts();
final boolean showBalances = (n > 1) && !isStreetMode();
for (int i = 0; i < n; i++) {
final String label = wallet.getAccountLabel(i);
final String label = (showBalances ?
getString(R.string.label_account, wallet.getAccountLabel(i), Helper.getDisplayAmount(wallet.getBalance(i), 2))
: wallet.getAccountLabel(i));
final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label);
item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp);
if (i == wallet.getAccountIndex())

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);
@@ -219,8 +221,8 @@ public class WalletFragment extends Fragment
if (isExchanging) return; // wait for exchange to finish - it will fire this itself then.
// at this point selection is XMR in case of error
String displayB;
double amountA = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // crash if this fails!
if (!Helper.CRYPTO.equals(balanceCurrency)) { // not XMR
double amountA = Helper.getDecimalAmount(unlockedBalance).doubleValue();
if (!Helper.BASE_CRYPTO.equals(balanceCurrency)) { // not XMR
double amountB = amountA * balanceRate;
displayB = Helper.getFormattedAmount(amountB, false);
} else { // XMR
@@ -229,23 +231,23 @@ 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();
void refreshBalance() {
double unconfirmedXmr = Double.parseDouble(Helper.getDisplayAmount(balance - unlockedBalance));
double unconfirmedXmr = Helper.getDecimalAmount(balance - unlockedBalance).doubleValue();
showUnconfirmed(unconfirmedXmr);
if (sCurrency.getSelectedItemPosition() == 0) { // XMR
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
double amountXmr = Helper.getDecimalAmount(unlockedBalance).doubleValue();
showBalance(Helper.getFormattedAmount(amountXmr, true));
} else { // not XMR
String currency = (String) sCurrency.getSelectedItem();
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) {
@@ -294,17 +296,17 @@ public class WalletFragment extends Fragment
public void exchangeFailed() {
sCurrency.setSelection(0, true); // default to XMR
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
double amountXmr = Helper.getDecimalAmount(unlockedBalance).doubleValue();
showBalance(Helper.getFormattedAmount(amountXmr, true));
hideExchanging();
}
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

@@ -46,7 +46,8 @@ import okhttp3.ResponseBody;
import timber.log.Timber;
public class NodeInfo extends Node {
final static public int MIN_MAJOR_VERSION = 9;
final static public int MIN_MAJOR_VERSION = 11;
final static public String RPC_VERSION = "2.0";
private long height = 0;
private long timestamp = 0;
@@ -228,11 +229,16 @@ public class NodeInfo extends Node {
responseCode = response.code();
if (response.isSuccessful()) {
ResponseBody respBody = response.body(); // closed through Response object
if ((respBody != null) && (respBody.contentLength() < 1000)) { // sanity check
if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check
final JSONObject json = new JSONObject(
respBody.string());
final JSONObject header = json.getJSONObject(
"result").getJSONObject("block_header");
String rpcVersion = json.getString("jsonrpc");
if (!RPC_VERSION.equals(rpcVersion))
return false;
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

@@ -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;
@@ -62,9 +63,6 @@ public class ProgressDialog extends AlertDialog {
pbBar = view.findViewById(R.id.pbBar);
tvProgress = view.findViewById(R.id.tvProgress);
setView(view);
//setTitle("blabla");
//super.setMessage("bubbu");
// view.invalidate();
setIndeterminate(indeterminate);
if (maxValue > 0) {
setMax(maxValue);
@@ -79,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

@@ -33,7 +33,9 @@ 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;
import android.widget.Toast;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.BarcodeData;
@@ -92,6 +94,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private View llPaymentId;
private TextView tvXmrTo;
private View llXmrTo;
private ImageButton bPasteAddress;
private boolean resolvingOA = false;
private boolean resolvingPP = false;
@@ -137,28 +140,12 @@ 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();
}
});
}
}
}
});
@@ -173,6 +160,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
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);
@@ -197,6 +185,32 @@ public class SendAddressWizardFragment extends SendWizardFragment {
}
});
bPasteAddress = view.findViewById(R.id.bPasteAddress);
bPasteAddress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String clip = Helper.getClipBoardText(getActivity());
if (clip == null) return;
// clean it up
final String address = clip.replaceAll("[^0-9A-Z-a-z]", "");
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() {
@@ -230,7 +244,10 @@ public class SendAddressWizardFragment extends SendWizardFragment {
bPaymentId.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
etPaymentId.getEditText().setText((Wallet.generatePaymentId()));
final EditText et = etPaymentId.getEditText();
et.setText((Wallet.generatePaymentId()));
et.setSelection(et.getText().length());
etPaymentId.requestFocus();
}
});
@@ -241,7 +258,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;
@@ -259,7 +275,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);
@@ -533,7 +548,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

@@ -27,11 +27,13 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.data.TxDataBtc;
@@ -435,6 +437,11 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
return false;
}
});
if (Helper.preventScreenshot()) {
passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
passwordDialog.show();
}
@@ -450,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

@@ -27,10 +27,12 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.TextView;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.model.PendingTransaction;
@@ -322,6 +324,11 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
return false;
}
});
if (Helper.preventScreenshot()) {
passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
passwordDialog.show();
}

View File

@@ -30,6 +30,7 @@ import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.data.UserNotes;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
@@ -154,7 +155,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
}
if ((infoItem.fee > 0)) {
String fee = Helper.getDisplayAmount(infoItem.fee, 5);
String fee = Helper.getDisplayAmount(infoItem.fee, Helper.DISPLAY_DIGITS_INFO);
tvFee.setText(context.getString(R.string.tx_list_fee, fee));
tvFee.setVisibility(View.VISIBLE);
} else {

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,23 +27,29 @@ 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;
public class Ledger {
static final public boolean ENABLED = true;
// 5:20 is same as wallet2.cpp::restore()
static public final int LOOKAHEAD_ACCOUNTS = 5;
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;
return BTChipTransportAndroidHID.getDevice(usbManager);
}
@@ -87,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;
@@ -110,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);
@@ -118,7 +139,6 @@ public class Ledger {
if (listener != null) listener.onInstructionReceive(ins, data);
sniffIn(data);
return data;
}
private byte[] exchange(byte[] apdu) throws BTChipException {
@@ -146,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);
@@ -249,7 +220,6 @@ public class Ledger {
if (ins == Instruction.INS_GET_KEY) {
snoopKey = (apdu[2] == 2);
}
}
private void sniffIn(byte[] data) {

View File

@@ -173,5 +173,4 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
return this.hash.compareTo(another.hash);
}
}
}
}

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);
@@ -353,9 +401,10 @@ public class Wallet {
if (label.equals(NEW_ACCOUNT_NAME)) {
String address = getAddress(accountIndex);
int len = address.length();
return address.substring(0, 6) +
label = address.substring(0, 6) +
"\u2026" + address.substring(len - 6, len);
} else return label;
}
return label;
}
public String getSubaddressLabel(int addressIndex) {

View File

@@ -91,14 +91,16 @@ public class WalletManager {
managedWallet = null;
}
public Wallet createWallet(File aFile, String password, String language) {
public Wallet createWallet(File aFile, String password, String language, long height) {
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
long oldHeight = wallet.getRestoreHeight();
wallet.setRestoreHeight(RestoreHeight.getInstance().getHeight(new Date()));
final long oldHeight = wallet.getRestoreHeight();
final long restoreHeight =
(height > -1) ? height : RestoreHeight.getInstance().getHeight(new Date());
wallet.setRestoreHeight(restoreHeight);
Timber.d("Changed Restore Height from %d to %d", oldHeight, wallet.getRestoreHeight());
wallet.setPassword(password); // this rewrites the keys file (which contains the restore height)
}
@@ -285,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
}
}
@@ -351,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;
@@ -69,10 +70,6 @@ public class WalletService extends Service {
public static final String REQUEST_CMD_SEND = "send";
public static final String REQUEST_CMD_SEND_NOTES = "notes";
public static final String REQUEST_CMD_SETNOTE = "setnote";
public static final String REQUEST_CMD_SETNOTE_TX = "tx";
public static final String REQUEST_CMD_SETNOTE_NOTES = "notes";
public static final int START_SERVICE = 1;
public static final int STOP_SERVICE = 2;
@@ -224,9 +221,7 @@ public class WalletService extends Service {
void onSendTransactionFailed(String error);
void onSetNotes(boolean success);
void onWalletStarted(Wallet.ConnectionStatus walletStatus);
void onWalletStarted(Wallet.Status walletStatus);
void onWalletOpen(Wallet.Device device);
}
@@ -293,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();
}
@@ -306,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)) {
@@ -368,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;
@@ -378,26 +373,6 @@ public class WalletService extends Service {
if (observer != null) observer.onSendTransactionFailed(error);
return;
}
} else if (cmd.equals(REQUEST_CMD_SETNOTE)) {
Wallet myWallet = getWallet();
Timber.d("SET NOTE for wallet: %s", myWallet.getName());
String txId = extras.getString(REQUEST_CMD_SETNOTE_TX);
String notes = extras.getString(REQUEST_CMD_SETNOTE_NOTES);
if ((txId != null) && (notes != null)) {
boolean success = myWallet.setUserNote(txId, notes);
if (!success) {
Timber.e(myWallet.getErrorString());
}
if (observer != null) observer.onSetNotes(success);
if (success) {
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());
}
if (observer != null) observer.onWalletStored(rc);
}
}
}
}
break;
@@ -490,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));
@@ -498,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();
@@ -513,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() {
@@ -558,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