mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-06 02:27:11 +02:00
Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b11357f379 | ||
![]() |
eba0156a6d | ||
![]() |
bf64d8bd89 | ||
![]() |
2d74281b31 | ||
![]() |
668cefb357 | ||
![]() |
1f5061df38 | ||
![]() |
51445f5941 | ||
![]() |
8c01ec36e8 | ||
![]() |
3cf84c599d | ||
![]() |
ead8564688 | ||
![]() |
b71b3badd8 | ||
![]() |
5ad46e2f54 | ||
![]() |
9d6895b60f | ||
![]() |
a8755ee0da | ||
![]() |
f186bc9d4f | ||
![]() |
9cb961a368 | ||
![]() |
aa78541b6f | ||
![]() |
eba6a78004 | ||
![]() |
8587eab41c | ||
![]() |
4271c743c7 | ||
![]() |
5b60987692 | ||
![]() |
e1bd04c945 | ||
![]() |
ef29db62c4 | ||
![]() |
82ce12e27c | ||
![]() |
4211687981 | ||
![]() |
d398416a3d | ||
![]() |
1844d84be8 | ||
![]() |
5101b7da5e | ||
![]() |
3e90f2e22e | ||
![]() |
f01a8eac5d | ||
![]() |
0c13338dc0 | ||
![]() |
78d6fef3e4 | ||
![]() |
cbddcdc92d | ||
![]() |
4289aaac77 | ||
![]() |
fd15da9c84 | ||
![]() |
c3357087d5 | ||
![]() |
47e0bc86fd | ||
![]() |
dbd45ea4f5 | ||
![]() |
90c3e6ef8b | ||
![]() |
bc6a816462 | ||
![]() |
274a1a25f1 | ||
![]() |
9e3a2b102e | ||
![]() |
647ae9a565 | ||
![]() |
2bbd20855f | ||
![]() |
b8fc5f910a | ||
![]() |
01700cf780 | ||
![]() |
2fa6286b0e | ||
![]() |
0ac1680e75 | ||
![]() |
bd9a98e0d8 | ||
![]() |
8a5121e792 | ||
![]() |
5e6d3f3032 | ||
![]() |
65991ff554 | ||
![]() |
b0629e46e8 | ||
![]() |
45ec3198a0 | ||
![]() |
9b66c466f2 | ||
![]() |
d257e183ad | ||
![]() |
10d8e441fe | ||
![]() |
9f9bc4793d | ||
![]() |
8b28e3ea1e | ||
![]() |
e394394538 | ||
![]() |
7424ef07f7 | ||
![]() |
f5ce6ec824 | ||
![]() |
4215a8bf9e | ||
![]() |
8b016f93dc | ||
![]() |
b239a5094b | ||
![]() |
6a2de36578 | ||
![]() |
5aded68c53 | ||
![]() |
d78a2be120 | ||
![]() |
e6c7800911 | ||
![]() |
cbbe079f67 | ||
![]() |
7fc2dc3ba1 | ||
![]() |
43204d64ef | ||
![]() |
1433143a39 | ||
![]() |
a9a78393a9 | ||
![]() |
fe7ab31050 | ||
![]() |
bf5ed793b3 | ||
![]() |
679bae5f42 | ||
![]() |
e3ccda910e | ||
![]() |
ae75a34977 | ||
![]() |
03efedf35c | ||
![]() |
403dbdf14f | ||
![]() |
0bf3c6f099 | ||
![]() |
1b0ac1c481 | ||
![]() |
dc95539fc1 | ||
![]() |
023fb9e215 | ||
![]() |
92728026c7 | ||
![]() |
8fd9598c6c | ||
![]() |
6633261ba2 | ||
![]() |
dc8c8634cb | ||
![]() |
dcf9b6db15 | ||
![]() |
654d63c32e | ||
![]() |
caf91fccfd | ||
![]() |
41f1f3dec0 | ||
![]() |
bb66d1f68d | ||
![]() |
310548b031 | ||
![]() |
ad7737475f | ||
![]() |
b2d07a65b7 | ||
![]() |
5dfcaae5b9 | ||
![]() |
3c91fc060c | ||
![]() |
f8f113faab | ||
![]() |
268a00cb3e | ||
![]() |
878500ae71 | ||
![]() |
a0debb0f7e | ||
![]() |
3a71e8d352 | ||
![]() |
af68c5e51f | ||
![]() |
5a2b48a087 |
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -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">
|
||||||
|
13
README.md
13
README.md
@@ -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.
|
||||||
|
|
||||||
|
2
app/.gitignore
vendored
2
app/.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
build
|
build
|
||||||
app.iml
|
app.iml
|
||||||
|
prod
|
||||||
|
alpha
|
||||||
|
@@ -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}
|
||||||
)
|
)
|
||||||
|
@@ -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 99
|
versionCode 134
|
||||||
versionName "1.5.9 'Maximum Nacho'"
|
versionName "1.8.4 '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"
|
||||||
@@ -45,27 +56,40 @@ android {
|
|||||||
// Map for the version code that gives each ABI a value.
|
// Map for the version code that gives each ABI a value.
|
||||||
def abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
|
def abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
|
||||||
|
|
||||||
|
// Enumerate translated locales
|
||||||
|
def availableLocales = ["en"]
|
||||||
|
new File("app/src/main/res/").eachFileMatch(~/^values-.*/) { file ->
|
||||||
|
def languageTag = file.name.substring(7).replace("-r", "-")
|
||||||
|
availableLocales.add(languageTag)
|
||||||
|
}
|
||||||
|
|
||||||
// APKs for the same app that all have the same version information.
|
// APKs for the same app that all have the same version information.
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
|
// Update string resource: available_locales
|
||||||
|
variant.resValue("string", "available_locales", availableLocales.join(","))
|
||||||
// Assigns a different version code for each output APK.
|
// Assigns a different version code for each output APK.
|
||||||
variant.outputs.all {
|
variant.outputs.all {
|
||||||
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"
|
||||||
@@ -73,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"
|
||||||
|
BIN
app/src/alpha/ic_launcher-web.png
Normal file
BIN
app/src/alpha/ic_launcher-web.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 244 KiB |
BIN
app/src/alpha/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
app/src/alpha/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
BIN
app/src/alpha/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
app/src/alpha/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/alpha/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
app/src/alpha/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
app/src/alpha/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
app/src/alpha/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
app/src/alpha/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
app/src/alpha/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
@@ -8,13 +8,14 @@
|
|||||||
<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:allowBackup="true"
|
android:name=".XmrWalletApplication"
|
||||||
|
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"
|
||||||
android:name=".XmrWalletApplication"
|
|
||||||
android:theme="@style/MyMaterialTheme">
|
android:theme="@style/MyMaterialTheme">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -28,11 +29,19 @@
|
|||||||
android:name=".LoginActivity"
|
android:name=".LoginActivity"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:launchMode="singleTop"
|
||||||
android:screenOrientation="portrait">
|
android:screenOrientation="portrait">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<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>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
@@ -40,7 +49,5 @@
|
|||||||
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>
|
826
app/src/main/assets/licenses.html
Normal file
826
app/src/main/assets/licenses.html
Normal file
File diff suppressed because it is too large
Load Diff
88
app/src/main/cpp/device_io_monerujo.hpp
Normal file
88
app/src/main/cpp/device_io_monerujo.hpp
Normal 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)
|
@@ -38,6 +38,7 @@ static jclass class_ArrayList;
|
|||||||
static jclass class_WalletListener;
|
static jclass class_WalletListener;
|
||||||
static jclass class_TransactionInfo;
|
static jclass class_TransactionInfo;
|
||||||
static jclass class_Transfer;
|
static jclass class_Transfer;
|
||||||
|
static jclass class_Ledger;
|
||||||
|
|
||||||
std::mutex _listenerMutex;
|
std::mutex _listenerMutex;
|
||||||
|
|
||||||
@@ -58,6 +59,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
|
|||||||
jenv->FindClass("com/m2049r/xmrwallet/model/Transfer")));
|
jenv->FindClass("com/m2049r/xmrwallet/model/Transfer")));
|
||||||
class_WalletListener = static_cast<jclass>(jenv->NewGlobalRef(
|
class_WalletListener = static_cast<jclass>(jenv->NewGlobalRef(
|
||||||
jenv->FindClass("com/m2049r/xmrwallet/model/WalletListener")));
|
jenv->FindClass("com/m2049r/xmrwallet/model/WalletListener")));
|
||||||
|
class_Ledger = static_cast<jclass>(jenv->NewGlobalRef(
|
||||||
|
jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger")));
|
||||||
return JNI_VERSION_1_6;
|
return JNI_VERSION_1_6;
|
||||||
}
|
}
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@@ -353,6 +356,39 @@ Java_com_m2049r_xmrwallet_model_WalletManager_createWalletFromKeysJ(JNIEnv *env,
|
|||||||
return reinterpret_cast<jlong>(wallet);
|
return reinterpret_cast<jlong>(wallet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) = 0;
|
||||||
|
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_com_m2049r_xmrwallet_model_WalletManager_createWalletFromDeviceJ(JNIEnv *env, jobject instance,
|
||||||
|
jstring path,
|
||||||
|
jstring password,
|
||||||
|
jint networkType,
|
||||||
|
jstring deviceName,
|
||||||
|
jlong restoreHeight,
|
||||||
|
jstring subaddressLookahead) {
|
||||||
|
const char *_path = env->GetStringUTFChars(path, NULL);
|
||||||
|
const char *_password = env->GetStringUTFChars(password, NULL);
|
||||||
|
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
|
||||||
|
const char *_deviceName = env->GetStringUTFChars(deviceName, NULL);
|
||||||
|
const char *_subaddressLookahead = env->GetStringUTFChars(subaddressLookahead, NULL);
|
||||||
|
|
||||||
|
Bitmonero::Wallet *wallet =
|
||||||
|
Bitmonero::WalletManagerFactory::getWalletManager()->createWalletFromDevice(
|
||||||
|
std::string(_path),
|
||||||
|
std::string(_password),
|
||||||
|
_networkType,
|
||||||
|
std::string(_deviceName),
|
||||||
|
(uint64_t) restoreHeight,
|
||||||
|
std::string(_subaddressLookahead));
|
||||||
|
|
||||||
|
env->ReleaseStringUTFChars(path, _path);
|
||||||
|
env->ReleaseStringUTFChars(password, _password);
|
||||||
|
env->ReleaseStringUTFChars(deviceName, _deviceName);
|
||||||
|
env->ReleaseStringUTFChars(subaddressLookahead, _subaddressLookahead);
|
||||||
|
return reinterpret_cast<jlong>(wallet);
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_WalletManager_walletExists(JNIEnv *env, jobject instance,
|
Java_com_m2049r_xmrwallet_model_WalletManager_walletExists(JNIEnv *env, jobject instance,
|
||||||
jstring path) {
|
jstring path) {
|
||||||
@@ -378,6 +414,23 @@ Java_com_m2049r_xmrwallet_model_WalletManager_verifyWalletPassword(JNIEnv *env,
|
|||||||
return static_cast<jboolean>(passwordOk);
|
return static_cast<jboolean>(passwordOk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//virtual int queryWalletHardware(const std::string &keys_file_name, const std::string &password) const = 0;
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_com_m2049r_xmrwallet_model_WalletManager_queryWalletDeviceJ(JNIEnv *env, jobject instance,
|
||||||
|
jstring keys_file_name,
|
||||||
|
jstring password) {
|
||||||
|
const char *_keys_file_name = env->GetStringUTFChars(keys_file_name, NULL);
|
||||||
|
const char *_password = env->GetStringUTFChars(password, NULL);
|
||||||
|
Bitmonero::Wallet::Device device_type;
|
||||||
|
bool ok = Bitmonero::WalletManagerFactory::getWalletManager()->
|
||||||
|
queryWalletDevice(device_type, std::string(_keys_file_name), std::string(_password));
|
||||||
|
env->ReleaseStringUTFChars(keys_file_name, _keys_file_name);
|
||||||
|
env->ReleaseStringUTFChars(password, _password);
|
||||||
|
if (ok)
|
||||||
|
return static_cast<jint>(device_type);
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jobject JNICALL
|
JNIEXPORT jobject JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_WalletManager_findWallets(JNIEnv *env, jobject instance,
|
Java_com_m2049r_xmrwallet_model_WalletManager_findWallets(JNIEnv *env, jobject instance,
|
||||||
@@ -636,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;
|
||||||
|
|
||||||
@@ -712,6 +778,13 @@ Java_com_m2049r_xmrwallet_model_Wallet_isSynchronized(JNIEnv *env, jobject insta
|
|||||||
return static_cast<jboolean>(wallet->synchronized());
|
return static_cast<jboolean>(wallet->synchronized());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL
|
||||||
|
Java_com_m2049r_xmrwallet_model_Wallet_getDeviceTypeJ(JNIEnv *env, jobject instance) {
|
||||||
|
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
|
||||||
|
Bitmonero::Wallet::Device device_type = wallet->getDeviceType();
|
||||||
|
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
|
||||||
JNIEXPORT jbyteArray JNICALL
|
JNIEXPORT jbyteArray JNICALL
|
||||||
Java_com_m2049r_xmrwallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jobject clazz,
|
Java_com_m2049r_xmrwallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jobject clazz,
|
||||||
@@ -1309,6 +1382,93 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jobject i
|
|||||||
Bitmonero::WalletManagerFactory::setLogLevel(level);
|
Bitmonero::WalletManagerFactory::setLogLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Ledger Stuff
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "device_io_monerujo.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) {
|
||||||
|
LOGD("LedgerExchange");
|
||||||
|
JNIEnv *jenv;
|
||||||
|
int envStat = attachJVM(&jenv);
|
||||||
|
if (envStat == JNI_ERR) return -1;
|
||||||
|
|
||||||
|
jmethodID exchangeMethod = jenv->GetStaticMethodID(class_Ledger, "Exchange", "([B)[B");
|
||||||
|
|
||||||
|
jsize sendLen = static_cast<jsize>(cmd_len);
|
||||||
|
jbyteArray dataSend = jenv->NewByteArray(sendLen);
|
||||||
|
jenv->SetByteArrayRegion(dataSend, 0, sendLen, (jbyte *) command);
|
||||||
|
jbyteArray dataRecv = (jbyteArray) jenv->CallStaticObjectMethod(class_Ledger, exchangeMethod,
|
||||||
|
dataSend);
|
||||||
|
jenv->DeleteLocalRef(dataSend);
|
||||||
|
if (dataRecv == nullptr) {
|
||||||
|
detachJVM(jenv, envStat);
|
||||||
|
LOGD("LedgerExchange SCARD_E_NO_READERS_AVAILABLE");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
jsize len = jenv->GetArrayLength(dataRecv);
|
||||||
|
LOGD("LedgerExchange SCARD_S_SUCCESS %ld/%d", cmd_len, len);
|
||||||
|
if (len <= max_resp_len) {
|
||||||
|
jenv->GetByteArrayRegion(dataRecv, 0, len, (jbyte *) response);
|
||||||
|
jenv->DeleteLocalRef(dataRecv);
|
||||||
|
detachJVM(jenv, envStat);
|
||||||
|
return static_cast<int>(len);;
|
||||||
|
} else {
|
||||||
|
jenv->DeleteLocalRef(dataRecv);
|
||||||
|
detachJVM(jenv, envStat);
|
||||||
|
LOGE("LedgerExchange SCARD_E_INSUFFICIENT_BUFFER");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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) {
|
||||||
|
LOGD("LedgerName");
|
||||||
|
JNIEnv *jenv;
|
||||||
|
int envStat = attachJVM(&jenv);
|
||||||
|
if (envStat == JNI_ERR) return -2;
|
||||||
|
|
||||||
|
jmethodID nameMethod = jenv->GetStaticMethodID(class_Ledger, "Name", "()Ljava/lang/String;");
|
||||||
|
jstring name = (jstring) jenv->CallStaticObjectMethod(class_Ledger, nameMethod);
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
if (name != nullptr) {
|
||||||
|
const char *_name = jenv->GetStringUTFChars(name, NULL);
|
||||||
|
strncpy(buffer, _name, len);
|
||||||
|
jenv->ReleaseStringUTFChars(name, _name);
|
||||||
|
buffer[len - 1] = 0; // terminate in case _name is bigger
|
||||||
|
ret = 0;
|
||||||
|
LOGD("LedgerName is %s", buffer);
|
||||||
|
} else {
|
||||||
|
buffer[0] = 0;
|
||||||
|
ret = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
detachJVM(jenv, envStat);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
53
app/src/main/java/com/btchip/BTChipException.java
Normal file
53
app/src/main/java/com/btchip/BTChipException.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
*******************************************************************************
|
||||||
|
* BTChip Bitcoin Hardware Wallet Java API
|
||||||
|
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||||
|
*
|
||||||
|
* 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.btchip;
|
||||||
|
|
||||||
|
public class BTChipException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 5512803003827126405L;
|
||||||
|
|
||||||
|
public BTChipException(String reason) {
|
||||||
|
super(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BTChipException(String reason, Throwable cause) {
|
||||||
|
super(reason, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BTChipException(String reason, int sw) {
|
||||||
|
super(reason);
|
||||||
|
this.sw = sw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSW() {
|
||||||
|
return sw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
if (sw == 0) {
|
||||||
|
return "BTChip Exception : " + getMessage();
|
||||||
|
} else {
|
||||||
|
return "BTChip Exception : " + getMessage() + " " + Integer.toHexString(sw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int sw;
|
||||||
|
|
||||||
|
}
|
31
app/src/main/java/com/btchip/comm/BTChipTransport.java
Normal file
31
app/src/main/java/com/btchip/comm/BTChipTransport.java
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
*******************************************************************************
|
||||||
|
* BTChip Bitcoin Hardware Wallet Java API
|
||||||
|
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||||
|
* (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.
|
||||||
|
********************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.btchip.comm;
|
||||||
|
|
||||||
|
import com.btchip.BTChipException;
|
||||||
|
|
||||||
|
public interface BTChipTransport {
|
||||||
|
public byte[] exchange(byte[] command);
|
||||||
|
|
||||||
|
public void close();
|
||||||
|
|
||||||
|
public void setDebug(boolean debugFlag);
|
||||||
|
}
|
126
app/src/main/java/com/btchip/comm/LedgerHelper.java
Normal file
126
app/src/main/java/com/btchip/comm/LedgerHelper.java
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
*******************************************************************************
|
||||||
|
* BTChip Bitcoin Hardware Wallet Java API
|
||||||
|
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||||
|
* (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.
|
||||||
|
********************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.btchip.comm;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
public class LedgerHelper {
|
||||||
|
|
||||||
|
private static final int TAG_APDU = 0x05;
|
||||||
|
|
||||||
|
public static byte[] wrapCommandAPDU(int channel, byte[] command, int packetSize) {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
if (packetSize < 3) {
|
||||||
|
throw new IllegalArgumentException("Can't handle Ledger framing with less than 3 bytes for the report");
|
||||||
|
}
|
||||||
|
int sequenceIdx = 0;
|
||||||
|
int offset = 0;
|
||||||
|
output.write(channel >> 8);
|
||||||
|
output.write(channel);
|
||||||
|
output.write(TAG_APDU);
|
||||||
|
output.write(sequenceIdx >> 8);
|
||||||
|
output.write(sequenceIdx);
|
||||||
|
sequenceIdx++;
|
||||||
|
output.write(command.length >> 8);
|
||||||
|
output.write(command.length);
|
||||||
|
int blockSize = (command.length > packetSize - 7 ? packetSize - 7 : command.length);
|
||||||
|
output.write(command, offset, blockSize);
|
||||||
|
offset += blockSize;
|
||||||
|
while (offset != command.length) {
|
||||||
|
output.write(channel >> 8);
|
||||||
|
output.write(channel);
|
||||||
|
output.write(TAG_APDU);
|
||||||
|
output.write(sequenceIdx >> 8);
|
||||||
|
output.write(sequenceIdx);
|
||||||
|
sequenceIdx++;
|
||||||
|
blockSize = (command.length - offset > packetSize - 5 ? packetSize - 5 : command.length - offset);
|
||||||
|
output.write(command, offset, blockSize);
|
||||||
|
offset += blockSize;
|
||||||
|
}
|
||||||
|
if ((output.size() % packetSize) != 0) {
|
||||||
|
byte[] padding = new byte[packetSize - (output.size() % packetSize)];
|
||||||
|
output.write(padding, 0, padding.length);
|
||||||
|
}
|
||||||
|
return output.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] unwrapResponseAPDU(int channel, byte[] data, int packetSize) {
|
||||||
|
ByteArrayOutputStream response = new ByteArrayOutputStream();
|
||||||
|
int offset = 0;
|
||||||
|
int responseLength;
|
||||||
|
int sequenceIdx = 0;
|
||||||
|
if ((data == null) || (data.length < 7 + 5)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (data[offset++] != (channel >> 8)) {
|
||||||
|
throw new IllegalArgumentException("Invalid channel");
|
||||||
|
}
|
||||||
|
if (data[offset++] != (channel & 0xff)) {
|
||||||
|
throw new IllegalArgumentException("Invalid channel");
|
||||||
|
}
|
||||||
|
if (data[offset++] != TAG_APDU) {
|
||||||
|
throw new IllegalArgumentException("Invalid tag");
|
||||||
|
}
|
||||||
|
if (data[offset++] != 0x00) {
|
||||||
|
throw new IllegalArgumentException("Invalid sequence");
|
||||||
|
}
|
||||||
|
if (data[offset++] != 0x00) {
|
||||||
|
throw new IllegalArgumentException("Invalid sequence");
|
||||||
|
}
|
||||||
|
responseLength = ((data[offset++] & 0xff) << 8);
|
||||||
|
responseLength |= (data[offset++] & 0xff);
|
||||||
|
if (data.length < 7 + responseLength) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int blockSize = (responseLength > packetSize - 7 ? packetSize - 7 : responseLength);
|
||||||
|
response.write(data, offset, blockSize);
|
||||||
|
offset += blockSize;
|
||||||
|
while (response.size() != responseLength) {
|
||||||
|
sequenceIdx++;
|
||||||
|
if (offset == data.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (data[offset++] != (channel >> 8)) {
|
||||||
|
throw new IllegalArgumentException("Invalid channel");
|
||||||
|
}
|
||||||
|
if (data[offset++] != (channel & 0xff)) {
|
||||||
|
throw new IllegalArgumentException("Invalid channel");
|
||||||
|
}
|
||||||
|
if (data[offset++] != TAG_APDU) {
|
||||||
|
throw new IllegalArgumentException("Invalid tag");
|
||||||
|
}
|
||||||
|
if (data[offset++] != (sequenceIdx >> 8)) {
|
||||||
|
throw new IllegalArgumentException("Invalid sequence");
|
||||||
|
}
|
||||||
|
if (data[offset++] != (sequenceIdx & 0xff)) {
|
||||||
|
throw new IllegalArgumentException("Invalid sequence");
|
||||||
|
}
|
||||||
|
blockSize = (responseLength - response.size() > packetSize - 5 ? packetSize - 5 : responseLength - response.size());
|
||||||
|
if (blockSize > data.length - offset) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
response.write(data, offset, blockSize);
|
||||||
|
offset += blockSize;
|
||||||
|
}
|
||||||
|
return response.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
*******************************************************************************
|
||||||
|
* BTChip Bitcoin Hardware Wallet Java API
|
||||||
|
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||||
|
* (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.
|
||||||
|
********************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.btchip.comm.android;
|
||||||
|
|
||||||
|
import android.hardware.usb.UsbConstants;
|
||||||
|
import android.hardware.usb.UsbDevice;
|
||||||
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
|
import android.hardware.usb.UsbEndpoint;
|
||||||
|
import android.hardware.usb.UsbInterface;
|
||||||
|
import android.hardware.usb.UsbManager;
|
||||||
|
import android.hardware.usb.UsbRequest;
|
||||||
|
|
||||||
|
import com.btchip.BTChipException;
|
||||||
|
import com.btchip.comm.BTChipTransport;
|
||||||
|
import com.btchip.comm.LedgerHelper;
|
||||||
|
import com.btchip.utils.Dump;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class BTChipTransportAndroidHID implements BTChipTransport {
|
||||||
|
|
||||||
|
public static UsbDevice getDevice(UsbManager manager) {
|
||||||
|
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
|
||||||
|
for (UsbDevice device : deviceList.values()) {
|
||||||
|
Timber.d("%04X:%04X %s, %s", device.getVendorId(), device.getProductId(), device.getManufacturerName(), device.getProductName());
|
||||||
|
if ((device.getVendorId() == VID) && (device.getProductId() == PID_HID)) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BTChipTransport open(UsbManager manager, UsbDevice device) throws IOException {
|
||||||
|
UsbDeviceConnection connection = manager.openDevice(device);
|
||||||
|
if (connection == null) throw new IOException("Device not connected");
|
||||||
|
// Must only be called once permission is granted (see http://developer.android.com/reference/android/hardware/usb/UsbManager.html)
|
||||||
|
// Important if enumerating, rather than being awaken by the intent notification
|
||||||
|
UsbInterface dongleInterface = device.getInterface(0);
|
||||||
|
UsbEndpoint in = null;
|
||||||
|
UsbEndpoint out = null;
|
||||||
|
for (int i = 0; i < dongleInterface.getEndpointCount(); i++) {
|
||||||
|
UsbEndpoint tmpEndpoint = dongleInterface.getEndpoint(i);
|
||||||
|
if (tmpEndpoint.getDirection() == UsbConstants.USB_DIR_IN) {
|
||||||
|
in = tmpEndpoint;
|
||||||
|
} else {
|
||||||
|
out = tmpEndpoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connection.claimInterface(dongleInterface, true);
|
||||||
|
return new BTChipTransportAndroidHID(connection, dongleInterface, in, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int VID = 0x2C97;
|
||||||
|
private static final int PID_HID = 0x0001;
|
||||||
|
|
||||||
|
private UsbDeviceConnection connection;
|
||||||
|
private UsbInterface dongleInterface;
|
||||||
|
private UsbEndpoint in;
|
||||||
|
private UsbEndpoint out;
|
||||||
|
private byte transferBuffer[];
|
||||||
|
private boolean debug;
|
||||||
|
|
||||||
|
public BTChipTransportAndroidHID(UsbDeviceConnection connection, UsbInterface dongleInterface, UsbEndpoint in, UsbEndpoint out) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.dongleInterface = dongleInterface;
|
||||||
|
this.in = in;
|
||||||
|
this.out = out;
|
||||||
|
transferBuffer = new byte[HID_BUFFER_SIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] exchange(byte[] command) {
|
||||||
|
ByteArrayOutputStream response = new ByteArrayOutputStream();
|
||||||
|
byte[] responseData = null;
|
||||||
|
int offset = 0;
|
||||||
|
if (debug) {
|
||||||
|
Timber.d("=> %s", Dump.dump(command));
|
||||||
|
}
|
||||||
|
command = LedgerHelper.wrapCommandAPDU(LEDGER_DEFAULT_CHANNEL, command, HID_BUFFER_SIZE);
|
||||||
|
UsbRequest requestOut = new UsbRequest();
|
||||||
|
requestOut.initialize(connection, out);
|
||||||
|
while (offset != command.length) {
|
||||||
|
int blockSize = (command.length - offset > HID_BUFFER_SIZE ? HID_BUFFER_SIZE : command.length - offset);
|
||||||
|
System.arraycopy(command, offset, transferBuffer, 0, blockSize);
|
||||||
|
requestOut.queue(ByteBuffer.wrap(transferBuffer), HID_BUFFER_SIZE);
|
||||||
|
connection.requestWait();
|
||||||
|
offset += blockSize;
|
||||||
|
}
|
||||||
|
requestOut.close();
|
||||||
|
ByteBuffer responseBuffer = ByteBuffer.allocate(HID_BUFFER_SIZE);
|
||||||
|
UsbRequest requestIn = new UsbRequest();
|
||||||
|
requestIn.initialize(connection, in);
|
||||||
|
while ((responseData = LedgerHelper.unwrapResponseAPDU(LEDGER_DEFAULT_CHANNEL, response.toByteArray(), HID_BUFFER_SIZE)) == null) {
|
||||||
|
responseBuffer.clear();
|
||||||
|
requestIn.queue(responseBuffer, HID_BUFFER_SIZE);
|
||||||
|
connection.requestWait();
|
||||||
|
responseBuffer.rewind();
|
||||||
|
responseBuffer.get(transferBuffer, 0, HID_BUFFER_SIZE);
|
||||||
|
response.write(transferBuffer, 0, HID_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
requestIn.close();
|
||||||
|
if (debug) {
|
||||||
|
Timber.d("<= %s", Dump.dump(responseData));
|
||||||
|
}
|
||||||
|
return responseData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
connection.releaseInterface(dongleInterface);
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDebug(boolean debugFlag) {
|
||||||
|
this.debug = debugFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int HID_BUFFER_SIZE = 64;
|
||||||
|
private static final int LEDGER_DEFAULT_CHANNEL = 1;
|
||||||
|
private static final int SW1_DATA_AVAILABLE = 0x61;
|
||||||
|
}
|
62
app/src/main/java/com/btchip/utils/Dump.java
Normal file
62
app/src/main/java/com/btchip/utils/Dump.java
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
*******************************************************************************
|
||||||
|
* BTChip Bitcoin Hardware Wallet Java API
|
||||||
|
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
|
||||||
|
*
|
||||||
|
* 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.btchip.utils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
public class Dump {
|
||||||
|
|
||||||
|
public static String dump(byte[] buffer, int offset, int length) {
|
||||||
|
String result = "";
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
String temp = Integer.toHexString((buffer[offset + i]) & 0xff);
|
||||||
|
if (temp.length() < 2) {
|
||||||
|
temp = "0" + temp;
|
||||||
|
}
|
||||||
|
result += temp;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String dump(byte[] buffer) {
|
||||||
|
return dump(buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] hexToBin(String src) {
|
||||||
|
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
int i = 0;
|
||||||
|
while (i < src.length()) {
|
||||||
|
char x = src.charAt(i);
|
||||||
|
if (!((x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f'))) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
result.write(Integer.valueOf("" + src.charAt(i) + src.charAt(i + 1), 16));
|
||||||
|
i += 2;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
294
app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
Normal file
294
app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -61,6 +61,7 @@ public class GenerateFragment extends Fragment {
|
|||||||
static final String TYPE_NEW = "new";
|
static final String TYPE_NEW = "new";
|
||||||
static final String TYPE_KEY = "key";
|
static final String TYPE_KEY = "key";
|
||||||
static final String TYPE_SEED = "seed";
|
static final String TYPE_SEED = "seed";
|
||||||
|
static final String TYPE_LEDGER = "ledger";
|
||||||
static final String TYPE_VIEWONLY = "view";
|
static final String TYPE_VIEWONLY = "view";
|
||||||
|
|
||||||
private TextInputLayout etWalletName;
|
private TextInputLayout etWalletName;
|
||||||
@@ -144,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
|
||||||
@@ -182,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;
|
||||||
@@ -190,10 +193,23 @@ public class GenerateFragment extends Fragment {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (type.equals(TYPE_LEDGER)) {
|
||||||
|
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||||
|
etWalletPassword.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)) {
|
||||||
|
etWalletRestoreHeight.requestFocus();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
} 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;
|
||||||
}
|
}
|
||||||
@@ -203,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();
|
||||||
}
|
}
|
||||||
@@ -215,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;
|
||||||
}
|
}
|
||||||
@@ -227,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();
|
||||||
}
|
}
|
||||||
@@ -239,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();
|
||||||
@@ -259,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();
|
||||||
}
|
}
|
||||||
@@ -273,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;
|
||||||
@@ -391,16 +413,24 @@ public class GenerateFragment extends Fragment {
|
|||||||
// is it a date?
|
// is it a date?
|
||||||
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
|
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
parser.setLenient(false);
|
parser.setLenient(false);
|
||||||
parser.parse(restoreHeight);
|
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||||
height = RestoreHeight.getInstance().getHeight(restoreHeight);
|
} catch (ParseException ex) {
|
||||||
} catch (ParseException exPE) {
|
}
|
||||||
|
if (height <= 0)
|
||||||
|
try {
|
||||||
|
// is it a date without dashes?
|
||||||
|
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");
|
||||||
|
parser.setLenient(false);
|
||||||
|
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||||
|
} catch (ParseException ex) {
|
||||||
|
}
|
||||||
|
if (height <= 0)
|
||||||
try {
|
try {
|
||||||
// or is it a height?
|
// or is it a height?
|
||||||
height = Long.parseLong(restoreHeight);
|
height = Long.parseLong(restoreHeight);
|
||||||
} catch (NumberFormatException exNFE) {
|
} catch (NumberFormatException ex) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Timber.d("Using Restore Height = %d", height);
|
Timber.d("Using Restore Height = %d", height);
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
@@ -477,6 +507,12 @@ public class GenerateFragment extends Fragment {
|
|||||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||||
}
|
}
|
||||||
activityCallback.onGenerate(name, crazyPass, seed, height);
|
activityCallback.onGenerate(name, crazyPass, seed, height);
|
||||||
|
} else if (type.equals(TYPE_LEDGER)) {
|
||||||
|
bGenerate.setEnabled(false);
|
||||||
|
if (fingerprintAuthAllowed) {
|
||||||
|
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||||
|
}
|
||||||
|
activityCallback.onGenerateLedger(name, crazyPass, height);
|
||||||
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
|
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
|
||||||
if (checkAddress() && checkViewKey() && checkSpendKey()) {
|
if (checkAddress() && checkViewKey() && checkSpendKey()) {
|
||||||
bGenerate.setEnabled(false);
|
bGenerate.setEnabled(false);
|
||||||
@@ -515,6 +551,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
return getString(R.string.generate_wallet_type_new);
|
return getString(R.string.generate_wallet_type_new);
|
||||||
case TYPE_SEED:
|
case TYPE_SEED:
|
||||||
return getString(R.string.generate_wallet_type_seed);
|
return getString(R.string.generate_wallet_type_seed);
|
||||||
|
case TYPE_LEDGER:
|
||||||
|
return getString(R.string.generate_wallet_type_ledger);
|
||||||
case TYPE_VIEWONLY:
|
case TYPE_VIEWONLY:
|
||||||
return getString(R.string.generate_wallet_type_view);
|
return getString(R.string.generate_wallet_type_view);
|
||||||
default:
|
default:
|
||||||
@@ -532,6 +570,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
|
|
||||||
void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
|
void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
|
||||||
|
|
||||||
|
void onGenerateLedger(String name, String password, long height);
|
||||||
|
|
||||||
void setTitle(String title);
|
void setTitle(String title);
|
||||||
|
|
||||||
void setToolbarButton(int type);
|
void setToolbarButton(int type);
|
||||||
@@ -567,6 +607,9 @@ public class GenerateFragment extends Fragment {
|
|||||||
case TYPE_SEED:
|
case TYPE_SEED:
|
||||||
inflater.inflate(R.menu.create_wallet_seed, menu);
|
inflater.inflate(R.menu.create_wallet_seed, menu);
|
||||||
break;
|
break;
|
||||||
|
case TYPE_LEDGER:
|
||||||
|
inflater.inflate(R.menu.create_wallet_ledger, menu);
|
||||||
|
break;
|
||||||
case TYPE_VIEWONLY:
|
case TYPE_VIEWONLY:
|
||||||
inflater.inflate(R.menu.create_wallet_view, menu);
|
inflater.inflate(R.menu.create_wallet_view, menu);
|
||||||
break;
|
break;
|
||||||
|
@@ -43,6 +43,8 @@ import android.widget.Switch;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.ledger.Ledger;
|
||||||
|
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
|
||||||
import com.m2049r.xmrwallet.model.NetworkType;
|
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.model.WalletManager;
|
||||||
@@ -52,8 +54,6 @@ import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
|||||||
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class GenerateReviewFragment extends Fragment {
|
public class GenerateReviewFragment extends Fragment {
|
||||||
@@ -76,6 +76,9 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
private ImageButton bCopyAddress;
|
private ImageButton bCopyAddress;
|
||||||
private LinearLayout llAdvancedInfo;
|
private LinearLayout llAdvancedInfo;
|
||||||
private LinearLayout llPassword;
|
private LinearLayout llPassword;
|
||||||
|
private LinearLayout llMnemonic;
|
||||||
|
private LinearLayout llSpendKey;
|
||||||
|
private LinearLayout llViewKey;
|
||||||
private Button bAdvancedInfo;
|
private Button bAdvancedInfo;
|
||||||
private Button bAccept;
|
private Button bAccept;
|
||||||
|
|
||||||
@@ -99,6 +102,9 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
|
bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
|
||||||
llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
|
llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
|
||||||
llPassword = (LinearLayout) view.findViewById(R.id.llPassword);
|
llPassword = (LinearLayout) view.findViewById(R.id.llPassword);
|
||||||
|
llMnemonic = (LinearLayout) view.findViewById(R.id.llMnemonic);
|
||||||
|
llSpendKey = (LinearLayout) view.findViewById(R.id.llSpendKey);
|
||||||
|
llViewKey = (LinearLayout) view.findViewById(R.id.llViewKey);
|
||||||
|
|
||||||
bAccept = (Button) view.findViewById(R.id.bAccept);
|
bAccept = (Button) view.findViewById(R.id.bAccept);
|
||||||
|
|
||||||
@@ -142,7 +148,6 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void showDetails() {
|
void showDetails() {
|
||||||
showProgress();
|
|
||||||
tvWalletPassword.setText(null);
|
tvWalletPassword.setText(null);
|
||||||
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath);
|
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath);
|
||||||
}
|
}
|
||||||
@@ -188,6 +193,21 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
boolean isWatchOnly;
|
boolean isWatchOnly;
|
||||||
Wallet.Status status;
|
Wallet.Status status;
|
||||||
|
|
||||||
|
boolean dialogOpened = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
showProgress();
|
||||||
|
if ((walletPath != null)
|
||||||
|
&& (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword())
|
||||||
|
== Wallet.Device.Device_Ledger)
|
||||||
|
&& (progressCallback != null)) {
|
||||||
|
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
|
||||||
|
dialogOpened = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(String... params) {
|
protected Boolean doInBackground(String... params) {
|
||||||
if (params.length != 1) return false;
|
if (params.length != 1) return false;
|
||||||
@@ -212,7 +232,16 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
|
|
||||||
address = wallet.getAddress();
|
address = wallet.getAddress();
|
||||||
seed = wallet.getSeed();
|
seed = wallet.getSeed();
|
||||||
|
switch (wallet.getDeviceType()) {
|
||||||
|
case Device_Ledger:
|
||||||
|
viewKey = Ledger.Key();
|
||||||
|
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();
|
||||||
if (closeWallet) wallet.close();
|
if (closeWallet) wallet.close();
|
||||||
@@ -222,6 +251,8 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Boolean result) {
|
protected void onPostExecute(Boolean result) {
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
|
if (dialogOpened)
|
||||||
|
progressCallback.dismissProgressDialog();
|
||||||
if (!isAdded()) return; // never mind
|
if (!isAdded()) return; // never mind
|
||||||
walletName = name;
|
walletName = name;
|
||||||
if (result) {
|
if (result) {
|
||||||
@@ -232,10 +263,22 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
llPassword.setVisibility(View.VISIBLE);
|
llPassword.setVisibility(View.VISIBLE);
|
||||||
tvWalletPassword.setText(getPassword());
|
tvWalletPassword.setText(getPassword());
|
||||||
tvWalletAddress.setText(address);
|
tvWalletAddress.setText(address);
|
||||||
|
if (!seed.isEmpty()) {
|
||||||
|
llMnemonic.setVisibility(View.VISIBLE);
|
||||||
tvWalletMnemonic.setText(seed);
|
tvWalletMnemonic.setText(seed);
|
||||||
|
}
|
||||||
|
boolean showAdvanced = false;
|
||||||
|
if (isKeyValid(viewKey)) {
|
||||||
|
llViewKey.setVisibility(View.VISIBLE);
|
||||||
tvWalletViewKey.setText(viewKey);
|
tvWalletViewKey.setText(viewKey);
|
||||||
|
showAdvanced = true;
|
||||||
|
}
|
||||||
|
if (isKeyValid(spendKey)) {
|
||||||
|
llSpendKey.setVisibility(View.VISIBLE);
|
||||||
tvWalletSpendKey.setText(spendKey);
|
tvWalletSpendKey.setText(spendKey);
|
||||||
bAdvancedInfo.setVisibility(View.VISIBLE);
|
showAdvanced = true;
|
||||||
|
}
|
||||||
|
if (showAdvanced) bAdvancedInfo.setVisibility(View.VISIBLE);
|
||||||
bCopyAddress.setClickable(true);
|
bCopyAddress.setClickable(true);
|
||||||
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
|
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
|
||||||
activityCallback.setTitle(name, getString(R.string.details_title));
|
activityCallback.setTitle(name, getString(R.string.details_title));
|
||||||
@@ -267,6 +310,8 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
public interface ProgressListener {
|
public interface ProgressListener {
|
||||||
void showProgressDialog(int msgId);
|
void showProgressDialog(int msgId);
|
||||||
|
|
||||||
|
void showLedgerProgressDialog(int mode);
|
||||||
|
|
||||||
void dismissProgressDialog();
|
void dismissProgressDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,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
|
||||||
@@ -577,4 +623,10 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
return openDialog;
|
return openDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isKeyValid(String key) {
|
||||||
|
return (key != null) && (key.length() == 64)
|
||||||
|
&& !key.equals("0000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
&& !key.toLowerCase().equals("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
||||||
|
// ledger implmenetation returns the spend key as f's
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -103,6 +103,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
|
|
||||||
void setNetworkType(NetworkType networkType);
|
void setNetworkType(NetworkType networkType);
|
||||||
|
|
||||||
|
boolean hasLedger();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -145,11 +146,13 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
fabView = (FloatingActionButton) view.findViewById(R.id.fabView);
|
fabView = (FloatingActionButton) view.findViewById(R.id.fabView);
|
||||||
fabKey = (FloatingActionButton) view.findViewById(R.id.fabKey);
|
fabKey = (FloatingActionButton) view.findViewById(R.id.fabKey);
|
||||||
fabSeed = (FloatingActionButton) view.findViewById(R.id.fabSeed);
|
fabSeed = (FloatingActionButton) view.findViewById(R.id.fabSeed);
|
||||||
|
fabLedger = (FloatingActionButton) view.findViewById(R.id.fabLedger);
|
||||||
|
|
||||||
fabNewL = (RelativeLayout) view.findViewById(R.id.fabNewL);
|
fabNewL = (RelativeLayout) view.findViewById(R.id.fabNewL);
|
||||||
fabViewL = (RelativeLayout) view.findViewById(R.id.fabViewL);
|
fabViewL = (RelativeLayout) view.findViewById(R.id.fabViewL);
|
||||||
fabKeyL = (RelativeLayout) view.findViewById(R.id.fabKeyL);
|
fabKeyL = (RelativeLayout) view.findViewById(R.id.fabKeyL);
|
||||||
fabSeedL = (RelativeLayout) view.findViewById(R.id.fabSeedL);
|
fabSeedL = (RelativeLayout) view.findViewById(R.id.fabSeedL);
|
||||||
|
fabLedgerL = (RelativeLayout) view.findViewById(R.id.fabLedgerL);
|
||||||
|
|
||||||
fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse);
|
fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse);
|
||||||
fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen);
|
fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen);
|
||||||
@@ -163,6 +166,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
fabView.setOnClickListener(this);
|
fabView.setOnClickListener(this);
|
||||||
fabKey.setOnClickListener(this);
|
fabKey.setOnClickListener(this);
|
||||||
fabSeed.setOnClickListener(this);
|
fabSeed.setOnClickListener(this);
|
||||||
|
fabLedger.setOnClickListener(this);
|
||||||
fabScreen.setOnClickListener(this);
|
fabScreen.setOnClickListener(this);
|
||||||
|
|
||||||
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
|
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
|
||||||
@@ -173,7 +177,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
etDummy = (EditText) view.findViewById(R.id.etDummy);
|
etDummy = (EditText) view.findViewById(R.id.etDummy);
|
||||||
|
|
||||||
ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
|
ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
|
||||||
Notice.showAll(llNotice,".*_login");
|
Notice.showAll(llNotice, ".*_login");
|
||||||
|
|
||||||
etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress);
|
etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress);
|
||||||
nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line);
|
nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line);
|
||||||
@@ -202,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;
|
||||||
@@ -426,9 +431,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFabOpen = false;
|
private boolean isFabOpen = false;
|
||||||
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed;
|
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabLedger;
|
||||||
private FrameLayout fabScreen;
|
private FrameLayout fabScreen;
|
||||||
private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL;
|
private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabLedgerL;
|
||||||
private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen;
|
private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen;
|
||||||
private Animation fab_pulse;
|
private Animation fab_pulse;
|
||||||
|
|
||||||
@@ -437,11 +442,14 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void animateFAB() {
|
public void animateFAB() {
|
||||||
if (isFabOpen) {
|
if (isFabOpen) { // close the fab
|
||||||
fabScreen.setVisibility(View.INVISIBLE);
|
|
||||||
fabScreen.setClickable(false);
|
fabScreen.setClickable(false);
|
||||||
fabScreen.startAnimation(fab_close_screen);
|
fabScreen.startAnimation(fab_close_screen);
|
||||||
fab.startAnimation(rotate_backward);
|
fab.startAnimation(rotate_backward);
|
||||||
|
if (fabLedgerL.getVisibility() == View.VISIBLE) {
|
||||||
|
fabLedgerL.startAnimation(fab_close);
|
||||||
|
fabLedger.setClickable(false);
|
||||||
|
} else {
|
||||||
fabNewL.startAnimation(fab_close);
|
fabNewL.startAnimation(fab_close);
|
||||||
fabNew.setClickable(false);
|
fabNew.setClickable(false);
|
||||||
fabViewL.startAnimation(fab_close);
|
fabViewL.startAnimation(fab_close);
|
||||||
@@ -450,11 +458,28 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
fabKey.setClickable(false);
|
fabKey.setClickable(false);
|
||||||
fabSeedL.startAnimation(fab_close);
|
fabSeedL.startAnimation(fab_close);
|
||||||
fabSeed.setClickable(false);
|
fabSeed.setClickable(false);
|
||||||
|
}
|
||||||
isFabOpen = false;
|
isFabOpen = false;
|
||||||
} else {
|
} else { // open the fab
|
||||||
fabScreen.setClickable(true);
|
fabScreen.setClickable(true);
|
||||||
fabScreen.startAnimation(fab_open_screen);
|
fabScreen.startAnimation(fab_open_screen);
|
||||||
fab.startAnimation(rotate_forward);
|
fab.startAnimation(rotate_forward);
|
||||||
|
if (activityCallback.hasLedger()) {
|
||||||
|
fabLedgerL.setVisibility(View.VISIBLE);
|
||||||
|
fabNewL.setVisibility(View.GONE);
|
||||||
|
fabViewL.setVisibility(View.GONE);
|
||||||
|
fabKeyL.setVisibility(View.GONE);
|
||||||
|
fabSeedL.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
fabLedgerL.startAnimation(fab_open);
|
||||||
|
fabLedger.setClickable(true);
|
||||||
|
} else {
|
||||||
|
fabLedgerL.setVisibility(View.GONE);
|
||||||
|
fabNewL.setVisibility(View.VISIBLE);
|
||||||
|
fabViewL.setVisibility(View.VISIBLE);
|
||||||
|
fabKeyL.setVisibility(View.VISIBLE);
|
||||||
|
fabSeedL.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
fabNewL.startAnimation(fab_open);
|
fabNewL.startAnimation(fab_open);
|
||||||
fabNew.setClickable(true);
|
fabNew.setClickable(true);
|
||||||
fabViewL.startAnimation(fab_open);
|
fabViewL.startAnimation(fab_open);
|
||||||
@@ -463,6 +488,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
fabKey.setClickable(true);
|
fabKey.setClickable(true);
|
||||||
fabSeedL.startAnimation(fab_open);
|
fabSeedL.startAnimation(fab_open);
|
||||||
fabSeed.setClickable(true);
|
fabSeed.setClickable(true);
|
||||||
|
}
|
||||||
isFabOpen = true;
|
isFabOpen = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -470,6 +496,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
int id = v.getId();
|
int id = v.getId();
|
||||||
|
Timber.d("onClick %d/%d", id, R.id.fabLedger);
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case R.id.fab:
|
case R.id.fab:
|
||||||
animateFAB();
|
animateFAB();
|
||||||
@@ -491,6 +518,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
animateFAB();
|
animateFAB();
|
||||||
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
|
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
|
||||||
break;
|
break;
|
||||||
|
case R.id.fabLedger:
|
||||||
|
Timber.d("FAB_LEDGER");
|
||||||
|
animateFAB();
|
||||||
|
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
|
||||||
|
break;
|
||||||
case R.id.fabScreen:
|
case R.id.fabScreen:
|
||||||
animateFAB();
|
animateFAB();
|
||||||
break;
|
break;
|
||||||
|
@@ -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);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
package com.m2049r.xmrwallet;
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import static android.view.WindowManager.LayoutParams;
|
import static android.view.WindowManager.LayoutParams;
|
||||||
|
|
||||||
@@ -36,4 +35,9 @@ public abstract class SecureActivity extends AppCompatActivity {
|
|||||||
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(Context context) {
|
||||||
|
super.attachBaseContext(LocaleHelper.setLocale(context, LocaleHelper.getLocale(context)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -271,7 +271,7 @@ public class WalletFragment extends Fragment
|
|||||||
bSend.setVisibility(View.VISIBLE);
|
bSend.setVisibility(View.VISIBLE);
|
||||||
bSend.setEnabled(true);
|
bSend.setEnabled(true);
|
||||||
}
|
}
|
||||||
enableAccountsList(true);
|
if (isVisible()) enableAccountsList(true); //otherwise it is enabled in onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean walletLoaded = false;
|
boolean walletLoaded = false;
|
||||||
|
@@ -18,8 +18,10 @@ package com.m2049r.xmrwallet;
|
|||||||
|
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@@ -32,4 +34,16 @@ public class XmrWalletApplication extends Application {
|
|||||||
Timber.plant(new Timber.DebugTree());
|
Timber.plant(new Timber.DebugTree());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(Context context) {
|
||||||
|
super.attachBaseContext(LocaleHelper.setLocale(context, LocaleHelper.getLocale(context)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration configuration) {
|
||||||
|
super.onConfigurationChanged(configuration);
|
||||||
|
LocaleHelper.updateSystemDefaultLocale(configuration.locale);
|
||||||
|
LocaleHelper.setLocale(XmrWalletApplication.this, LocaleHelper.getLocale(XmrWalletApplication.this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user