1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-07 11:35:59 +02:00

Compare commits

..

62 Commits

Author SHA1 Message Date
m2049r
a8755ee0da correct error message 2018-10-08 22:50:40 +02:00
m2049r
f186bc9d4f core wallet api 0.13 2018-10-08 22:50:16 +02:00
ProkhorZ
9cb961a368 Translate missing strings (#429)
As requested in #411
2018-10-08 22:34:00 +02:00
m2049r
aa78541b6f fix build 2018-10-08 20:58:26 +02:00
v1docq47
eba6a78004 Update for Russian translation #370 (#422)
* Update for Russian translation
- strings.xml
- help.xml
2018-10-08 19:44:56 +02:00
m2049r
8587eab41c whitespace & cleanup 2018-10-08 19:02:00 +02:00
m2049r
4271c743c7 update wallet balance on refresh 2018-10-08 19:02:00 +02:00
m2049r
5b60987692 remove beta flavor 2018-10-08 19:02:00 +02:00
m2049r
e1bd04c945 tweaks & new version 2018-10-08 19:02:00 +02:00
m2049r
ef29db62c4 change error message 2018-10-08 19:02:00 +02:00
m2049r
82ce12e27c increase mixin to 10 as per protocol v8 2018-10-08 19:02:00 +02:00
m2049r
4211687981 rework build process 2018-10-08 19:02:00 +02:00
m2049r
d398416a3d translation prep 2018-10-08 19:02:00 +02:00
m2049r
1844d84be8 whitespace 2018-10-08 19:02:00 +02:00
m2049r
5101b7da5e flavor alpha,beta 2018-10-08 19:02:00 +02:00
m2049r
3e90f2e22e new Ledger interface 2018-10-08 19:02:00 +02:00
m2049r
f01a8eac5d bump version 2018-10-08 19:02:00 +02:00
m2049r
0c13338dc0 link with libsodium 2018-10-08 19:02:00 +02:00
m2049r
78d6fef3e4 getDeviceType 2018-10-08 19:02:00 +02:00
m2049r
cbddcdc92d new api & queryWalletDevice 2018-10-08 19:02:00 +02:00
jaro Lee
4289aaac77 Update strings.xml (#428)
Minor translation misssteps. Bringing to context.
Regarding `send_settings_title` - NASTAVENIA string is too long (wrapping the text in a button). Changed to `Možnosti`  (options in english)
2018-10-08 19:00:55 +02:00
m2049r
fd15da9c84 cleaned old/irrelevant stuff 2018-10-08 18:59:36 +02:00
m2049r
c3357087d5 add missing strings 2018-10-08 13:35:10 +02:00
m2049r
47e0bc86fd fixed positional formatting 2018-10-08 13:35:10 +02:00
Wobole
dbd45ea4f5 Update german translation (#425) 2018-10-08 13:02:06 +02:00
ProkhorZ
90c3e6ef8b Add Dutch translation in values-nl (#411) 2018-10-08 13:00:17 +02:00
m2049r
bc6a816462 upgrade AndroidStudio 2018-09-25 22:39:07 +02:00
m2049r
274a1a25f1 correct cstdio files to patch 2018-09-25 21:27:05 +02:00
m2049r
9e3a2b102e correct sed syntax 2018-09-25 20:53:58 +02:00
Johan Lindqvist
647ae9a565 Patch 1 (#414)
* Added Swedish translations

* Fixed spelling error
2018-09-23 08:38:20 +02:00
m2049r
2bbd20855f deal correctly with escaped strings (#419) 2018-09-22 11:20:31 +02:00
erciccione
b8fc5f910a add 'ErCiccione' to the credits (#417) 2018-09-21 18:26:22 +02:00
erciccione
01700cf780 update values-it/strings.xml (#413) 2018-09-21 14:39:05 +02:00
Lafudoci
2fa6286b0e Update zh-rTW translation for OpenAlias (#415) 2018-09-21 14:37:24 +02:00
jar'o Lee
0ac1680e75 Update strings.xml (#416)
new strings translated , minor fix of weird-sounding old translation (restore height)
2018-09-21 14:37:06 +02:00
el00ruobuob
bd9a98e0d8 French translation OpenAlias & Language menu (#412) 2018-09-21 14:36:47 +02:00
jenniferberger
8a5121e792 enhance build description (#271)
Writing a script to build the artifacts in a reproducible environment
- PATH for openssl build was not set
- branch for m2049r/monero.git was not set
2018-09-21 09:52:07 +02:00
m2049r
5e6d3f3032 refresh qr code when notes changed (#410) 2018-09-18 21:25:46 +02:00
m2049r
65991ff554 bump version 2018-09-17 17:58:24 +02:00
m2049r
b0629e46e8 correct address entry message (#409) 2018-09-17 17:56:04 +02:00
m2049r
45ec3198a0 bump version & fix permissions (#408) 2018-09-17 11:50:06 +02:00
m2049r
9b66c466f2 update licenses (#407) 2018-09-17 11:48:12 +02:00
m2049r
d257e183ad tweak message (#406) 2018-09-17 10:07:21 +02:00
m2049r
10d8e441fe Ledger translations (#405)
- zh-rCN, ru, nb, sv, it, es, ro, el, pt missing
2018-09-17 10:03:52 +02:00
m2049r
9f9bc4793d OpenAlias support for XMR & BTC (#404)
* with support for OpenAlias QR Codes
2018-09-17 09:03:07 +02:00
m2049r
8b28e3ea1e check ACTION_DOWN when KEYCODE_ENTER (#403) 2018-09-16 18:16:55 +02:00
m2049r
e394394538 Description in QR Code (#401)
* use notes in qr code

* remove payment id on receive

* prep translations
2018-09-14 21:15:10 +02:00
jar'o Lee
7424ef07f7 Slovak Translation #2 (#341) 2018-09-07 22:58:35 +02:00
Lafudoci
f5ce6ec824 Update zh-rTW translation (#388)
* Update zh-rTW translation

* Unify the space between english and zh character.

* Follow W3C requirements for text layout

* Unify the space between English and zh characters in other files
* Use full-width punctuation marks

* Adjust the position of line break
2018-09-04 00:26:29 +02:00
m2049r
4215a8bf9e Better offline estimation of restore height (#398)
* 2018-09-01 blockheight

* better offline restore height estimate for new wallets
2018-09-04 00:25:18 +02:00
m2049r
8b016f93dc bump version 2018-08-19 13:45:05 +02:00
m2049r
b239a5094b Upgrade targetSdkVersion to Oreo (27) (#392)
* targetSdkVersion 27

* fix unescaped apostrophes in strings

* update Oreo notifications
2018-08-18 15:50:06 +02:00
m2049r
6a2de36578 prevent unregisterReceiver if not registered (#387) 2018-08-12 13:31:25 +02:00
m2049r
5aded68c53 Improve Send Workflow (#386)
* remove password query on initial spend screen

* report error if send tx fails

* better failed tx alert
2018-08-11 12:23:00 +02:00
m2049r
d78a2be120 send_qr_hint is not translatable (#385) 2018-08-10 16:59:39 +02:00
m2049r
e6c7800911 update gradle version 2018-08-10 16:52:14 +02:00
Attila
cbbe079f67 Hungarian update (#384)
NFC translation plus a minor fix added.
2018-08-10 16:52:48 +02:00
m2049r
7fc2dc3ba1 bump version code 2018-08-08 22:10:39 +02:00
m2049r
43204d64ef Interim merge of Ledger translations (#383)
* Ledger french translation (#359)

* zh-TW translation for Ledger (#368, #373)

* Hungarian Ledgers strings added (#372)

* German translation

* update help about entering height
2018-08-08 22:10:28 +02:00
m2049r
1433143a39 Use NFC for receive & send (#380)
* Use NFC Tag for receive & send

* prep translations
2018-08-06 22:18:57 +02:00
m2049r
a9a78393a9 clear amounts when typing (#379) 2018-08-05 23:26:01 +02:00
m2049r
fe7ab31050 getRestoreHeight (#378) 2018-08-05 23:25:41 +02:00
94 changed files with 5263 additions and 1512 deletions

1
.idea/gradle.xml generated
View File

@@ -3,6 +3,7 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="disableWrapperSourceDistributionNotification" value="true" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules"> <option name="modules">

View File

@@ -22,12 +22,8 @@ Help us translate Monerujo! You can find instructions [On Taiga](https://taiga.g
You may lose all your Moneroj if you use this App. Be cautious when spending on the mainnet. You may lose all your Moneroj if you use this App. Be cautious when spending on the mainnet.
### Random Notes ### Random Notes
- Based off monero v0.11.1.0 - works on the mainnet & stagenet
- currently only android32 (runs on 64-bit as well)
- works on the testnet & mainnet
- sync is slow due to 32-bit architecture
- use your own daemon - it's easy - use your own daemon - it's easy
- screen stays on until first sync is complete
- Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/ - Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/
### TODO ### TODO
@@ -43,13 +39,8 @@ of the "real" testnet. After creating a new wallet, make a **new** one by recov
The official monero client shows the same behaviour. The official monero client shows the same behaviour.
### HOW TO BUILD ### HOW TO BUILD
No need to build. Binaries are included:
- openssl-1.0.2l See [the instructions](doc/BUILDING-external-libs.md)
- monero-v0.12
- boost_1_58_0
If you want to build them yourself (recommended) check out [the instructions](doc/BUILDING-external-libs.md)
Then, fire up Android Studio and build the APK. Then, fire up Android Studio and build the APK.

View File

@@ -7,6 +7,14 @@ add_library( monerujo
set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../external-libs) set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../external-libs)
############
# libsodium
############
add_library(sodium STATIC IMPORTED)
set_target_properties(sodium PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/libsodium/lib/${ANDROID_ABI}/libsodium.a)
############ ############
# OpenSSL # OpenSSL
############ ############
@@ -184,5 +192,7 @@ target_link_libraries( monerujo
ssl ssl
crypto crypto
sodium
${log-lib} ${log-lib}
) )

View File

@@ -1,14 +1,14 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 25 compileSdkVersion 27
buildToolsVersion '27.0.3' buildToolsVersion '28.0.2'
defaultConfig { defaultConfig {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 25 targetSdkVersion 27
versionCode 111 versionCode 131
versionName "1.6.1 'Nano S'" versionName "1.8.1 'Bullets And Octane-Pirates'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@@ -18,6 +18,16 @@ android {
} }
} }
flavorDimensions "version"
productFlavors {
alpha {
applicationIdSuffix ".alpha"
versionNameSuffix " (alpha)"
}
prod {
}
}
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
@@ -27,6 +37,7 @@ android {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
} }
} }
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path "CMakeLists.txt" path "CMakeLists.txt"
@@ -61,20 +72,24 @@ android {
output -> output ->
def abiName = output.getFilter(com.android.build.OutputFile.ABI) def abiName = output.getFilter(com.android.build.OutputFile.ABI)
output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode
//def flavor = output.getFilter(flavor)
if (abiName == null) abiName = "universal" if (abiName == null) abiName = "universal"
def v = "${variant.versionName}".replaceFirst(" .*\$", "").replace(".", "x") def v = "${variant.versionName}".replaceFirst(" '.*' ?", "")
.replace(".", "x")
.replace("(", "-")
.replace(")", "")
outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk" outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk"
} }
} }
} }
dependencies { dependencies {
implementation 'com.android.support:appcompat-v7:25.4.0' implementation "com.android.support:appcompat-v7:$rootProject.ext.supportVersion"
implementation 'com.android.support:design:25.4.0' implementation "com.android.support:design:$rootProject.ext.supportVersion"
implementation 'com.android.support:support-v4:25.4.0' implementation "com.android.support:support-v4:$rootProject.ext.supportVersion"
implementation 'com.android.support:recyclerview-v7:25.4.0' implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion"
implementation 'com.android.support:cardview-v7:25.4.0' implementation "com.android.support:cardview-v7:$rootProject.ext.supportVersion"
implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion" implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
@@ -82,6 +97,10 @@ dependencies {
implementation 'com.nulab-inc:zxcvbn:1.2.3' implementation 'com.nulab-inc:zxcvbn:1.2.3'
implementation 'dnsjava:dnsjava:2.1.8'
implementation 'org.jitsi:dnssecjava:1.1.3'
implementation 'org.slf4j:slf4j-nop:1.7.25'
testImplementation "junit:junit:$rootProject.ext.junitVersion" testImplementation "junit:junit:$rootProject.ext.junitVersion"
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"

View File

@@ -8,10 +8,11 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" /> <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.NFC" />
<application <application
android:name=".XmrWalletApplication" android:name=".XmrWalletApplication"
android:allowBackup="true" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
@@ -41,32 +42,12 @@
<meta-data <meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter" /> android:resource="@xml/usb_device_filter" />
</activity> </activity>
<!--activity
android:name=".util.UsbEventReceiverActivity"
android:excludeFromRecents="true"
android:exported="false"
android:label="@string/app_name"
android:noHistory="true"
android:process=":UsbEventReceiverActivityProcess"
android:taskAffinity="com.m2049r.xmrwallet.taskAffinityUsbEventReceiver"
android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter" />
</activity-->
<service <service
android:name=".service.WalletService" android:name=".service.WalletService"
android:description="@string/service_description" android:description="@string/service_description"
android:exported="false" android:exported="false"
android:label="Monero Wallet Service" /> android:label="Monero Wallet Service" />
</application> </application>
</manifest> </manifest>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
// 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

@@ -416,17 +416,20 @@ Java_com_m2049r_xmrwallet_model_WalletManager_verifyWalletPassword(JNIEnv *env,
//virtual int queryWalletHardware(const std::string &keys_file_name, const std::string &password) const = 0; //virtual int queryWalletHardware(const std::string &keys_file_name, const std::string &password) const = 0;
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_queryWalletHardware(JNIEnv *env, jobject instance, Java_com_m2049r_xmrwallet_model_WalletManager_queryWalletDeviceJ(JNIEnv *env, jobject instance,
jstring keys_file_name, jstring keys_file_name,
jstring password) { jstring password) {
const char *_keys_file_name = env->GetStringUTFChars(keys_file_name, NULL); const char *_keys_file_name = env->GetStringUTFChars(keys_file_name, NULL);
const char *_password = env->GetStringUTFChars(password, NULL); const char *_password = env->GetStringUTFChars(password, NULL);
int hardwareId = Bitmonero::Wallet::Device device_type;
Bitmonero::WalletManagerFactory::getWalletManager()-> bool ok = Bitmonero::WalletManagerFactory::getWalletManager()->
queryWalletHardware(std::string(_keys_file_name), std::string(_password)); queryWalletDevice(device_type, std::string(_keys_file_name), std::string(_password));
env->ReleaseStringUTFChars(keys_file_name, _keys_file_name); env->ReleaseStringUTFChars(keys_file_name, _keys_file_name);
env->ReleaseStringUTFChars(password, _password); env->ReleaseStringUTFChars(password, _password);
return static_cast<jint>(hardwareId); if (ok)
return static_cast<jint>(device_type);
else
return -1;
} }
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL
@@ -686,7 +689,20 @@ Java_com_m2049r_xmrwallet_model_Wallet_initJ(JNIEnv *env, jobject instance,
} }
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0; // virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_setRestoreHeight(JNIEnv *env, jobject instance,
jlong height) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->setRefreshFromBlockHeight((uint64_t) height);
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getRestoreHeight(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->getRefreshFromBlockHeight();
}
// virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0; // virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0;
// virtual bool connectToDaemon() = 0; // virtual bool connectToDaemon() = 0;
@@ -762,11 +778,11 @@ Java_com_m2049r_xmrwallet_model_Wallet_isSynchronized(JNIEnv *env, jobject insta
return static_cast<jboolean>(wallet->synchronized()); return static_cast<jboolean>(wallet->synchronized());
} }
JNIEXPORT jboolean JNICALL JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_isKeyOnDevice(JNIEnv *env, jobject instance) { Java_com_m2049r_xmrwallet_model_Wallet_getDeviceTypeJ(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance); Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
bool key_on_device = wallet->isKeyOnDevice(); Bitmonero::Wallet::Device device_type = wallet->getDeviceType();
return static_cast<jboolean>(key_on_device); return static_cast<jint>(device_type);
} }
//void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h //void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h
@@ -1370,24 +1386,22 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jobject i
// Ledger Stuff // Ledger Stuff
// //
#include "monerujo_ledger.h" #include "device_io_monerujo.hpp"
/** /**
* @brief LedgerExchange - exchange data with Ledger Device * @brief LedgerExchange - exchange data with Ledger Device
* @param pbSendBuffer - buffer for data to send * @param command - buffer for data to send
* @param cbSendLength - length of send buffer * @param cmd_len - length of send to send
* @param pbRecvBuffer - buffer for received data * @param response - buffer for received data
* @param pcbRecvLength - pointer to size of receive buffer * @param max_resp_len - size of receive buffer
* gets set with length of received data on successful return *
* @return SCARD_S_SUCCESS - success * @return length of received data in response or -1 if error
* SCARD_E_NO_READERS_AVAILABLE - no device connected / found
* SCARD_E_INSUFFICIENT_BUFFER - pbRecvBuffer is too small for the response
*/ */
LONG LedgerExchange( int LedgerExchange(
LPCBYTE pbSendBuffer, unsigned char *command,
DWORD cbSendLength, unsigned int cmd_len,
LPBYTE pbRecvBuffer, unsigned char *response,
LPDWORD pcbRecvLength) { unsigned int max_resp_len) {
LOGD("LedgerExchange"); LOGD("LedgerExchange");
JNIEnv *jenv; JNIEnv *jenv;
int envStat = attachJVM(&jenv); int envStat = attachJVM(&jenv);
@@ -1395,30 +1409,29 @@ LONG LedgerExchange(
jmethodID exchangeMethod = jenv->GetStaticMethodID(class_Ledger, "Exchange", "([B)[B"); jmethodID exchangeMethod = jenv->GetStaticMethodID(class_Ledger, "Exchange", "([B)[B");
jsize sendLen = static_cast<jsize>(cbSendLength); jsize sendLen = static_cast<jsize>(cmd_len);
jbyteArray dataSend = jenv->NewByteArray(sendLen); jbyteArray dataSend = jenv->NewByteArray(sendLen);
jenv->SetByteArrayRegion(dataSend, 0, sendLen, (jbyte *) pbSendBuffer); jenv->SetByteArrayRegion(dataSend, 0, sendLen, (jbyte *) command);
jbyteArray dataRecv = (jbyteArray) jenv->CallStaticObjectMethod(class_Ledger, exchangeMethod, jbyteArray dataRecv = (jbyteArray) jenv->CallStaticObjectMethod(class_Ledger, exchangeMethod,
dataSend); dataSend);
jenv->DeleteLocalRef(dataSend); jenv->DeleteLocalRef(dataSend);
if (dataRecv == nullptr) { if (dataRecv == nullptr) {
detachJVM(jenv, envStat); detachJVM(jenv, envStat);
LOGD("LedgerExchange SCARD_E_NO_READERS_AVAILABLE"); LOGD("LedgerExchange SCARD_E_NO_READERS_AVAILABLE");
return SCARD_E_NO_READERS_AVAILABLE; return -1;
} }
jsize len = jenv->GetArrayLength(dataRecv); jsize len = jenv->GetArrayLength(dataRecv);
LOGD("LedgerExchange SCARD_S_SUCCESS %ld/%d", cbSendLength, len); LOGD("LedgerExchange SCARD_S_SUCCESS %ld/%d", cmd_len, len);
if (len <= *pcbRecvLength) { if (len <= max_resp_len) {
*pcbRecvLength = static_cast<DWORD>(len); jenv->GetByteArrayRegion(dataRecv, 0, len, (jbyte *) response);
jenv->GetByteArrayRegion(dataRecv, 0, len, (jbyte *) pbRecvBuffer);
jenv->DeleteLocalRef(dataRecv); jenv->DeleteLocalRef(dataRecv);
detachJVM(jenv, envStat); detachJVM(jenv, envStat);
return SCARD_S_SUCCESS; return static_cast<int>(len);;
} else { } else {
jenv->DeleteLocalRef(dataRecv); jenv->DeleteLocalRef(dataRecv);
detachJVM(jenv, envStat); detachJVM(jenv, envStat);
LOGE("LedgerExchange SCARD_E_INSUFFICIENT_BUFFER"); LOGE("LedgerExchange SCARD_E_INSUFFICIENT_BUFFER");
return SCARD_E_INSUFFICIENT_BUFFER; return -1;
} }
} }

View File

@@ -1,46 +0,0 @@
/**
* Copyright (c) 2018 m2049r
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.
*/
#ifndef XMRWALLET_LEDGER_H
#define XMRWALLET_LEDGER_H
#ifdef __cplusplus
extern "C"
{
#endif
#define SCARD_S_SUCCESS ((LONG)0x00000000) /**< No error was encountered. */
#define SCARD_E_INSUFFICIENT_BUFFER ((LONG)0x80100008) /**< The data buffer to receive returned data is too small for the returned data. */
#define SCARD_E_NO_READERS_AVAILABLE ((LONG)0x8010002E) /**< Cannot find a smart card reader. */
typedef long LONG;
typedef unsigned long DWORD;
typedef DWORD *LPDWORD;
typedef unsigned char BYTE;
typedef BYTE *LPBYTE;
typedef const BYTE *LPCBYTE;
typedef char CHAR;
typedef CHAR *LPSTR;
int LedgerFind(char *buffer, size_t len);
LONG LedgerExchange(LPCBYTE pbSendBuffer, DWORD cbSendLength, LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength);
#ifdef __cplusplus
}
#endif
#endif //XMRWALLET_LEDGER_H

View File

@@ -1,14 +1,33 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.PowerManager; import android.os.PowerManager;
import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.widget.Toast;
import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.dialog.ProgressDialog; import com.m2049r.xmrwallet.dialog.ProgressDialog;
import com.m2049r.xmrwallet.fragment.send.SendFragment;
import com.m2049r.xmrwallet.ledger.Ledger; import com.m2049r.xmrwallet.ledger.Ledger;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import java.io.IOException;
import timber.log.Timber; import timber.log.Timber;
public class BaseActivity extends SecureActivity implements GenerateReviewFragment.ProgressListener { public class BaseActivity extends SecureActivity implements GenerateReviewFragment.ProgressListener {
@@ -102,4 +121,174 @@ public class BaseActivity extends SecureActivity implements GenerateReviewFragme
wl = null; wl = null;
Timber.d("WakeLock released"); Timber.d("WakeLock released");
} }
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initNfc();
}
@Override
protected void onPostResume() {
super.onPostResume();
if (nfcAdapter != null) {
nfcAdapter.enableForegroundDispatch(this, nfcPendingIntent, null, null);
// intercept all techs so we can tell the user their tag is no good
}
}
@Override
protected void onPause() {
Timber.d("onPause()");
if (nfcAdapter != null)
nfcAdapter.disableForegroundDispatch(this);
super.onPause();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processNfcIntent(intent);
}
// NFC stuff
private NfcAdapter nfcAdapter;
private PendingIntent nfcPendingIntent;
public void initNfc() {
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) // no NFC support
return;
nfcPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
0);
}
private void processNfcIntent(Intent intent) {
String action = intent.getAction();
Timber.d("ACTION=%s", action);
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)
|| NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)
|| NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Ndef ndef = Ndef.get(tag);
if (ndef == null) {
Toast.makeText(this, getString(R.string.nfc_tag_unsupported), Toast.LENGTH_LONG).show();
return;
}
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (f instanceof ReceiveFragment) {
// We want to write a Tag from the ReceiveFragment
BarcodeData bc = ((ReceiveFragment) f).getBarcodeData();
if (bc != null) {
new AsyncWriteTag(ndef, bc.getUri()).execute();
} // else wallet is not loaded yet or receive is otherwise not ready - ignore
} else if (f instanceof SendFragment) {
// We want to read a Tag for the SendFragment
NdefMessage ndefMessage = ndef.getCachedNdefMessage();
if (ndefMessage == null) {
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
return;
}
NdefRecord firstRecord = ndefMessage.getRecords()[0];
Uri uri = firstRecord.toUri(); // we insist on the first record
if (uri == null) {
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
} else {
BarcodeData bc = BarcodeData.fromQrCode(uri.toString());
if (bc == null)
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
else
onUriScanned(bc);
}
}
}
}
// this gets called only if we get data
@CallSuper
void onUriScanned(BarcodeData barcodeData) {
// do nothing by default yet
}
private BarcodeData barcodeData = null;
private BarcodeData popBarcodeData() {
BarcodeData popped = barcodeData;
barcodeData = null;
return popped;
}
private class AsyncWriteTag extends AsyncTask<Void, Void, Boolean> {
Ndef ndef;
Uri uri;
String errorMessage = null;
AsyncWriteTag(Ndef ndef, Uri uri) {
this.ndef = ndef;
this.uri = uri;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
showProgressDialog(R.string.progress_nfc_write);
}
@Override
protected Boolean doInBackground(Void... params) {
if (params.length != 0) return false;
try {
writeNdef(ndef, uri);
return true;
} catch (IOException | FormatException ex) {
Timber.e(ex);
} catch (IllegalArgumentException ex) {
errorMessage = ex.getMessage();
Timber.d(errorMessage);
} finally {
try {
ndef.close();
} catch (IOException ex) {
Timber.e(ex);
}
}
return false;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (isDestroyed()) {
return;
}
dismissProgressDialog();
if (!result) {
if (errorMessage != null)
Toast.makeText(getApplicationContext(), errorMessage, Toast.LENGTH_LONG).show();
else
Toast.makeText(getApplicationContext(), getString(R.string.nfc_write_failed), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), getString(R.string.nfc_write_successful), Toast.LENGTH_SHORT).show();
}
}
}
void writeNdef(Ndef ndef, Uri uri) throws IOException, FormatException {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) return; // no NFC support here
NdefRecord recordNFC = NdefRecord.createUri(uri);
NdefMessage message = new NdefMessage(recordNFC);
ndef.connect();
int tagSize = ndef.getMaxSize();
int msgSize = message.getByteArrayLength();
Timber.d("tagSize=%d, msgSIze=%d, uriSize=%d", tagSize, msgSize, uri.toString().length());
if (tagSize < msgSize)
throw new IllegalArgumentException(getString(R.string.nfc_tag_size, tagSize, msgSize));
ndef.writeNdefMessage(message);
}
} }

View File

@@ -145,7 +145,8 @@ public class GenerateFragment extends Fragment {
etWalletName.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletName.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (checkName()) { if (checkName()) {
etWalletPassword.requestFocus(); etWalletPassword.requestFocus();
} // otherwise ignore } // otherwise ignore
@@ -183,7 +184,8 @@ public class GenerateFragment extends Fragment {
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE); etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
generateWallet(); generateWallet();
return true; return true;
@@ -195,7 +197,8 @@ public class GenerateFragment extends Fragment {
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE); etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
etWalletRestoreHeight.requestFocus(); etWalletRestoreHeight.requestFocus();
return true; return true;
} }
@@ -205,7 +208,8 @@ public class GenerateFragment extends Fragment {
} else if (type.equals(TYPE_SEED)) { } else if (type.equals(TYPE_SEED)) {
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
etWalletMnemonic.requestFocus(); etWalletMnemonic.requestFocus();
return true; return true;
} }
@@ -215,7 +219,8 @@ public class GenerateFragment extends Fragment {
etWalletMnemonic.setVisibility(View.VISIBLE); etWalletMnemonic.setVisibility(View.VISIBLE);
etWalletMnemonic.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletMnemonic.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (checkMnemonic()) { if (checkMnemonic()) {
etWalletRestoreHeight.requestFocus(); etWalletRestoreHeight.requestFocus();
} }
@@ -227,7 +232,8 @@ public class GenerateFragment extends Fragment {
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) { } else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
etWalletAddress.requestFocus(); etWalletAddress.requestFocus();
return true; return true;
} }
@@ -239,7 +245,8 @@ public class GenerateFragment extends Fragment {
{ {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (checkAddress()) { if (checkAddress()) {
etWalletViewKey.requestFocus(); etWalletViewKey.requestFocus();
} }
@@ -251,7 +258,8 @@ public class GenerateFragment extends Fragment {
etWalletViewKey.setVisibility(View.VISIBLE); etWalletViewKey.setVisibility(View.VISIBLE);
etWalletViewKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletViewKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (checkViewKey()) { if (checkViewKey()) {
if (type.equals(TYPE_KEY)) { if (type.equals(TYPE_KEY)) {
etWalletSpendKey.requestFocus(); etWalletSpendKey.requestFocus();
@@ -271,7 +279,8 @@ public class GenerateFragment extends Fragment {
{ {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (checkSpendKey()) { if (checkSpendKey()) {
etWalletRestoreHeight.requestFocus(); etWalletRestoreHeight.requestFocus();
} }
@@ -285,7 +294,8 @@ public class GenerateFragment extends Fragment {
etWalletRestoreHeight.setVisibility(View.VISIBLE); etWalletRestoreHeight.setVisibility(View.VISIBLE);
etWalletRestoreHeight.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletRestoreHeight.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
generateWallet(); generateWallet();
return true; return true;

View File

@@ -200,7 +200,8 @@ public class GenerateReviewFragment extends Fragment {
super.onPreExecute(); super.onPreExecute();
showProgress(); showProgress();
if ((walletPath != null) if ((walletPath != null)
&& (WalletManager.getInstance().queryWalletHardware(walletPath + ".keys", getPassword()) == 1) && (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword())
== Wallet.Device.Device_Ledger)
&& (progressCallback != null)) { && (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE); progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
dialogOpened = true; dialogOpened = true;
@@ -231,10 +232,15 @@ public class GenerateReviewFragment extends Fragment {
address = wallet.getAddress(); address = wallet.getAddress();
seed = wallet.getSeed(); seed = wallet.getSeed();
if (wallet.isKeyOnDevice()) { switch (wallet.getDeviceType()) {
case Device_Ledger:
viewKey = Ledger.Key(); viewKey = Ledger.Key();
} else { break;
case Device_Software:
viewKey = wallet.getSecretViewKey(); viewKey = wallet.getSecretViewKey();
break;
default:
throw new IllegalStateException("Hardware backing not supported. At all!");
} }
spendKey = isWatchOnly ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey(); spendKey = isWatchOnly ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey();
isWatchOnly = wallet.isWatchOnly(); isWatchOnly = wallet.isWatchOnly();
@@ -594,7 +600,8 @@ public class GenerateReviewFragment extends Fragment {
// accept keyboard "ok" // accept keyboard "ok"
etPasswordB.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etPasswordB.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
String newPasswordA = etPasswordA.getEditText().getText().toString(); String newPasswordA = etPasswordA.getEditText().getText().toString();
String newPasswordB = etPasswordB.getEditText().getText().toString(); String newPasswordB = etPasswordB.getEditText().getText().toString();
// disallow empty passwords // disallow empty passwords

View File

@@ -149,8 +149,7 @@ public class LoginActivity extends BaseActivity
} else { } else {
Timber.i("Waiting for permissions"); Timber.i("Waiting for permissions");
} }
processUsbIntent(getIntent());
processIntent(getIntent());
} }
boolean checkServiceRunning() { boolean checkServiceRunning() {
@@ -327,7 +326,8 @@ public class LoginActivity extends BaseActivity
// accept keyboard "ok" // accept keyboard "ok"
etRename.setOnEditorActionListener(new TextView.OnEditorActionListener() { etRename.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboardAlways(LoginActivity.this); Helper.hideKeyboardAlways(LoginActivity.this);
String newName = etRename.getText().toString(); String newName = etRename.getText().toString();
dialog.cancel(); dialog.cancel();
@@ -1171,11 +1171,19 @@ public class LoginActivity extends BaseActivity
String keyPath = new File(Helper.getWalletRoot(LoginActivity.this), String keyPath = new File(Helper.getWalletRoot(LoginActivity.this),
walletName + ".keys").getAbsolutePath(); walletName + ".keys").getAbsolutePath();
// check if we need connected hardware // check if we need connected hardware
int hw = WalletManager.getInstance().queryWalletHardware(keyPath, password); Wallet.Device device =
if ((hw == 1) && (!hasLedger())) { WalletManager.getInstance().queryWalletDevice(keyPath, password);
switch (device) {
case Device_Ledger:
if (!hasLedger()) {
toast(R.string.open_wallet_ledger_missing); toast(R.string.open_wallet_ledger_missing);
} else { } else {
// hw could be < 0 meaning the password is wrong - this gets dealt with later startWallet(walletName, password, fingerprintUsed);
}
break;
default:
// device could be undefined meaning the password is wrong
// this gets dealt with later
startWallet(walletName, password, fingerprintUsed); startWallet(walletName, password, fingerprintUsed);
} }
} }
@@ -1183,6 +1191,7 @@ public class LoginActivity extends BaseActivity
} else { // this cannot really happen as we prefilter choices } else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show(); Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
} }
} }
// USB Stuff - (Ledger) // USB Stuff - (Ledger)
@@ -1255,10 +1264,10 @@ public class LoginActivity extends BaseActivity
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
super.onNewIntent(intent); super.onNewIntent(intent);
processIntent(intent); processUsbIntent(intent);
} }
private void processIntent(Intent intent) { private void processUsbIntent(Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
synchronized (this) { synchronized (this) {
@@ -1277,7 +1286,10 @@ public class LoginActivity extends BaseActivity
BroadcastReceiver detachReceiver; BroadcastReceiver detachReceiver;
private void unregisterDetachReceiver() { private void unregisterDetachReceiver() {
if (detachReceiver != null) unregisterReceiver(detachReceiver); if (detachReceiver != null) {
unregisterReceiver(detachReceiver);
detachReceiver = null;
}
} }
private void registerDetachReceiver() { private void registerDetachReceiver() {

View File

@@ -206,7 +206,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
etDaemonAddress.setOnEditorActionListener(new TextView.OnEditorActionListener() { etDaemonAddress.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
etDummy.requestFocus(); etDummy.requestFocus();
return true; return true;

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2017 m2049r
*
* 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.
*/
package com.m2049r.xmrwallet;
import com.m2049r.xmrwallet.data.BarcodeData;
public interface OnUriScannedListener {
boolean onUriScanned(BarcodeData barcodeData);
}

View File

@@ -20,6 +20,7 @@ import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.nfc.NfcManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
@@ -65,9 +66,8 @@ public class ReceiveFragment extends Fragment {
private ProgressBar pbProgress; private ProgressBar pbProgress;
private TextView tvAddressLabel; private TextView tvAddressLabel;
private TextView tvAddress; private TextView tvAddress;
private TextInputLayout etPaymentId; private TextInputLayout etNotes;
private ExchangeView evAmount; private ExchangeView evAmount;
private Button bPaymentId;
private TextView tvQrCode; private TextView tvQrCode;
private ImageView qrCode; private ImageView qrCode;
private ImageView qrCodeFull; private ImageView qrCodeFull;
@@ -95,9 +95,8 @@ public class ReceiveFragment extends Fragment {
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
tvAddressLabel = (TextView) view.findViewById(R.id.tvAddressLabel); tvAddressLabel = (TextView) view.findViewById(R.id.tvAddressLabel);
tvAddress = (TextView) view.findViewById(R.id.tvAddress); tvAddress = (TextView) view.findViewById(R.id.tvAddress);
etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId); etNotes = (TextInputLayout) view.findViewById(R.id.etNotes);
evAmount = (ExchangeView) view.findViewById(R.id.evAmount); evAmount = (ExchangeView) view.findViewById(R.id.evAmount);
bPaymentId = (Button) view.findViewById(R.id.bPaymentId);
qrCode = (ImageView) view.findViewById(R.id.qrCode); qrCode = (ImageView) view.findViewById(R.id.qrCode);
tvQrCode = (TextView) view.findViewById(R.id.tvQrCode); tvQrCode = (TextView) view.findViewById(R.id.tvQrCode);
qrCodeFull = (ImageView) view.findViewById(R.id.qrCodeFull); qrCodeFull = (ImageView) view.findViewById(R.id.qrCodeFull);
@@ -105,7 +104,6 @@ public class ReceiveFragment extends Fragment {
bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress); bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
bSubaddress = (Button) view.findViewById(R.id.bSubaddress); bSubaddress = (Button) view.findViewById(R.id.bSubaddress);
etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
bCopyAddress.setOnClickListener(new View.OnClickListener() { bCopyAddress.setOnClickListener(new View.OnClickListener() {
@@ -134,39 +132,32 @@ public class ReceiveFragment extends Fragment {
} }
}); });
etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { final EditText notesEdit = etNotes.getEditText();
notesEdit.setRawInputType(InputType.TYPE_CLASS_TEXT);
notesEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
if (checkPaymentId()) { // && evAmount.checkXmrAmount(true)) { || (actionId == EditorInfo.IME_ACTION_DONE)) {
generateQr(); generateQr();
}
return true; return true;
} }
return false; return false;
} }
}); });
etPaymentId.getEditText().addTextChangedListener(new TextWatcher() { notesEdit.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
clearQR();
}
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void beforeTextChanged(CharSequence s, int start, int count, int after) {
} }
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTextChanged(CharSequence s, int start, int before, int count) {
clearQR();
} }
});
bPaymentId.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void afterTextChanged(Editable s) {
etPaymentId.getEditText().setText((Wallet.generatePaymentId()));
etPaymentId.getEditText().setSelection(etPaymentId.getEditText().getText().length());
if (checkPaymentId()) { //&& evAmount.checkXmrAmount(true)) {
generateQr();
}
} }
}); });
@@ -190,10 +181,12 @@ public class ReceiveFragment extends Fragment {
qrCode.setOnClickListener(new View.OnClickListener() { qrCode.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Helper.hideKeyboard(getActivity());
etDummy.requestFocus();
if (qrValid) { if (qrValid) {
qrCodeFull.setImageBitmap(((BitmapDrawable) qrCode.getDrawable()).getBitmap()); qrCodeFull.setImageBitmap(((BitmapDrawable) qrCode.getDrawable()).getBitmap());
qrCodeFull.setVisibility(View.VISIBLE); qrCodeFull.setVisibility(View.VISIBLE);
} else if (checkPaymentId()) { } else {
evAmount.doExchange(); evAmount.doExchange();
} }
} }
@@ -226,6 +219,12 @@ public class ReceiveFragment extends Fragment {
throw new IllegalStateException("no wallet info"); throw new IllegalStateException("no wallet info");
} }
} }
View tvNfc = view.findViewById(R.id.tvNfc);
NfcManager manager = (NfcManager) getContext().getSystemService(Context.NFC_SERVICE);
if ((manager != null) && (manager.getDefaultAdapter() != null))
tvNfc.setVisibility(View.VISIBLE);
return view; return view;
} }
@@ -257,7 +256,7 @@ public class ReceiveFragment extends Fragment {
void setQR(Bitmap qr) { void setQR(Bitmap qr) {
qrCode.setImageBitmap(qr); qrCode.setImageBitmap(qr);
qrValid = true; qrValid = true;
tvQrCode.setVisibility(View.INVISIBLE); tvQrCode.setVisibility(View.GONE);
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
etDummy.requestFocus(); etDummy.requestFocus();
} }
@@ -284,8 +283,6 @@ public class ReceiveFragment extends Fragment {
listenerCallback.setTitle(wallet.getName()); listenerCallback.setTitle(wallet.getName());
listenerCallback.setSubtitle(wallet.getAccountLabel()); listenerCallback.setSubtitle(wallet.getAccountLabel());
tvAddress.setText(wallet.getAddress()); tvAddress.setText(wallet.getAddress());
etPaymentId.setEnabled(true);
bPaymentId.setEnabled(true);
enableCopyAddress(true); enableCopyAddress(true);
hideProgress(); hideProgress();
generateQr(); generateQr();
@@ -323,7 +320,8 @@ public class ReceiveFragment extends Fragment {
super.onPreExecute(); super.onPreExecute();
showProgress(); showProgress();
if ((walletPath != null) if ((walletPath != null)
&& (WalletManager.getInstance().queryWalletHardware(walletPath + ".keys", password) == 1) && (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", password)
== Wallet.Device.Device_Ledger)
&& (progressCallback != null)) { && (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE); progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
dialogOpened = true; dialogOpened = true;
@@ -373,51 +371,29 @@ public class ReceiveFragment extends Fragment {
} }
} }
public BarcodeData getBarcodeData() {
private boolean checkPaymentId() { if (qrValid)
String paymentId = etPaymentId.getEditText().getText().toString(); return bcData;
boolean ok = paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId); else
return null;
if (!ok) {
etPaymentId.setError(getString(R.string.receive_paymentid_invalid));
} else {
etPaymentId.setError(null);
}
return ok;
} }
private BarcodeData bcData = null;
private void generateQr() { private void generateQr() {
Timber.d("GENQR"); Timber.d("GENQR");
String address = tvAddress.getText().toString(); String address = tvAddress.getText().toString();
String paymentId = etPaymentId.getEditText().getText().toString(); String notes = etNotes.getEditText().getText().toString();
String xmrAmount = evAmount.getAmount(); String xmrAmount = evAmount.getAmount();
Timber.d("%s/%s/%s", xmrAmount, paymentId, address); Timber.d("%s/%s/%s", xmrAmount, notes, address);
if ((xmrAmount == null) || !Wallet.isAddressValid(address)) { if ((xmrAmount == null) || !Wallet.isAddressValid(address)) {
clearQR(); clearQR();
Timber.d("CLEARQR"); Timber.d("CLEARQR");
return; return;
} }
StringBuffer sb = new StringBuffer(); bcData = new BarcodeData(BarcodeData.Asset.XMR, address, null, notes, xmrAmount);
sb.append(BarcodeData.XMR_SCHEME).append(address);
boolean first = true;
if (!paymentId.isEmpty()) {
if (first) {
sb.append("?");
first = false;
}
sb.append(BarcodeData.XMR_PAYMENTID).append('=').append(paymentId);
}
if (!xmrAmount.isEmpty()) {
if (first) {
sb.append("?");
} else {
sb.append("&");
}
sb.append(BarcodeData.XMR_AMOUNT).append('=').append(xmrAmount);
}
String text = sb.toString();
int size = Math.min(qrCode.getHeight(), qrCode.getWidth()); int size = Math.min(qrCode.getHeight(), qrCode.getWidth());
Bitmap qr = generate(text, size, size); Bitmap qr = generate(bcData.getUriString(), size, size);
if (qr != null) { if (qr != null) {
setQR(qr); setQR(qr);
Timber.d("SETQR"); Timber.d("SETQR");
@@ -538,7 +514,7 @@ public class ReceiveFragment extends Fragment {
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
if (wallet.isKeyOnDevice() && (progressCallback != null)) { if ((wallet.getDeviceType() == Wallet.Device.Device_Ledger) && (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS); progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS);
dialogOpened = true; dialogOpened = true;
} }

View File

@@ -27,7 +27,6 @@ import android.content.pm.PackageManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView; import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
@@ -52,6 +51,7 @@ import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment; import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
import com.m2049r.xmrwallet.fragment.send.SendFragment; import com.m2049r.xmrwallet.fragment.send.SendFragment;
import com.m2049r.xmrwallet.ledger.Ledger;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
@@ -274,6 +274,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
break; break;
case Toolbar.BUTTON_CANCEL: case Toolbar.BUTTON_CANCEL:
onDisposeRequest(); onDisposeRequest();
Helper.hideKeyboard(WalletActivity.this);
WalletActivity.super.onBackPressed(); WalletActivity.super.onBackPressed();
break; break;
case Toolbar.BUTTON_CLOSE: case Toolbar.BUTTON_CLOSE:
@@ -432,17 +433,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override @Override
public void onSendRequest() { public void onSendRequest() {
if (needVerifyIdentity) {
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
@Override
public void action(String walletName, String password, boolean fingerprintUsed) {
replaceFragment(new SendFragment(), null, null); replaceFragment(new SendFragment(), null, null);
needVerifyIdentity = false;
}
});
} else {
replaceFragment(new SendFragment(), null, null);
}
} }
@Override @Override
@@ -471,6 +462,11 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override @Override
public boolean onRefreshed(final Wallet wallet, final boolean full) { public boolean onRefreshed(final Wallet wallet, final boolean full) {
Timber.d("onRefreshed()"); Timber.d("onRefreshed()");
runOnUiThread(new Runnable() {
public void run() {
updateAccountsBalance();
}
});
if (numAccounts != wallet.getNumAccounts()) { if (numAccounts != wallet.getNumAccounts()) {
numAccounts = wallet.getNumAccounts(); numAccounts = wallet.getNumAccounts();
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@@ -526,26 +522,35 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
boolean haveWallet = false; boolean haveWallet = false;
@Override @Override
public void onWalletOpen(final int hardware) { public void onWalletOpen(final Wallet.Device device) {
if (hardware > 0) switch (device) {
case Device_Ledger:
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE); showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
} }
}); });
} }
}
@Override @Override
public void onWalletStarted(final boolean success) { public void onWalletStarted(final Wallet.ConnectionStatus connStatus) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
dismissProgressDialog(); dismissProgressDialog();
if (!success) { switch (connStatus) {
case ConnectionStatus_Disconnected:
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show(); Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show();
break;
case ConnectionStatus_WrongVersion:
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_wrongversion), Toast.LENGTH_LONG).show();
break;
case ConnectionStatus_Connected:
break;
} }
} }
}); });
if (!success) { if (connStatus != Wallet.ConnectionStatus.ConnectionStatus_Connected) {
finish(); finish();
} else { } else {
haveWallet = true; haveWallet = true;
@@ -555,6 +560,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
getSupportFragmentManager().findFragmentById(R.id.fragment_container); getSupportFragmentManager().findFragmentById(R.id.fragment_container);
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
updateAccountsHeader();
if (walletFragment != null) { if (walletFragment != null) {
walletFragment.onLoaded(); walletFragment.onLoaded();
} }
@@ -726,7 +732,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
intent.putExtra(WalletService.REQUEST_CMD_TX_TAG, tag); intent.putExtra(WalletService.REQUEST_CMD_TX_TAG, tag);
startService(intent); startService(intent);
Timber.d("CREATE TX request sent"); Timber.d("CREATE TX request sent");
if (getWallet().isKeyOnDevice()) if (getWallet().getDeviceType() == Wallet.Device.Device_Ledger)
showLedgerProgressDialog(LedgerProgressDialog.TYPE_SEND); showLedgerProgressDialog(LedgerProgressDialog.TYPE_SEND);
} else { } else {
Timber.e("Service not bound"); Timber.e("Service not bound");
@@ -841,26 +847,39 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
private BarcodeData scannedData = null;
@Override @Override
public boolean onScanned(String qrCode) { public boolean onScanned(String qrCode) {
// #gurke // #gurke
BarcodeData bcData = BarcodeData.fromQrCode(qrCode); BarcodeData bcData = BarcodeData.fromQrCode(qrCode);
if (bcData != null) { if (bcData != null) {
this.scannedData = bcData;
popFragmentStack(null); popFragmentStack(null);
Timber.d("AAA");
onUriScanned(bcData);
return true; return true;
} else { } else {
return false; return false;
} }
} }
OnUriScannedListener onUriScannedListener = null;
@Override @Override
public BarcodeData popScannedData() { public void setOnUriScannedListener(OnUriScannedListener onUriScannedListener) {
BarcodeData data = scannedData; this.onUriScannedListener = onUriScannedListener;
scannedData = null; }
return data;
@Override
void onUriScanned(BarcodeData barcodeData) {
super.onUriScanned(barcodeData);
boolean processed = false;
if (onUriScannedListener != null) {
processed = onUriScannedListener.onUriScanned(barcodeData);
}
if (!processed || (onUriScannedListener == null)) {
Toast.makeText(this, getString(R.string.nfc_tag_read_what), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, getString(R.string.nfc_tag_read_success), Toast.LENGTH_SHORT).show();
}
} }
@Override @Override
@@ -918,7 +937,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
drawer.closeDrawer(GravityCompat.START); drawer.closeDrawer(GravityCompat.START);
return; return;
} }
final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container); final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof OnBackPressedListener) { if (fragment instanceof OnBackPressedListener) {
if (!((OnBackPressedListener) fragment).onBackPressed()) { if (!((OnBackPressedListener) fragment).onBackPressed()) {
@@ -951,13 +969,22 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
// drawer stuff // drawer stuff
void updateAccountsList() {
void updateAccountsBalance() {
final Wallet wallet = getWallet(); final Wallet wallet = getWallet();
final TextView tvName = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvName);
tvName.setText(wallet.getName());
final TextView tvBalance = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvBalance); final TextView tvBalance = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvBalance);
tvBalance.setText(getString(R.string.accounts_balance, tvBalance.setText(getString(R.string.accounts_balance,
Helper.getDisplayAmount(wallet.getBalanceAll(), 5))); Helper.getDisplayAmount(wallet.getBalanceAll(), 5)));
}
void updateAccountsHeader() {
final Wallet wallet = getWallet();
final TextView tvName = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvName);
tvName.setText(wallet.getName());
}
void updateAccountsList() {
final Wallet wallet = getWallet();
Menu menu = accountsView.getMenu(); Menu menu = accountsView.getMenu();
menu.removeGroup(R.id.accounts_list); menu.removeGroup(R.id.accounts_list);
final int n = wallet.getNumAccounts(); final int n = wallet.getNumAccounts();
@@ -1024,7 +1051,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
// accept keyboard "ok" // accept keyboard "ok"
etRename.setOnEditorActionListener(new TextView.OnEditorActionListener() { etRename.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboardAlways(WalletActivity.this); Helper.hideKeyboardAlways(WalletActivity.this);
String newName = etRename.getText().toString(); String newName = etRename.getText().toString();
dialog.cancel(); dialog.cancel();
@@ -1069,12 +1097,17 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
if (getWallet().isKeyOnDevice()) { switch (getWallet().getDeviceType()) {
case Device_Ledger:
showLedgerProgressDialog(LedgerProgressDialog.TYPE_ACCOUNT); showLedgerProgressDialog(LedgerProgressDialog.TYPE_ACCOUNT);
dialogOpened = true; dialogOpened = true;
} else { break;
case Device_Software:
showProgressDialog(R.string.accounts_progress_new); showProgressDialog(R.string.accounts_progress_new);
dialogOpened = true; dialogOpened = true;
break;
default:
throw new IllegalStateException("Hardware backing not supported. At all!");
} }
} }

View File

@@ -18,10 +18,9 @@ package com.m2049r.xmrwallet.data;
import android.net.Uri; import android.net.Uri;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator; import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.OpenAliasHelper;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -29,9 +28,14 @@ import java.util.Map;
import timber.log.Timber; import timber.log.Timber;
public class BarcodeData { public class BarcodeData {
public static final String XMR_SCHEME = "monero:"; public static final String XMR_SCHEME = "monero:";
public static final String XMR_PAYMENTID = "tx_payment_id"; public static final String XMR_PAYMENTID = "tx_payment_id";
public static final String XMR_AMOUNT = "tx_amount"; public static final String XMR_AMOUNT = "tx_amount";
public static final String XMR_DESCRIPTION = "tx_description";
public static final String OA_XMR_ASSET = "xmr";
public static final String OA_BTC_ASSET = "btc";
static final String BTC_SCHEME = "bitcoin:"; static final String BTC_SCHEME = "bitcoin:";
static final String BTC_AMOUNT = "amount"; static final String BTC_AMOUNT = "amount";
@@ -40,10 +44,24 @@ public class BarcodeData {
XMR, BTC XMR, BTC
} }
public enum Security {
NORMAL,
OA_NO_DNSSEC,
OA_DNSSEC
}
public Asset asset = null; public Asset asset = null;
public String addressName = null;
public String address = null; public String address = null;
public String paymentId = null; public String paymentId = null;
public String amount = null; public String amount = null;
public String description = null;
public Security security = Security.NORMAL;
public BarcodeData(String uri) {
this.asset = asset;
this.address = address;
}
public BarcodeData(Asset asset, String address) { public BarcodeData(Asset asset, String address) {
this.asset = asset; this.asset = asset;
@@ -63,6 +81,48 @@ public class BarcodeData {
this.amount = amount; this.amount = amount;
} }
public BarcodeData(Asset asset, String address, String paymentId, String description, String amount) {
this.asset = asset;
this.address = address;
this.paymentId = paymentId;
this.description = description;
this.amount = amount;
}
public void setAddressName(String name) {
addressName = name;
}
public void setSecurity(Security security) {
this.security = security;
}
public Uri getUri() {
return Uri.parse(getUriString());
}
public String getUriString() {
if (asset != Asset.XMR) throw new IllegalStateException("We can only do XMR stuff!");
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;
sb.append(BarcodeData.XMR_DESCRIPTION).append('=').append(Uri.encode(description));
}
if ((amount != null) && !amount.isEmpty()) {
sb.append(first ? "?" : "&");
sb.append(BarcodeData.XMR_AMOUNT).append('=').append(amount);
}
return sb.toString();
}
static public BarcodeData fromQrCode(String qrCode) { static public BarcodeData fromQrCode(String qrCode) {
// check for monero uri // check for monero uri
BarcodeData bcData = parseMoneroUri(qrCode); BarcodeData bcData = parseMoneroUri(qrCode);
@@ -74,10 +134,14 @@ public class BarcodeData {
if (bcData == null) { if (bcData == null) {
bcData = parseBitcoinUri(qrCode); bcData = parseBitcoinUri(qrCode);
} }
// check for naked btc addres // check for naked btc address
if (bcData == null) { if (bcData == null) {
bcData = parseBitcoinNaked(qrCode); bcData = parseBitcoinNaked(qrCode);
} }
// check for OpenAlias
if (bcData == null) {
bcData = parseOpenAlias(qrCode);
}
return bcData; return bcData;
} }
@@ -98,7 +162,7 @@ public class BarcodeData {
String noScheme = uri.substring(XMR_SCHEME.length()); String noScheme = uri.substring(XMR_SCHEME.length());
Uri monero = Uri.parse(noScheme); Uri monero = Uri.parse(noScheme);
Map<String, String> parms = new HashMap<>(); Map<String, String> parms = new HashMap<>();
String query = monero.getQuery(); String query = monero.getEncodedQuery();
if (query != null) { if (query != null) {
String[] args = query.split("&"); String[] args = query.split("&");
for (String arg : args) { for (String arg : args) {
@@ -112,6 +176,7 @@ public class BarcodeData {
} }
String address = monero.getPath(); String address = monero.getPath();
String paymentId = parms.get(XMR_PAYMENTID); String paymentId = parms.get(XMR_PAYMENTID);
String description = parms.get(XMR_DESCRIPTION);
String amount = parms.get(XMR_AMOUNT); String amount = parms.get(XMR_AMOUNT);
if (amount != null) { if (amount != null) {
try { try {
@@ -130,7 +195,7 @@ public class BarcodeData {
Timber.d("address invalid"); Timber.d("address invalid");
return null; return null;
} }
return new BarcodeData(Asset.XMR, address, paymentId, amount); return new BarcodeData(Asset.XMR, address, paymentId, description, amount);
} }
static public BarcodeData parseMoneroNaked(String address) { static public BarcodeData parseMoneroNaked(String address) {
@@ -198,4 +263,61 @@ public class BarcodeData {
return new BarcodeData(BarcodeData.Asset.BTC, address); return new BarcodeData(BarcodeData.Asset.BTC, address);
} }
static public BarcodeData parseOpenAlias(String oaString) {
Timber.d("parseOpenAlias=%s", oaString);
if (oaString == null) return null;
Map<String, String> oaAttrs = OpenAliasHelper.parse(oaString);
if (oaAttrs == null) return null;
String oaAsset = oaAttrs.get(OpenAliasHelper.OA1_ASSET);
if (oaAsset == null) return null;
String address = oaAttrs.get(OpenAliasHelper.OA1_ADDRESS);
if (address == null) return null;
Asset asset;
if (OA_XMR_ASSET.equals(oaAsset)) {
if (!Wallet.isAddressValid(address)) {
Timber.d("XMR address invalid");
return null;
}
asset = Asset.XMR;
} else if (OA_BTC_ASSET.equals(oaAsset)) {
if (!BitcoinAddressValidator.validate(address)) {
Timber.d("BTC address invalid");
return null;
}
asset = Asset.BTC;
} else {
Timber.i("Unsupported OpenAlias asset %s", oaAsset);
return null;
}
String paymentId = oaAttrs.get(OpenAliasHelper.OA1_PAYMENTID);
String description = oaAttrs.get(OpenAliasHelper.OA1_DESCRIPTION);
if (description == null) {
description = oaAttrs.get(OpenAliasHelper.OA1_NAME);
}
String amount = oaAttrs.get(OpenAliasHelper.OA1_AMOUNT);
String addressName = oaAttrs.get(OpenAliasHelper.OA1_NAME);
if (amount != null) {
try {
Double.parseDouble(amount);
} catch (NumberFormatException ex) {
Timber.d(ex.getLocalizedMessage());
return null; // we have an amount but its not a number!
}
}
if ((paymentId != null) && !Wallet.isPaymentIdValid(paymentId)) {
Timber.d("paymentId invalid");
return null;
}
BarcodeData bc = new BarcodeData(asset, address, paymentId, description, amount);
bc.setAddressName(addressName);
return bc;
}
} }

View File

@@ -32,6 +32,14 @@ import android.widget.TextView;
import com.m2049r.xmrwallet.BuildConfig; import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import timber.log.Timber;
public class AboutFragment extends DialogFragment { public class AboutFragment extends DialogFragment {
static final String TAG = "AboutFragment"; static final String TAG = "AboutFragment";
@@ -52,7 +60,7 @@ public class AboutFragment extends DialogFragment {
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_about, null); final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_about, null);
((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getString(R.string.about_licenses))); ((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getLicencesHtml()));
((TextView) view.findViewById(R.id.tvVersion)).setText(getString(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); ((TextView) view.findViewById(R.id.tvVersion)).setText(getString(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
@@ -66,4 +74,18 @@ public class AboutFragment extends DialogFragment {
}); });
return builder.create(); return builder.create();
} }
private String getLicencesHtml() {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(getContext().getAssets().open("licenses.html"), StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null)
sb.append(line);
return sb.toString();
} catch (IOException ex) {
Timber.e(ex);
return ex.getLocalizedMessage();
}
}
} }

View File

@@ -17,6 +17,7 @@
package com.m2049r.xmrwallet.fragment.send; package com.m2049r.xmrwallet.fragment.send;
import android.content.Context; import android.content.Context;
import android.nfc.NfcManager;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.support.v7.widget.CardView; import android.support.v7.widget.CardView;
@@ -24,6 +25,7 @@ import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Patterns;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -38,9 +40,12 @@ import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.TxData; import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.data.TxDataBtc; import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator; import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OpenAliasHelper;
import com.m2049r.xmrwallet.util.UserNotes;
import java.util.Map;
import timber.log.Timber; import timber.log.Timber;
@@ -64,6 +69,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
public interface Listener { public interface Listener {
void setBarcodeData(BarcodeData data); void setBarcodeData(BarcodeData data);
BarcodeData getBarcodeData();
void setMode(SendFragment.Mode mode); void setMode(SendFragment.Mode mode);
TxData getTxData(); TxData getTxData();
@@ -72,6 +79,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private EditText etDummy; private EditText etDummy;
private TextInputLayout etAddress; private TextInputLayout etAddress;
private TextInputLayout etPaymentId; private TextInputLayout etPaymentId;
private TextInputLayout etNotes;
private Button bPaymentId; private Button bPaymentId;
private CardView cvScan; private CardView cvScan;
private View tvPaymentIdIntegrated; private View tvPaymentIdIntegrated;
@@ -79,12 +87,12 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private TextView tvXmrTo; private TextView tvXmrTo;
private View llXmrTo; private View llXmrTo;
private boolean resolvingOA = false;
OnScanListener onScanListener; OnScanListener onScanListener;
public interface OnScanListener { public interface OnScanListener {
void onScan(); void onScan();
BarcodeData popScannedData();
} }
@Override @Override
@@ -104,8 +112,13 @@ public class SendAddressWizardFragment extends SendWizardFragment {
etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
if (checkAddress()) { || (actionId == EditorInfo.IME_ACTION_NEXT)) {
String dnsOA = dnsFromOpenAlias(etAddress.getEditText().getText().toString());
Timber.d("OpenAlias is %s", dnsOA);
if (dnsOA != null) {
processOpenAlias(dnsOA);
} else if (checkAddress()) {
if (llPaymentId.getVisibility() == View.VISIBLE) { if (llPaymentId.getVisibility() == View.VISIBLE) {
etPaymentId.requestFocus(); etPaymentId.requestFocus();
} else { } else {
@@ -154,15 +167,14 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} }
}); });
etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId); etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId);
etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (checkPaymentId()) { if (checkPaymentId()) {
etDummy.requestFocus(); etNotes.requestFocus();
Helper.hideKeyboard(getActivity());
} }
return true; return true;
} }
@@ -192,6 +204,20 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} }
}); });
etNotes = (TextInputLayout) view.findViewById(R.id.etNotes);
etNotes.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
etNotes.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_DONE)) {
etDummy.requestFocus();
Helper.hideKeyboard(getActivity());
return true;
}
return false;
}
});
cvScan = (CardView) view.findViewById(R.id.bScan); cvScan = (CardView) view.findViewById(R.id.bScan);
cvScan.setOnClickListener(new View.OnClickListener() { cvScan.setOnClickListener(new View.OnClickListener() {
@Override @Override
@@ -206,9 +232,46 @@ public class SendAddressWizardFragment extends SendWizardFragment {
etDummy.requestFocus(); etDummy.requestFocus();
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
View tvNfc = view.findViewById(R.id.tvNfc);
NfcManager manager = (NfcManager) getContext().getSystemService(Context.NFC_SERVICE);
if ((manager != null) && (manager.getDefaultAdapter() != null))
tvNfc.setVisibility(View.VISIBLE);
return view; return view;
} }
private void processOpenAlias(String dnsOA) {
if (resolvingOA) return; // already resolving - just wait
if (dnsOA != null) {
resolvingOA = true;
etAddress.setError(getString(R.string.send_address_resolve_openalias));
OpenAliasHelper.resolve(dnsOA, new OpenAliasHelper.OnResolvedListener() {
@Override
public void onResolved(Map<BarcodeData.Asset, BarcodeData> dataMap) {
resolvingOA = false;
BarcodeData barcodeData = dataMap.get(BarcodeData.Asset.XMR);
if (barcodeData == null) barcodeData = dataMap.get(BarcodeData.Asset.BTC);
if (barcodeData != null) {
Timber.d("Security=%s, %s", barcodeData.security.toString(), barcodeData.address);
processScannedData(barcodeData);
etDummy.requestFocus();
Helper.hideKeyboard(getActivity());
} else {
etAddress.setError(getString(R.string.send_address_not_openalias));
Timber.d("NO XMR OPENALIAS TXT FOUND");
}
}
@Override
public void onFailure() {
resolvingOA = false;
etAddress.setError(getString(R.string.send_address_not_openalias));
Timber.e("OA FAILED");
}
});
} // else ignore
}
private boolean checkAddressNoError() { private boolean checkAddressNoError() {
String address = etAddress.getEditText().getText().toString(); String address = etAddress.getEditText().getText().toString();
return Wallet.isAddressValid(address) return Wallet.isAddressValid(address)
@@ -255,12 +318,21 @@ public class SendAddressWizardFragment extends SendWizardFragment {
return ok; return ok;
} }
private void shakeAddress() {
etAddress.startAnimation(Helper.getShakeAnimation(getContext()));
}
@Override @Override
public boolean onValidateFields() { public boolean onValidateFields() {
boolean ok = true; boolean ok = true;
if (!checkAddressNoError()) { if (!checkAddressNoError()) {
etAddress.startAnimation(Helper.getShakeAnimation(getContext())); shakeAddress();
ok = false; ok = false;
String dnsOA = dnsFromOpenAlias(etAddress.getEditText().getText().toString());
Timber.d("OpenAlias is %s", dnsOA);
if (dnsOA != null) {
processOpenAlias(dnsOA);
}
} }
if (!checkPaymentId()) { if (!checkPaymentId()) {
etPaymentId.startAnimation(Helper.getShakeAnimation(getContext())); etPaymentId.startAnimation(Helper.getShakeAnimation(getContext()));
@@ -277,6 +349,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
txData.setDestinationAddress(etAddress.getEditText().getText().toString()); txData.setDestinationAddress(etAddress.getEditText().getText().toString());
txData.setPaymentId(etPaymentId.getEditText().getText().toString()); txData.setPaymentId(etPaymentId.getEditText().getText().toString());
} }
txData.setUserNotes(new UserNotes(etNotes.getEditText().getText().toString()));
} }
return true; return true;
} }
@@ -298,27 +371,49 @@ public class SendAddressWizardFragment extends SendWizardFragment {
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
Timber.d("onResume"); Timber.d("onResume");
BarcodeData data = onScanListener.popScannedData(); processScannedData();
sendListener.setBarcodeData(data); }
if (data != null) {
public void processScannedData(BarcodeData barcodeData) {
sendListener.setBarcodeData(barcodeData);
if (isResumed())
processScannedData();
}
public void processScannedData() {
BarcodeData barcodeData = sendListener.getBarcodeData();
if (barcodeData != null) {
Timber.d("GOT DATA"); Timber.d("GOT DATA");
String scannedAddress = data.address; String scannedAddress = barcodeData.address;
if (scannedAddress != null) { if (scannedAddress != null) {
etAddress.getEditText().setText(scannedAddress); etAddress.getEditText().setText(scannedAddress);
checkAddress(); if (checkAddress()) {
if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC)
etAddress.setError(getString(R.string.send_address_no_dnssec));
else if (barcodeData.security == BarcodeData.Security.OA_DNSSEC)
etAddress.setError(getString(R.string.send_address_openalias));
}
} else { } else {
etAddress.getEditText().getText().clear(); etAddress.getEditText().getText().clear();
etAddress.setError(null); etAddress.setError(null);
} }
String scannedPaymenId = data.paymentId; String scannedPaymentId = barcodeData.paymentId;
if (scannedPaymenId != null) { if (scannedPaymentId != null) {
etPaymentId.getEditText().setText(scannedPaymenId); etPaymentId.getEditText().setText(scannedPaymentId);
checkPaymentId(); checkPaymentId();
} else { } else {
etPaymentId.getEditText().getText().clear(); etPaymentId.getEditText().getText().clear();
etPaymentId.setError(null); etPaymentId.setError(null);
} }
String scannedNotes = barcodeData.description;
if (scannedNotes != null) {
etNotes.getEditText().setText(scannedNotes);
} else {
etNotes.getEditText().getText().clear();
etNotes.setError(null);
} }
} else
Timber.d("barcodeData=null");
} }
@Override @Override
@@ -328,4 +423,14 @@ public class SendAddressWizardFragment extends SendWizardFragment {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
etDummy.requestFocus(); etDummy.requestFocus();
} }
String dnsFromOpenAlias(String openalias) {
Timber.d("checking openalias candidate %s", openalias);
if (Patterns.DOMAIN_NAME.matcher(openalias).matches()) return openalias;
if (Patterns.EMAIL_ADDRESS.matcher(openalias).matches()) {
openalias = openalias.replaceFirst("@", ".");
if (Patterns.DOMAIN_NAME.matcher(openalias).matches()) return openalias;
}
return null; // not an openalias
}
} }

View File

@@ -216,9 +216,9 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
} }
@Override @Override
public void sendFailed() { public void sendFailed(String error) {
Timber.e("SEND FAILED");
pbProgressSend.setVisibility(View.INVISIBLE); pbProgressSend.setVisibility(View.INVISIBLE);
Toast.makeText(getContext(), getString(R.string.status_transaction_failed, error), Toast.LENGTH_LONG).show();
} }
@Override @Override
@@ -421,7 +421,8 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
// accept keyboard "ok" // accept keyboard "ok"
etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
String pass = etPassword.getEditText().getText().toString(); String pass = etPassword.getEditText().getText().toString();
if (getActivityCallback().verifyWalletPassword(pass)) { if (getActivityCallback().verifyWalletPassword(pass)) {
Helper.hideKeyboardAlways(activity); Helper.hideKeyboardAlways(activity);

View File

@@ -19,7 +19,7 @@ package com.m2049r.xmrwallet.fragment.send;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
interface SendConfirm { interface SendConfirm {
void sendFailed(); void sendFailed(String errorText);
void createTransactionFailed(String errorText); void createTransactionFailed(String errorText);

View File

@@ -145,17 +145,22 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
} }
@Override @Override
public void sendFailed() { public void sendFailed(String errorText) {
pbProgressSend.setVisibility(View.INVISIBLE); pbProgressSend.setVisibility(View.INVISIBLE);
showAlert(getString(R.string.send_create_tx_error_title), errorText);
} }
@Override @Override
public void createTransactionFailed(String errorText) { public void createTransactionFailed(String errorText) {
hideProgress(); hideProgress();
showAlert(getString(R.string.send_create_tx_error_title), errorText);
}
private void showAlert(String title, String message) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setCancelable(true). builder.setCancelable(true).
setTitle(getString(R.string.send_create_tx_error_title)). setTitle(title).
setMessage(errorText). setMessage(message).
create(). create().
show(); show();
} }
@@ -297,7 +302,8 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
// accept keyboard "ok" // accept keyboard "ok"
etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
String pass = etPassword.getEditText().getText().toString(); String pass = etPassword.getEditText().getText().toString();
if (getActivityCallback().verifyWalletPassword(pass)) { if (getActivityCallback().verifyWalletPassword(pass)) {
Helper.hideKeyboardAlways(activity); Helper.hideKeyboardAlways(activity);

View File

@@ -37,16 +37,15 @@ import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.OnBackPressedListener; import com.m2049r.xmrwallet.OnBackPressedListener;
import com.m2049r.xmrwallet.OnUriScannedListener;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.PendingTx; import com.m2049r.xmrwallet.data.PendingTx;
import com.m2049r.xmrwallet.data.TxData; import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.data.TxDataBtc; import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.layout.SpendViewPager; import com.m2049r.xmrwallet.layout.SpendViewPager;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.NodeList;
import com.m2049r.xmrwallet.util.Notice; import com.m2049r.xmrwallet.util.Notice;
import com.m2049r.xmrwallet.util.UserNotes; import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.widget.DotBar; import com.m2049r.xmrwallet.widget.DotBar;
@@ -62,7 +61,7 @@ public class SendFragment extends Fragment
SendSettingsWizardFragment.Listener, SendSettingsWizardFragment.Listener,
SendConfirmWizardFragment.Listener, SendConfirmWizardFragment.Listener,
SendSuccessWizardFragment.Listener, SendSuccessWizardFragment.Listener,
OnBackPressedListener { OnBackPressedListener, OnUriScannedListener {
private Listener activityCallback; private Listener activityCallback;
@@ -86,6 +85,8 @@ public class SendFragment extends Fragment
void setTitle(String title); void setTitle(String title);
void setSubtitle(String subtitle); void setSubtitle(String subtitle);
void setOnUriScannedListener(OnUriScannedListener onUriScannedListener);
} }
private EditText etDummy; private EditText etDummy;
@@ -99,10 +100,6 @@ public class SendFragment extends Fragment
private Button bDone; private Button bDone;
private View llXmrToEnabled;
private View ibXmrToInfoClose;
static private int MAX_FALLBACK = Integer.MAX_VALUE; static private int MAX_FALLBACK = Integer.MAX_VALUE;
@Override @Override
@@ -226,13 +223,20 @@ public class SendFragment extends Fragment
Timber.d("onAttach %s", context); Timber.d("onAttach %s", context);
super.onAttach(context); super.onAttach(context);
if (context instanceof Listener) { if (context instanceof Listener) {
this.activityCallback = (Listener) context; activityCallback = (Listener) context;
activityCallback.setOnUriScannedListener(this);
} else { } else {
throw new ClassCastException(context.toString() throw new ClassCastException(context.toString()
+ " must implement Listener"); + " must implement Listener");
} }
} }
@Override
public void onDetach() {
activityCallback.setOnUriScannedListener(null);
super.onDetach();
}
private SpendViewPager spendViewPager; private SpendViewPager spendViewPager;
private SpendPagerAdapter pagerAdapter; private SpendPagerAdapter pagerAdapter;
@@ -247,6 +251,18 @@ public class SendFragment extends Fragment
} }
} }
@Override
public boolean onUriScanned(BarcodeData barcodeData) {
if (spendViewPager.getCurrentItem() == SpendPagerAdapter.POS_ADDRESS) {
final SendWizardFragment fragment = pagerAdapter.getFragment(SpendPagerAdapter.POS_ADDRESS);
if (fragment instanceof SendAddressWizardFragment) {
((SendAddressWizardFragment) fragment).processScannedData(barcodeData);
return true;
}
}
return false;
}
enum Mode { enum Mode {
XMR, BTC XMR, BTC
} }
@@ -414,8 +430,14 @@ public class SendFragment extends Fragment
barcodeData = data; barcodeData = data;
} }
@Override
public BarcodeData getBarcodeData() {
return barcodeData;
}
@Override @Override
public BarcodeData popBarcodeData() { public BarcodeData popBarcodeData() {
Timber.d("POPPED");
BarcodeData data = barcodeData; BarcodeData data = barcodeData;
barcodeData = null; barcodeData = null;
return data; return data;
@@ -512,12 +534,11 @@ public class SendFragment extends Fragment
public void onSendTransactionFailed(final String error) { public void onSendTransactionFailed(final String error) {
Timber.d("error=%s", error); Timber.d("error=%s", error);
committedTx = null; committedTx = null;
Toast.makeText(getContext(), getString(R.string.status_transaction_failed, error), Toast.LENGTH_SHORT).show(); final SendConfirm confirm = getSendConfirm();
enableNavigation(); if (confirm != null) {
final SendConfirm fragment = getSendConfirm(); confirm.sendFailed(getString(R.string.status_transaction_failed, error));
if (fragment != null) {
fragment.sendFailed();
} }
enableNavigation();
} }
@Override @Override

View File

@@ -36,7 +36,7 @@ import com.m2049r.xmrwallet.util.UserNotes;
import timber.log.Timber; import timber.log.Timber;
public class SendSettingsWizardFragment extends SendWizardFragment { public class SendSettingsWizardFragment extends SendWizardFragment {
final static public int MIXIN = 6; final static public int MIXIN = 10;
public static SendSettingsWizardFragment newInstance(Listener listener) { public static SendSettingsWizardFragment newInstance(Listener listener) {
SendSettingsWizardFragment instance = new SendSettingsWizardFragment(); SendSettingsWizardFragment instance = new SendSettingsWizardFragment();
@@ -62,10 +62,8 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
PendingTransaction.Priority.Priority_High}; // must match the layout XML PendingTransaction.Priority.Priority_High}; // must match the layout XML
private Spinner sPriority; private Spinner sPriority;
private EditText etNotes;
private EditText etDummy; private EditText etDummy;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
@@ -77,19 +75,6 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
sPriority = (Spinner) view.findViewById(R.id.sPriority); sPriority = (Spinner) view.findViewById(R.id.sPriority);
etNotes = (EditText) view.findViewById(R.id.etNotes);
etNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
etNotes.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
etDummy.requestFocus();
Helper.hideKeyboard(getActivity());
return true;
}
return false;
}
});
etDummy = (EditText) view.findViewById(R.id.etDummy); etDummy = (EditText) view.findViewById(R.id.etDummy);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
@@ -102,7 +87,6 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
TxData txData = sendListener.getTxData(); TxData txData = sendListener.getTxData();
txData.setPriority(Priorities[sPriority.getSelectedItemPosition()]); txData.setPriority(Priorities[sPriority.getSelectedItemPosition()]);
txData.setMixin(MIXIN); txData.setMixin(MIXIN);
txData.setUserNotes(new UserNotes(etNotes.getText().toString()));
} }
return true; return true;
} }

View File

@@ -34,11 +34,9 @@ import java.io.IOException;
import timber.log.Timber; import timber.log.Timber;
public class Ledger { public class Ledger {
// lookahead parameters as suggest on // 5:20 is same as wallet2.cpp::restore()
// https://monero.stackexchange.com/a/9902/8977 (Step 8) static public final int LOOKAHEAD_ACCOUNTS = 5;
// by dEBRUYNE static public final int LOOKAHEAD_SUBADDRESSES = 20;
static public final int LOOKAHEAD_ACCOUNTS = 3;
static public final int LOOKAHEAD_SUBADDRESSES = 100;
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES; static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
public static final int SW_OK = 0x9000; public static final int SW_OK = 0x9000;

View File

@@ -60,6 +60,12 @@ public class Wallet {
this.accountIndex = accountIndex; this.accountIndex = accountIndex;
} }
public enum Device {
Device_Undefined,
Device_Software,
Device_Ledger
};
public enum Status { public enum Status {
Status_Ok, Status_Ok,
Status_Error, Status_Error,
@@ -150,6 +156,11 @@ public class Wallet {
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0; // virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0; // virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
public native void setRestoreHeight(long height);
public native long getRestoreHeight();
// virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0; // virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0;
// virtual bool connectToDaemon() = 0; // virtual bool connectToDaemon() = 0;
@@ -389,5 +400,11 @@ public class Wallet {
return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1); return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1);
} }
public native boolean isKeyOnDevice(); public Wallet.Device getDeviceType() {
int device = getDeviceTypeJ();
return Wallet.Device.values()[device + 1]; // mapping is monero+1=android
}
private native int getDeviceTypeJ();
} }

View File

@@ -18,6 +18,7 @@ package com.m2049r.xmrwallet.model;
import com.m2049r.xmrwallet.data.WalletNode; import com.m2049r.xmrwallet.data.WalletNode;
import com.m2049r.xmrwallet.ledger.Ledger; import com.m2049r.xmrwallet.ledger.Ledger;
import com.m2049r.xmrwallet.util.RestoreHeight;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@@ -25,6 +26,7 @@ import java.io.FileReader;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import timber.log.Timber; import timber.log.Timber;
@@ -76,6 +78,13 @@ public class WalletManager {
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, getNetworkType().getValue()); long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, getNetworkType().getValue());
Wallet wallet = new Wallet(walletHandle); Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet); manageWallet(wallet);
if (wallet.getStatus() == Wallet.Status.Status_Ok) {
// (Re-)Estimate restore height based on what we know
long oldHeight = wallet.getRestoreHeight();
wallet.setRestoreHeight(RestoreHeight.getInstance().getHeight(new Date()));
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)
}
return wallet; return wallet;
} }
@@ -169,10 +178,15 @@ public class WalletManager {
public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only); public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
public boolean verifyWalletPasswordOnly(String keys_file_name, String password) { public boolean verifyWalletPasswordOnly(String keys_file_name, String password) {
return queryWalletHardware(keys_file_name, password) >= 0; return queryWalletDevice(keys_file_name, password) != Wallet.Device.Device_Undefined;
} }
public native int queryWalletHardware(String keys_file_name, String password); public Wallet.Device queryWalletDevice(String keys_file_name, String password) {
int device = queryWalletDeviceJ(keys_file_name, password);
return Wallet.Device.values()[device + 1]; // mapping is monero+1=android
}
private native int queryWalletDeviceJ(String keys_file_name, String password);
//public native List<String> findWallets(String path); // this does not work - some error in boost //public native List<String> findWallets(String path); // this does not work - some error in boost

View File

@@ -17,17 +17,22 @@
package com.m2049r.xmrwallet.service; package com.m2049r.xmrwallet.service;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Binder; import android.os.Binder;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.Process; import android.os.Process;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.WalletActivity; import com.m2049r.xmrwallet.WalletActivity;
@@ -45,6 +50,7 @@ public class WalletService extends Service {
public static boolean Running = false; public static boolean Running = false;
final static int NOTIFICATION_ID = 2049; final static int NOTIFICATION_ID = 2049;
final static String CHANNEL_ID = "m_service";
public static final String REQUEST_WALLET = "wallet"; public static final String REQUEST_WALLET = "wallet";
public static final String REQUEST = "request"; public static final String REQUEST = "request";
@@ -220,9 +226,9 @@ public class WalletService extends Service {
void onSetNotes(boolean success); void onSetNotes(boolean success);
void onWalletStarted(boolean success); void onWalletStarted(Wallet.ConnectionStatus walletStatus);
void onWalletOpen(int hardware); void onWalletOpen(Wallet.Device device);
} }
String progressText = null; String progressText = null;
@@ -287,9 +293,9 @@ public class WalletService extends Service {
if (walletId != null) { if (walletId != null) {
showProgress(getString(R.string.status_wallet_loading)); showProgress(getString(R.string.status_wallet_loading));
showProgress(10); showProgress(10);
boolean success = start(walletId, walletPw); Wallet.ConnectionStatus connStatus = start(walletId, walletPw);
if (observer != null) observer.onWalletStarted(success); if (observer != null) observer.onWalletStarted(connStatus);
if (!success) { if (connStatus != Wallet.ConnectionStatus.ConnectionStatus_Connected) {
errorState = true; errorState = true;
stop(); stop();
} }
@@ -340,19 +346,21 @@ public class WalletService extends Service {
Wallet myWallet = getWallet(); Wallet myWallet = getWallet();
Timber.d("SEND TX for wallet: %s", myWallet.getName()); Timber.d("SEND TX for wallet: %s", myWallet.getName());
PendingTransaction pendingTransaction = myWallet.getPendingTransaction(); PendingTransaction pendingTransaction = myWallet.getPendingTransaction();
if ((pendingTransaction == null) if (pendingTransaction == null) {
|| (pendingTransaction.getStatus() != PendingTransaction.Status.Status_Ok)) { throw new IllegalArgumentException("PendingTransaction is null"); // die
}
if (pendingTransaction.getStatus() != PendingTransaction.Status.Status_Ok) {
Timber.e("PendingTransaction is %s", pendingTransaction.getStatus()); Timber.e("PendingTransaction is %s", pendingTransaction.getStatus());
final String error = pendingTransaction.getErrorString(); final String error = pendingTransaction.getErrorString();
myWallet.disposePendingTransaction(); // it's broken anyway myWallet.disposePendingTransaction(); // it's broken anyway
if (observer != null) observer.onSendTransactionFailed(error); if (observer != null) observer.onSendTransactionFailed(error);
return; return;
} }
final String txid = pendingTransaction.getFirstTxId(); final String txid = pendingTransaction.getFirstTxId(); // tx ids vanish after commit()!
boolean success = pendingTransaction.commit("", true); boolean success = pendingTransaction.commit("", true);
if (success) {
myWallet.disposePendingTransaction(); myWallet.disposePendingTransaction();
if (observer != null) observer.onTransactionSent(txid); if (observer != null) observer.onTransactionSent(txid);
if (success) {
String notes = extras.getString(REQUEST_CMD_SEND_NOTES); String notes = extras.getString(REQUEST_CMD_SEND_NOTES);
if ((notes != null) && (!notes.isEmpty())) { if ((notes != null) && (!notes.isEmpty())) {
myWallet.setUserNote(txid, notes); myWallet.setUserNote(txid, notes);
@@ -364,6 +372,11 @@ public class WalletService extends Service {
} }
if (observer != null) observer.onWalletStored(rc); if (observer != null) observer.onWalletStored(rc);
listener.updated = true; listener.updated = true;
} else {
final String error = pendingTransaction.getErrorString();
myWallet.disposePendingTransaction();
if (observer != null) observer.onSendTransactionFailed(error);
return;
} }
} else if (cmd.equals(REQUEST_CMD_SETNOTE)) { } else if (cmd.equals(REQUEST_CMD_SETNOTE)) {
Wallet myWallet = getWallet(); Wallet myWallet = getWallet();
@@ -477,7 +490,7 @@ public class WalletService extends Service {
return true; // true is important so that onUnbind is also called next time return true; // true is important so that onUnbind is also called next time
} }
private boolean start(String walletName, String walletPassword) { private Wallet.ConnectionStatus start(String walletName, String walletPassword) {
Timber.d("start()"); Timber.d("start()");
startNotfication(); startNotfication();
showProgress(getString(R.string.status_wallet_loading)); showProgress(getString(R.string.status_wallet_loading));
@@ -485,9 +498,11 @@ public class WalletService extends Service {
if (listener == null) { if (listener == null) {
Timber.d("start() loadWallet"); Timber.d("start() loadWallet");
Wallet aWallet = loadWallet(walletName, walletPassword); Wallet aWallet = loadWallet(walletName, walletPassword);
if ((aWallet == null) || (aWallet.getConnectionStatus() != Wallet.ConnectionStatus.ConnectionStatus_Connected)) { Wallet.ConnectionStatus connStatus = Wallet.ConnectionStatus.ConnectionStatus_Disconnected;
if (aWallet != null) connStatus = aWallet.getConnectionStatus();
if (connStatus != Wallet.ConnectionStatus.ConnectionStatus_Connected) {
if (aWallet != null) aWallet.close(); if (aWallet != null) aWallet.close();
return false; return connStatus;
} }
listener = new MyWalletListener(); listener = new MyWalletListener();
listener.start(); listener.start();
@@ -498,7 +513,7 @@ public class WalletService extends Service {
// if we try to refresh the history here we get occasional segfaults! // 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 // doesnt matter since we update as soon as we get a new block anyway
Timber.d("start() done"); Timber.d("start() done");
return true; return Wallet.ConnectionStatus.ConnectionStatus_Connected;
} }
public void stop() { public void stop() {
@@ -537,8 +552,9 @@ public class WalletService extends Service {
showProgress(30); showProgress(30);
if (walletMgr.walletExists(path)) { if (walletMgr.walletExists(path)) {
Timber.d("open wallet %s", path); Timber.d("open wallet %s", path);
int hw = WalletManager.getInstance().queryWalletHardware(path + ".keys", walletPassword); Wallet.Device device = WalletManager.getInstance().queryWalletDevice(path + ".keys", walletPassword);
if (observer != null) observer.onWalletOpen(hw); Timber.d("device is %s", device.toString());
if (observer != null) observer.onWalletOpen(device);
wallet = walletMgr.openWallet(path, walletPassword); wallet = walletMgr.openWallet(path, walletPassword);
showProgress(60); showProgress(60);
Timber.d("wallet opened"); Timber.d("wallet opened");
@@ -559,11 +575,26 @@ public class WalletService extends Service {
private void startNotfication() { private void startNotfication() {
Intent notificationIntent = new Intent(this, WalletActivity.class); Intent notificationIntent = new Intent(this, WalletActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification = new Notification.Builder(this)
String channelId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? createNotificationChannel() : "";
Notification notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle(getString(R.string.service_description)) .setContentTitle(getString(R.string.service_description))
.setOngoing(true)
.setSmallIcon(R.drawable.ic_monerujo) .setSmallIcon(R.drawable.ic_monerujo)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.build(); .build();
startForeground(NOTIFICATION_ID, notification); startForeground(NOTIFICATION_ID, notification);
} }
@RequiresApi(Build.VERSION_CODES.O)
private String createNotificationChannel() {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, getString(R.string.service_description),
NotificationManager.IMPORTANCE_LOW);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
notificationManager.createNotificationChannel(channel);
return CHANNEL_ID;
}
} }

View File

@@ -76,10 +76,14 @@ import okhttp3.HttpUrl;
import timber.log.Timber; import timber.log.Timber;
public class Helper { public class Helper {
static private final String FLAVOR_SUFFIX =
(BuildConfig.FLAVOR.equals("prod") ? "" : "." + BuildConfig.FLAVOR)
+ (BuildConfig.DEBUG ? "-debug" : "");
static public final String CRYPTO = "XMR"; static public final String CRYPTO = "XMR";
static private final String WALLET_DIR = "monerujo" + (BuildConfig.DEBUG ? "-debug" : ""); static private final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
static private final String HOME_DIR = "monero" + (BuildConfig.DEBUG ? "-debug" : ""); static private final String HOME_DIR = "monero" + FLAVOR_SUFFIX;
static public int DISPLAY_DIGITS_INFO = 5; static public int DISPLAY_DIGITS_INFO = 5;
@@ -563,7 +567,8 @@ public class Helper {
// accept keyboard "ok" // accept keyboard "ok"
etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
String pass = etPassword.getEditText().getText().toString(); String pass = etPassword.getEditText().getText().toString();
if (loginTask == null) { if (loginTask == null) {
loginTask = new LoginWalletTask(pass, false); loginTask = new LoginWalletTask(pass, false);

View File

@@ -0,0 +1,245 @@
/*
* Copyright (c) 2018 m2049r
*
* 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.
*/
// Specs from https://openalias.org/
package com.m2049r.xmrwallet.util;
import android.os.AsyncTask;
import com.m2049r.xmrwallet.data.BarcodeData;
import org.jitsi.dnssec.validator.ValidatingResolver;
import org.xbill.DNS.DClass;
import org.xbill.DNS.Flags;
import org.xbill.DNS.Message;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Rcode;
import org.xbill.DNS.Record;
import org.xbill.DNS.Section;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TXTRecord;
import org.xbill.DNS.Type;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import timber.log.Timber;
public class OpenAliasHelper {
public static final String OA1_SCHEME = "oa1:";
public static final String OA1_ASSET = "asset";
public static final String OA1_ADDRESS = "recipient_address";
public static final String OA1_NAME = "recipient_name";
public static final String OA1_DESCRIPTION = "tx_description";
public static final String OA1_AMOUNT = "tx_amount";
public static final String OA1_PAYMENTID = "tx_payment_id";
public static final int DNS_LOOKUP_TIMEOUT = 2500; // ms
public static void resolve(String name, OnResolvedListener resolvedListener) {
new DnsTxtResolver(resolvedListener).execute(name);
}
public static Map<String, String> parse(String oaString) {
return new OpenAliasParser(oaString).parse();
}
public interface OnResolvedListener {
void onResolved(Map<BarcodeData.Asset, BarcodeData> dataMap);
void onFailure();
}
private static class DnsTxtResolver extends AsyncTask<String, Void, Boolean> {
List<String> txts = new ArrayList<>();
boolean dnssec = false;
private final OnResolvedListener resolvedListener;
private DnsTxtResolver(OnResolvedListener resolvedListener) {
this.resolvedListener = resolvedListener;
}
// trust anchor of the root zone
// http://data.iana.org/root-anchors/root-anchors.xml
final String ROOT =
". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5\n" +
". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D";
final String[] DNSSEC_SERVERS = {
"4.2.2.1", // Level3
"4.2.2.2", // Level3
"4.2.2.6", // Level3
"1.1.1.1", // cloudflare
"9.9.9.9", // quad9
"8.8.4.4", // google
"8.8.8.8" // google
};
@Override
protected Boolean doInBackground(String... args) {
//main();
if (args.length != 1) return false;
String name = args[0];
if ((name == null) || (name.isEmpty()))
return false; //pointless trying to lookup nothing
Timber.d("Resolving %s", name);
try {
SimpleResolver sr = new SimpleResolver(DNSSEC_SERVERS[new Random().nextInt(DNSSEC_SERVERS.length)]);
ValidatingResolver vr = new ValidatingResolver(sr);
vr.setTimeout(0, DNS_LOOKUP_TIMEOUT);
vr.loadTrustAnchors(new ByteArrayInputStream(ROOT.getBytes("ASCII")));
Record qr = Record.newRecord(Name.fromConstantString(name + "."), Type.TXT, DClass.IN);
Message response = vr.send(Message.newQuery(qr));
final int rcode = response.getRcode();
if (rcode != Rcode.NOERROR) {
Timber.i("Rcode: %s", Rcode.string(rcode));
for (RRset set : response.getSectionRRsets(Section.ADDITIONAL)) {
if (set.getName().equals(Name.root) && set.getType() == Type.TXT
&& set.getDClass() == ValidatingResolver.VALIDATION_REASON_QCLASS) {
Timber.i("Reason: %s", ((TXTRecord) set.first()).getStrings().get(0));
}
}
return false;
} else {
dnssec = response.getHeader().getFlag(Flags.AD);
for (Record record : response.getSectionArray(Section.ANSWER)) {
if (record.getType() == Type.TXT) {
txts.addAll(((TXTRecord) record).getStrings());
}
}
}
} catch (IOException | IllegalArgumentException ex) {
return false;
}
return true;
}
@Override
public void onPostExecute(Boolean success) {
if (resolvedListener != null)
if (success) {
Map<BarcodeData.Asset, BarcodeData> dataMap = new HashMap<>();
for (String txt : txts) {
BarcodeData bc = BarcodeData.parseOpenAlias(txt);
if (bc != null) {
bc.setSecurity(dnssec ? BarcodeData.Security.OA_DNSSEC : BarcodeData.Security.OA_NO_DNSSEC);
if (!dataMap.containsKey(bc.asset)) {
dataMap.put(bc.asset, bc);
}
}
}
resolvedListener.onResolved(dataMap);
} else {
resolvedListener.onFailure();
}
}
}
private static class OpenAliasParser {
int currentPos = 0;
final String oaString;
StringBuilder sb = new StringBuilder();
OpenAliasParser(String oaString) {
this.oaString = oaString;
}
Map<String, String> parse() {
if ((oaString == null) || !oaString.startsWith(OA1_SCHEME)) return null;
if (oaString.charAt(oaString.length() - 1) != ';') return null;
Map<String, String> oaAttributes = new HashMap<>();
final int assetEnd = oaString.indexOf(' ');
if (assetEnd > 20) return null; // random sanity check
String asset = oaString.substring(OA1_SCHEME.length(), assetEnd);
oaAttributes.put(OA1_ASSET, asset);
boolean inQuote = false;
boolean inKey = true;
String key = null;
for (currentPos = assetEnd; currentPos < oaString.length() - 1; currentPos++) {
char c = currentChar();
if (inKey) {
if ((sb.length() == 0) && Character.isWhitespace(c)) continue;
if ((c == '\\') || (c == ';')) return null;
if (c == '=') {
key = sb.toString();
if (oaAttributes.containsKey(key)) return null; // no duplicate keys allowed
sb.setLength(0);
inKey = false;
} else {
sb.append(c);
}
continue;
}
// now we are in the value
if ((sb.length() == 0) && (c == '"')) {
inQuote = true;
continue;
}
if ((!inQuote || ((sb.length() > 0) && (c == '"'))) && (nextChar() == ';')) {
if (!inQuote) appendCurrentEscapedChar();
oaAttributes.put(key, sb.toString());
sb.setLength(0);
currentPos++; // skip the next ;
inQuote = false;
inKey = true;
key = null;
continue;
}
appendCurrentEscapedChar();
}
if (inQuote) return null;
if (key != null) {
oaAttributes.put(key, sb.toString());
}
return oaAttributes;
}
char currentChar() {
return oaString.charAt(currentPos);
}
char nextChar() throws IndexOutOfBoundsException {
int pos = currentPos;
char c = oaString.charAt(pos);
if (c == '\\') {
pos++;
}
return oaString.charAt(pos + 1);
}
void appendCurrentEscapedChar() throws IndexOutOfBoundsException {
char c = oaString.charAt(currentPos);
if (c == '\\') {
c = oaString.charAt(++currentPos);
}
sb.append(c);
}
}
}

View File

@@ -94,6 +94,7 @@ public class RestoreHeight {
blockheight.put("2018-06-01", 1585135L); blockheight.put("2018-06-01", 1585135L);
blockheight.put("2018-07-01", 1606715L); blockheight.put("2018-07-01", 1606715L);
blockheight.put("2018-08-01", 1629017L); blockheight.put("2018-08-01", 1629017L);
blockheight.put("2018-09-01", 1651347L);
} }
public long getHeight(String date) { public long getHeight(String date) {

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