mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-05 09:58:42 +02:00
Compare commits
114 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
0776b7b6a3 | ||
![]() |
c2eed85a83 | ||
![]() |
d7c2b4a727 | ||
![]() |
085e41f5da | ||
![]() |
6c6b3061a8 | ||
![]() |
5fc15779b7 | ||
![]() |
520d151f3c | ||
![]() |
7aad941dab | ||
![]() |
01e7693425 | ||
![]() |
091538752b | ||
![]() |
d5b95dd976 | ||
![]() |
008f06959c | ||
![]() |
817816cd34 | ||
![]() |
9d41d5da52 | ||
![]() |
ad76a7ffc1 | ||
![]() |
475542c4f3 | ||
![]() |
b5d0659ca9 | ||
![]() |
781bfbc78b | ||
![]() |
8985511209 | ||
![]() |
3c8a4ce967 | ||
![]() |
fcfedbcfae | ||
![]() |
74279b135a | ||
![]() |
d6d2de8312 | ||
![]() |
af0ecb2894 | ||
![]() |
975cc4f43c | ||
![]() |
74ba36de26 | ||
![]() |
7627e15a48 | ||
![]() |
37244cb9e0 | ||
![]() |
843566b820 | ||
![]() |
0bcf156929 | ||
![]() |
b1d91e2671 | ||
![]() |
271cd2d4a8 | ||
![]() |
22c5a543db | ||
![]() |
cd986860c5 | ||
![]() |
0cf5981eae | ||
![]() |
e109df34f0 | ||
![]() |
5a7aa6cc77 | ||
![]() |
f50629ff81 | ||
![]() |
cb12d64e5f | ||
![]() |
a8f08fb9b9 | ||
![]() |
3e9be418a8 | ||
![]() |
fa5dc9988d | ||
![]() |
857cf8d6d8 | ||
![]() |
63e0c265cb | ||
![]() |
39d2c0a25f | ||
![]() |
ff6e00e1a1 | ||
![]() |
3a839a04d5 | ||
![]() |
42e4db5cc1 | ||
![]() |
d18999e731 | ||
![]() |
425246beb1 | ||
![]() |
e1a2572236 | ||
![]() |
7fdae76f51 | ||
![]() |
b4e1767a7b | ||
![]() |
9b9995437d | ||
![]() |
94abc00422 | ||
![]() |
d7d6601b60 | ||
![]() |
bf95994c9e | ||
![]() |
a2d6ca0740 | ||
![]() |
1115bbb706 | ||
![]() |
0b17ed4322 | ||
![]() |
bcc85a5b3f | ||
![]() |
5b26f1a30b | ||
![]() |
63677d5027 | ||
![]() |
96579c1be4 | ||
![]() |
af58b76f0c | ||
![]() |
7879f31f63 | ||
![]() |
2ca7b41982 | ||
![]() |
8bdc0f8fde | ||
![]() |
0bb9e9cb6c | ||
![]() |
bdc2a72790 | ||
![]() |
073bd96b17 | ||
![]() |
37f22a9dc2 | ||
![]() |
bc3c5b3f66 | ||
![]() |
c61a62cc85 | ||
![]() |
66a6583ec4 | ||
![]() |
d24b37e2f2 | ||
![]() |
161c155de6 | ||
![]() |
dd59233dd4 | ||
![]() |
e9c74d4d9c | ||
![]() |
b37adb4546 | ||
![]() |
a4e99209be | ||
![]() |
45aa8f30e5 | ||
![]() |
60a7b15700 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,8 +1,7 @@
|
||||
.gradle
|
||||
/build
|
||||
*.iml
|
||||
/.idea/libraries
|
||||
/.idea/workspace.xml
|
||||
/.idea
|
||||
/local.properties
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
19
README.md
19
README.md
@@ -1,12 +1,23 @@
|
||||
# Monerujo
|
||||
Another Android Monero Wallet
|
||||
Another Android Monero Wallet for Monero
|
||||
**(not
|
||||
Monero Classic,
|
||||
Monero-Classic,
|
||||
Monero Zero,
|
||||
Monero Original,
|
||||
Monero C,
|
||||
Monero V)**
|
||||
|
||||
### QUICKSTART
|
||||
- Download the APK for the most current release [here](https://github.com/m2049r/xmrwallet/releases) and install it
|
||||
- Alternatively add our F-Droid repo https://f-droid.monerujo.io/fdroid/repo with fingerpint ```A8 2C 68 E1 4A F0 AA 6A 2E C2 0E 6B 27 2E FF 25 E5 A0 38 F3 F6 58 84 31 6E 0F 5E 0D 91 E7 B7 13``` to your F-Droid client
|
||||
- Run the App and select "Generate Wallet" to create a new wallet or recover a wallet
|
||||
- Advanced users can copy over synced wallet files (all files) onto sdcard in directory Monerujo (created first time App is started)
|
||||
- See the [FAQ](doc/FAQ.md)
|
||||
|
||||
## Translations
|
||||
Help us translate Monerujo! You can find instructions [On Taiga](https://taiga.getmonero.org/project/erciccione-monero-localization/wiki/monerujo), and if you need help/support, open an issue or contact the Localization Workgroup. You can find us on the freenode channel `#monero-translations`, also relayed on [MatterMost](https://mattermost.getmonero.org/monero/channels/monero-translations), and matrix/riot.
|
||||
|
||||
### Disclaimer
|
||||
You may lose all your Moneroj if you use this App. Be cautious when spending on the mainnet.
|
||||
|
||||
@@ -23,6 +34,8 @@ You may lose all your Moneroj if you use this App. Be cautious when spending on
|
||||
- see taiga.getmonero.org & issues on github
|
||||
|
||||
### Issues / Pitfalls
|
||||
- Users of Zenfone MAX & Zenfone 2 Laser (possibly others) **MUST** use the armeabi-v7a APK as the arm64-v8a build uses hardware AES
|
||||
functionality these models don't have.
|
||||
- You should backup your wallet files in the "monerujo" folder periodically.
|
||||
- Also note, that on some devices the backups will only be visible on a PC over USB after a reboot of the device (it's an Android bug/feature)
|
||||
- Created wallets on a private testnet are unusable because the restore height is set to that
|
||||
@@ -33,8 +46,8 @@ The official monero client shows the same behaviour.
|
||||
No need to build. Binaries are included:
|
||||
|
||||
- openssl-1.0.2l
|
||||
- monero-v0.11.1.0
|
||||
- boost_1_64_0
|
||||
- monero-v0.12
|
||||
- boost_1_58_0
|
||||
|
||||
If you want to build them yourself (recommended) check out [the instructions](doc/BUILDING-external-libs.md)
|
||||
|
||||
|
@@ -1,15 +1,14 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'witness'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion '26.0.2'
|
||||
buildToolsVersion '27.0.3'
|
||||
defaultConfig {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 25
|
||||
versionCode 85
|
||||
versionName "1.4.5 'Monero Spedner'"
|
||||
versionCode 111
|
||||
versionName "1.6.1 'Nano S'"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
@@ -17,9 +16,6 @@ android {
|
||||
arguments '-DANDROID_STL=c++_shared'
|
||||
}
|
||||
}
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -49,62 +45,46 @@ android {
|
||||
// Map for the version code that gives each ABI a value.
|
||||
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.
|
||||
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.
|
||||
variant.outputs.each {
|
||||
variant.outputs.all {
|
||||
output ->
|
||||
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
|
||||
output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode
|
||||
|
||||
if (abiName == null) abiName = "universal"
|
||||
def v = "${variant.versionName}".replaceFirst(" .*\$", "").replace(".", "x")
|
||||
outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:appcompat-v7:25.4.0'
|
||||
compile 'com.android.support:design:25.4.0'
|
||||
compile 'com.android.support:support-v4:25.4.0'
|
||||
compile 'com.android.support:recyclerview-v7:25.4.0'
|
||||
compile 'com.android.support:cardview-v7:25.4.0'
|
||||
compile 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
compile 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
implementation 'com.android.support:appcompat-v7:25.4.0'
|
||||
implementation 'com.android.support:design:25.4.0'
|
||||
implementation 'com.android.support:support-v4:25.4.0'
|
||||
implementation 'com.android.support:recyclerview-v7:25.4.0'
|
||||
implementation 'com.android.support:cardview-v7:25.4.0'
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
|
||||
compile "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
||||
compile "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
|
||||
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
||||
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
|
||||
|
||||
compile 'com.nulab-inc:zxcvbn:1.2.3'
|
||||
implementation 'com.nulab-inc:zxcvbn:1.2.3'
|
||||
|
||||
testCompile "junit:junit:$rootProject.ext.junitVersion"
|
||||
testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
||||
testCompile "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
||||
testCompile 'org.json:json:20140107'
|
||||
testCompile 'net.jodah:concurrentunit:0.4.2'
|
||||
}
|
||||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'com.android.support:design:3f409bf2019967ffc344cfaf11e52131fac982468a1707aaeb25bf3c52838966',
|
||||
'com.android.support:appcompat-v7:70551e62660db15b790c5275f56b9de4dd9407d1494d07c8f3dd5698f3638677',
|
||||
'com.android.support:transition:848270144fb180efd2bf928a00ed176dbbc5290badfd638390ffba90088df8b3',
|
||||
'me.dm7.barcodescanner:zxing:d43973c9527c23fa8e6d338c6a2c458e373ce1ac6bcaa3bc41d11ae49116000d',
|
||||
'me.dm7.barcodescanner:core:a5c8a704089b58029db166172ed8e55d756877d010a85a0b1c94fdc96ffb8f9a',
|
||||
'com.android.support:support-v4:ee44c481a1f4d6978568e223e8125379b52b2ececdd53450e09ebae144bd377d',
|
||||
'com.android.support:recyclerview-v7:a2fe121f9d01ed8980e97095b4a3fe9700a0aa0a7d4b0f8c594f765ad8455a0d',
|
||||
'com.android.support:cardview-v7:f3fbbe1fcfdbec7333c6a2c516c5fd511a909d1975271818e268d6fe297d8c70',
|
||||
'com.android.support.constraint:constraint-layout:b0c688cc2b7172608f8153a689d746da40f71e52d7e2fe2bfd9df2f92db77085',
|
||||
'com.android.support:animated-vector-drawable:628ab1d56a6ee4cbedf32617af8b2a1fe02964ed0628e8f898cc09ddba6e1835',
|
||||
'com.android.support:support-vector-drawable:077009d13882ee96f061e4bc2dbe7cce7ae1762d8297592a787ff741afbfb1f2',
|
||||
'com.android.support:support-fragment:316d35d4d2d2902057efad104a73e4bdb50bee260a7075678185b8cd71170945',
|
||||
'com.android.support:support-core-ui:e72ae29b823889686cff6fcb948d6745c2baf6d4c2af4fdffa1ec1e42e3833a3',
|
||||
'com.android.support:support-media-compat:566a161d9cb0083ef62a53e46b71ce5b3d455b8635b1a0a4ae28d96d4b583de8',
|
||||
'com.android.support:support-core-utils:34b8437dfa95ff28d29cf57ffa3b1354a9fa9bfe4059f0fd5ce2f5e4326a1748',
|
||||
'com.android.support:support-compat:54019c63614ce08b02d7b9605490cd2b29ba5b2505f394a9517450b5f72b30ca',
|
||||
'com.android.support:support-annotations:a774272036941b4e912eb426d70c848bde7f06a3bf5fb491f75a427dc6595270',
|
||||
'com.android.support.constraint:constraint-layout-solver:8c62525a9bc5cff5633a96cb9b32fffeccaf41b8841aa87fc22607070dea9b8d',
|
||||
'com.google.zxing:core:bba7724e02a997cec38213af77133ee8e24b0d5cf5fa7ecbc16a4fa93f11ee0d',
|
||||
'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850',
|
||||
'com.squareup.okhttp3:okhttp:7265adbd6f028aade307f58569d814835cd02bc9beffb70c25f72c9de50d61c4',
|
||||
'com.jakewharton.timber:timber:35c22867f2673132e97e17857d36bb2fc25f5790f0425406833ed0254d62fc66',
|
||||
'com.nulab-inc:zxcvbn:18d7862a6abd2705defec478d77dedadf8f3bb7cf811df22995494f05485785f',
|
||||
]
|
||||
testImplementation "junit:junit:$rootProject.ext.junitVersion"
|
||||
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
||||
testImplementation 'org.json:json:20140107'
|
||||
testImplementation 'net.jodah:concurrentunit:0.4.2'
|
||||
}
|
||||
|
@@ -7,13 +7,14 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<application
|
||||
android:name=".XmrWalletApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:name=".XmrWalletApplication"
|
||||
android:theme="@style/MyMaterialTheme">
|
||||
|
||||
<activity
|
||||
@@ -27,13 +28,39 @@
|
||||
android:name=".LoginActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</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
|
||||
android:name=".util.UsbEventReceiverActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:noHistory="true"
|
||||
android:process=":UsbEventReceiverActivityProcess"
|
||||
android:taskAffinity="com.m2049r.xmrwallet.taskAffinityUsbEventReceiver"
|
||||
android:theme="@style/Theme.Transparent">
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/usb_device_filter" />
|
||||
</activity-->
|
||||
|
||||
<service
|
||||
android:name=".service.WalletService"
|
||||
android:description="@string/service_description"
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@
|
||||
#define XMRWALLET_WALLET_LIB_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
/*
|
||||
#include <android/log.h>
|
||||
|
||||
@@ -27,13 +28,13 @@
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
|
||||
*/
|
||||
|
||||
jfieldID getHandleField(JNIEnv *env, jobject obj, const char* fieldName = "handle") {
|
||||
jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
||||
jclass c = env->GetObjectClass(obj);
|
||||
return env->GetFieldID(c, fieldName, "J"); // of type long
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *getHandle(JNIEnv *env, jobject obj, const char* fieldName = "handle") {
|
||||
template<typename T>
|
||||
T *getHandle(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
||||
jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName));
|
||||
return reinterpret_cast<T *>(handle);
|
||||
}
|
||||
@@ -42,10 +43,35 @@ void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) {
|
||||
env->SetLongField(obj, getHandleField(env, obj), handle);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
void setHandle(JNIEnv *env, jobject obj, T *t) {
|
||||
jlong handle = reinterpret_cast<jlong>(t);
|
||||
setHandleFromLong(env, obj, handle);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// from monero-core crypto/hash-ops.h - avoid #including monero code here
|
||||
enum {
|
||||
HASH_SIZE = 32,
|
||||
HASH_DATA_AREA = 136
|
||||
};
|
||||
|
||||
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed);
|
||||
|
||||
inline void slow_hash(const void *data, const size_t length, char *hash) {
|
||||
cn_slow_hash(data, length, hash, 0 /* variant */, 0/*prehashed*/);
|
||||
}
|
||||
|
||||
inline void slow_hash_broken(const void *data, char *hash, int variant) {
|
||||
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //XMRWALLET_WALLET_LIB_H
|
||||
|
46
app/src/main/cpp/monerujo_ledger.h
Normal file
46
app/src/main/cpp/monerujo_ledger.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2018 m2049r
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef XMRWALLET_LEDGER_H
|
||||
#define XMRWALLET_LEDGER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#define SCARD_S_SUCCESS ((LONG)0x00000000) /**< No error was encountered. */
|
||||
#define SCARD_E_INSUFFICIENT_BUFFER ((LONG)0x80100008) /**< The data buffer to receive returned data is too small for the returned data. */
|
||||
#define SCARD_E_NO_READERS_AVAILABLE ((LONG)0x8010002E) /**< Cannot find a smart card reader. */
|
||||
|
||||
typedef long LONG;
|
||||
typedef unsigned long DWORD;
|
||||
typedef DWORD *LPDWORD;
|
||||
typedef unsigned char BYTE;
|
||||
typedef BYTE *LPBYTE;
|
||||
typedef const BYTE *LPCBYTE;
|
||||
|
||||
typedef char CHAR;
|
||||
typedef CHAR *LPSTR;
|
||||
|
||||
int LedgerFind(char *buffer, size_t len);
|
||||
LONG LedgerExchange(LPCBYTE pbSendBuffer, DWORD cbSendLength, LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //XMRWALLET_LEDGER_H
|
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();
|
||||
}
|
||||
|
||||
|
||||
}
|
105
app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
Normal file
105
app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
Normal file
@@ -0,0 +1,105 @@
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import com.m2049r.xmrwallet.dialog.ProgressDialog;
|
||||
import com.m2049r.xmrwallet.ledger.Ledger;
|
||||
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class BaseActivity extends SecureActivity implements GenerateReviewFragment.ProgressListener {
|
||||
|
||||
ProgressDialog progressDialog = null;
|
||||
|
||||
private class SimpleProgressDialog extends ProgressDialog {
|
||||
|
||||
SimpleProgressDialog(Context context, int msgId) {
|
||||
super(context);
|
||||
setCancelable(false);
|
||||
setMessage(context.getString(msgId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// prevent back button
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showProgressDialog(int msgId) {
|
||||
showProgressDialog(msgId, 0);
|
||||
}
|
||||
|
||||
public void showProgressDialog(int msgId, long delay) {
|
||||
dismissProgressDialog(); // just in case
|
||||
progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
|
||||
if (delay > 0) {
|
||||
Handler handler = new Handler();
|
||||
handler.postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
if (progressDialog != null) progressDialog.show();
|
||||
}
|
||||
}, delay);
|
||||
} else {
|
||||
progressDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLedgerProgressDialog(int mode) {
|
||||
dismissProgressDialog(); // just in case
|
||||
progressDialog = new LedgerProgressDialog(BaseActivity.this, mode);
|
||||
Ledger.setListener((Ledger.Listener) progressDialog);
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismissProgressDialog() {
|
||||
if (progressDialog == null) return; // nothing to do
|
||||
if (progressDialog instanceof Ledger.Listener) {
|
||||
Ledger.unsetListener((Ledger.Listener) progressDialog);
|
||||
}
|
||||
if (progressDialog.isShowing()) {
|
||||
progressDialog.dismiss();
|
||||
}
|
||||
progressDialog = null;
|
||||
}
|
||||
|
||||
static final int RELEASE_WAKE_LOCK_DELAY = 5000; // millisconds
|
||||
|
||||
private PowerManager.WakeLock wl = null;
|
||||
|
||||
void acquireWakeLock() {
|
||||
if ((wl != null) && wl.isHeld()) return;
|
||||
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
this.wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name));
|
||||
try {
|
||||
wl.acquire();
|
||||
Timber.d("WakeLock acquired");
|
||||
} catch (SecurityException ex) {
|
||||
Timber.w("WakeLock NOT acquired: %s", ex.getLocalizedMessage());
|
||||
wl = null;
|
||||
}
|
||||
}
|
||||
|
||||
void releaseWakeLock(int delayMillis) {
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
releaseWakeLock();
|
||||
}
|
||||
}, delayMillis);
|
||||
}
|
||||
|
||||
void releaseWakeLock() {
|
||||
if ((wl == null) || !wl.isHeld()) return;
|
||||
wl.release();
|
||||
wl = null;
|
||||
Timber.d("WakeLock released");
|
||||
}
|
||||
}
|
@@ -16,12 +16,15 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
@@ -32,20 +35,23 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.FingerprintHelper;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
import com.nulabinc.zxcvbn.Strength;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -55,10 +61,12 @@ public class GenerateFragment extends Fragment {
|
||||
static final String TYPE_NEW = "new";
|
||||
static final String TYPE_KEY = "key";
|
||||
static final String TYPE_SEED = "seed";
|
||||
static final String TYPE_LEDGER = "ledger";
|
||||
static final String TYPE_VIEWONLY = "view";
|
||||
|
||||
private TextInputLayout etWalletName;
|
||||
private TextInputLayout etWalletPassword;
|
||||
private LinearLayout llFingerprintAuth;
|
||||
private TextInputLayout etWalletAddress;
|
||||
private TextInputLayout etWalletMnemonic;
|
||||
private TextInputLayout etWalletViewKey;
|
||||
@@ -79,6 +87,7 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
etWalletName = (TextInputLayout) view.findViewById(R.id.etWalletName);
|
||||
etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword);
|
||||
llFingerprintAuth = (LinearLayout) view.findViewById(R.id.llFingerprintAuth);
|
||||
etWalletMnemonic = (TextInputLayout) view.findViewById(R.id.etWalletMnemonic);
|
||||
etWalletAddress = (TextInputLayout) view.findViewById(R.id.etWalletAddress);
|
||||
etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey);
|
||||
@@ -146,6 +155,30 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
if (FingerprintHelper.isDeviceSupported(getContext())) {
|
||||
llFingerprintAuth.setVisibility(View.VISIBLE);
|
||||
|
||||
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0);
|
||||
swFingerprintAllowed.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (!swFingerprintAllowed.isChecked()) return;
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setMessage(Html.fromHtml(getString(R.string.generate_fingerprint_warn)))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(R.string.label_ok), null)
|
||||
.setNegativeButton(getString(R.string.label_cancel), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
swFingerprintAllowed.setChecked(false);
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (type.equals(TYPE_NEW)) {
|
||||
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@@ -158,6 +191,17 @@ public class GenerateFragment extends Fragment {
|
||||
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)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
etWalletRestoreHeight.requestFocus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else if (type.equals(TYPE_SEED)) {
|
||||
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
@@ -359,16 +403,24 @@ public class GenerateFragment extends Fragment {
|
||||
// is it a date?
|
||||
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
|
||||
parser.setLenient(false);
|
||||
parser.parse(restoreHeight);
|
||||
height = RestoreHeight.getInstance().getHeight(restoreHeight);
|
||||
} catch (ParseException exPE) {
|
||||
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||
} catch (ParseException ex) {
|
||||
}
|
||||
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 {
|
||||
// or is it a height?
|
||||
height = Long.parseLong(restoreHeight);
|
||||
} catch (NumberFormatException exNFE) {
|
||||
} catch (NumberFormatException ex) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
Timber.d("Using Restore Height = %d", height);
|
||||
return height;
|
||||
}
|
||||
@@ -423,18 +475,34 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
String name = etWalletName.getEditText().getText().toString();
|
||||
String password = etWalletPassword.getEditText().getText().toString();
|
||||
boolean fingerprintAuthAllowed = ((Switch) llFingerprintAuth.getChildAt(0)).isChecked();
|
||||
|
||||
// create the real wallet password
|
||||
String crazyPass = KeyStoreHelper.getCrazyPass(getActivity(), password);
|
||||
|
||||
long height = getHeight();
|
||||
if (height < 0) height = 0;
|
||||
|
||||
if (type.equals(TYPE_NEW)) {
|
||||
bGenerate.setEnabled(false);
|
||||
activityCallback.onGenerate(name, password);
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||
}
|
||||
activityCallback.onGenerate(name, crazyPass);
|
||||
} else if (type.equals(TYPE_SEED)) {
|
||||
if (!checkMnemonic()) return;
|
||||
String seed = etWalletMnemonic.getEditText().getText().toString();
|
||||
bGenerate.setEnabled(false);
|
||||
activityCallback.onGenerate(name, password, seed, height);
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||
}
|
||||
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)) {
|
||||
if (checkAddress() && checkViewKey() && checkSpendKey()) {
|
||||
bGenerate.setEnabled(false);
|
||||
@@ -444,7 +512,10 @@ public class GenerateFragment extends Fragment {
|
||||
if (type.equals(TYPE_KEY)) {
|
||||
spendKey = etWalletSpendKey.getEditText().getText().toString();
|
||||
}
|
||||
activityCallback.onGenerate(name, password, address, viewKey, spendKey, height);
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||
}
|
||||
activityCallback.onGenerate(name, crazyPass, address, viewKey, spendKey, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -470,6 +541,8 @@ public class GenerateFragment extends Fragment {
|
||||
return getString(R.string.generate_wallet_type_new);
|
||||
case TYPE_SEED:
|
||||
return getString(R.string.generate_wallet_type_seed);
|
||||
case TYPE_LEDGER:
|
||||
return getString(R.string.generate_wallet_type_ledger);
|
||||
case TYPE_VIEWONLY:
|
||||
return getString(R.string.generate_wallet_type_view);
|
||||
default:
|
||||
@@ -487,6 +560,8 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
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 setToolbarButton(int type);
|
||||
@@ -522,6 +597,9 @@ public class GenerateFragment extends Fragment {
|
||||
case TYPE_SEED:
|
||||
inflater.inflate(R.menu.create_wallet_seed, menu);
|
||||
break;
|
||||
case TYPE_LEDGER:
|
||||
inflater.inflate(R.menu.create_wallet_ledger, menu);
|
||||
break;
|
||||
case TYPE_VIEWONLY:
|
||||
inflater.inflate(R.menu.create_wallet_view, menu);
|
||||
break;
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -16,13 +16,12 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
|
||||
import java.io.File;
|
||||
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams;
|
||||
|
||||
@@ -36,4 +35,9 @@ public abstract class SecureActivity extends AppCompatActivity {
|
||||
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context context) {
|
||||
super.attachBaseContext(LocaleHelper.setLocale(context, LocaleHelper.getLocale(context)));
|
||||
}
|
||||
}
|
||||
|
@@ -59,6 +59,7 @@ public class TxFragment extends Fragment {
|
||||
TS_FORMATTER.setTimeZone(tz);
|
||||
}
|
||||
|
||||
private TextView tvAccount;
|
||||
private TextView tvTxTimestamp;
|
||||
private TextView tvTxId;
|
||||
private TextView tvTxKey;
|
||||
@@ -88,6 +89,7 @@ public class TxFragment extends Fragment {
|
||||
tvDestinationBtc = (TextView) view.findViewById(R.id.tvDestinationBtc);
|
||||
tvTxAmountBtc = (TextView) view.findViewById(R.id.tvTxAmountBtc);
|
||||
|
||||
tvAccount = (TextView) view.findViewById(R.id.tvAccount);
|
||||
tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
|
||||
tvTxId = (TextView) view.findViewById(R.id.tvTxId);
|
||||
tvTxKey = (TextView) view.findViewById(R.id.tvTxKey);
|
||||
@@ -222,6 +224,8 @@ public class TxFragment extends Fragment {
|
||||
activityCallback.setSubtitle(getString(R.string.tx_title));
|
||||
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||
|
||||
tvAccount.setText(getString(R.string.tx_account_formatted, info.account, info.subaddress));
|
||||
|
||||
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
|
||||
tvTxId.setText(info.hash);
|
||||
tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey);
|
||||
@@ -236,9 +240,6 @@ public class TxFragment extends Fragment {
|
||||
String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-");
|
||||
|
||||
long realAmount = info.amount;
|
||||
if (info.isPending) {
|
||||
realAmount = realAmount - info.fee;
|
||||
}
|
||||
tvTxAmount.setText(sign + Wallet.getDisplayAmount(realAmount));
|
||||
|
||||
if ((info.fee > 0)) {
|
||||
@@ -286,7 +287,10 @@ public class TxFragment extends Fragment {
|
||||
}
|
||||
} else {
|
||||
sb.append("-");
|
||||
dstSb.append(info.direction == TransactionInfo.Direction.Direction_In ? activityCallback.getWalletAddress() : "-");
|
||||
dstSb.append(info.direction ==
|
||||
TransactionInfo.Direction.Direction_In ?
|
||||
activityCallback.getWalletSubaddress(info.account, info.subaddress) :
|
||||
"-");
|
||||
}
|
||||
tvTxTransfers.setText(sb.toString());
|
||||
tvDestination.setText(dstSb.toString());
|
||||
@@ -321,7 +325,7 @@ public class TxFragment extends Fragment {
|
||||
Listener activityCallback;
|
||||
|
||||
public interface Listener {
|
||||
String getWalletAddress();
|
||||
String getWalletSubaddress(int accountIndex, int subaddressIndex);
|
||||
|
||||
String getTxKey(String hash);
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -43,9 +43,7 @@ import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
|
||||
import com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
@@ -101,7 +99,9 @@ public class WalletFragment extends Fragment
|
||||
ivSynced = (ImageView) view.findViewById(R.id.ivSynced);
|
||||
|
||||
sCurrency = (Spinner) view.findViewById(R.id.sCurrency);
|
||||
sCurrency.setAdapter(ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner_balance));
|
||||
ArrayAdapter currencyAdapter = ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner_balance);
|
||||
currencyAdapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item);
|
||||
sCurrency.setAdapter(currencyAdapter);
|
||||
|
||||
bSend = (Button) view.findViewById(R.id.bSend);
|
||||
bReceive = (Button) view.findViewById(R.id.bReceive);
|
||||
@@ -150,7 +150,7 @@ public class WalletFragment extends Fragment
|
||||
// at this point selection is XMR in case of error
|
||||
String displayB;
|
||||
double amountA = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // crash if this fails!
|
||||
if (!"XMR".equals(balanceCurrency)) { // not XMR
|
||||
if (!Helper.CRYPTO.equals(balanceCurrency)) { // not XMR
|
||||
double amountB = amountA * balanceRate;
|
||||
displayB = Helper.getFormattedAmount(amountB, false);
|
||||
} else { // XMR
|
||||
@@ -159,10 +159,10 @@ public class WalletFragment extends Fragment
|
||||
tvBalance.setText(displayB);
|
||||
}
|
||||
|
||||
String balanceCurrency = "XMR";
|
||||
String balanceCurrency = Helper.CRYPTO;
|
||||
double balanceRate = 1.0;
|
||||
|
||||
private final ExchangeApi exchangeApi = new ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient());
|
||||
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
|
||||
|
||||
void refreshBalance() {
|
||||
if (sCurrency.getSelectedItemPosition() == 0) { // XMR
|
||||
@@ -170,9 +170,10 @@ public class WalletFragment extends Fragment
|
||||
tvBalance.setText(Helper.getFormattedAmount(amountXmr, true));
|
||||
} else { // not XMR
|
||||
String currency = (String) sCurrency.getSelectedItem();
|
||||
Timber.d(currency);
|
||||
if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) {
|
||||
showExchanging();
|
||||
exchangeApi.queryExchangeRate("XMR", currency,
|
||||
exchangeApi.queryExchangeRate(Helper.CRYPTO, currency,
|
||||
new ExchangeCallback() {
|
||||
@Override
|
||||
public void onSuccess(final ExchangeRate exchangeRate) {
|
||||
@@ -228,10 +229,10 @@ public class WalletFragment extends Fragment
|
||||
|
||||
public void exchange(final ExchangeRate exchangeRate) {
|
||||
hideExchanging();
|
||||
if (!"XMR".equals(exchangeRate.getBaseCurrency())) {
|
||||
if (!Helper.CRYPTO.equals(exchangeRate.getBaseCurrency())) {
|
||||
Timber.e("Not XMR");
|
||||
sCurrency.setSelection(0, true);
|
||||
balanceCurrency = "XMR";
|
||||
balanceCurrency = Helper.CRYPTO;
|
||||
balanceRate = 1.0;
|
||||
} else {
|
||||
int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency());
|
||||
@@ -256,7 +257,7 @@ public class WalletFragment extends Fragment
|
||||
// called from activity
|
||||
|
||||
public void onRefreshed(final Wallet wallet, final boolean full) {
|
||||
Timber.d("onRefreshed()");
|
||||
Timber.d("onRefreshed(%b)", full);
|
||||
if (full) {
|
||||
List<TransactionInfo> list = wallet.getHistory().getAll();
|
||||
adapter.setInfos(list);
|
||||
@@ -270,6 +271,7 @@ public class WalletFragment extends Fragment
|
||||
bSend.setVisibility(View.VISIBLE);
|
||||
bSend.setEnabled(true);
|
||||
}
|
||||
if (isVisible()) enableAccountsList(true); //otherwise it is enabled in onResume()
|
||||
}
|
||||
|
||||
boolean walletLoaded = false;
|
||||
@@ -313,7 +315,7 @@ public class WalletFragment extends Fragment
|
||||
if (wallet == null) return;
|
||||
walletTitle = wallet.getName();
|
||||
String watchOnly = (wallet.isWatchOnly() ? getString(R.string.label_watchonly) : "");
|
||||
walletSubtitle = wallet.getAddress().substring(0, 10) + "…" + watchOnly;
|
||||
walletSubtitle = wallet.getAccountLabel();
|
||||
activityCallback.setTitle(walletTitle, walletSubtitle);
|
||||
Timber.d("wallet title is %s", walletTitle);
|
||||
}
|
||||
@@ -323,10 +325,13 @@ public class WalletFragment extends Fragment
|
||||
private String walletSubtitle = null;
|
||||
private long unlockedBalance = 0;
|
||||
|
||||
private int accountIdx = -1;
|
||||
|
||||
private void updateStatus(Wallet wallet) {
|
||||
if (!isAdded()) return;
|
||||
Timber.d("updateStatus()");
|
||||
if (walletTitle == null) {
|
||||
if ((walletTitle == null) || (accountIdx != wallet.getAccountIndex())) {
|
||||
accountIdx = wallet.getAccountIndex();
|
||||
setActivityTitle(wallet);
|
||||
}
|
||||
long balance = wallet.getBalance();
|
||||
@@ -352,7 +357,7 @@ public class WalletFragment extends Fragment
|
||||
setProgress(x);
|
||||
ivSynced.setVisibility(View.GONE);
|
||||
} else {
|
||||
sync = getString(R.string.status_synced) + formatter.format(wallet.getBlockChainHeight());
|
||||
sync = getString(R.string.status_synced) + " " + formatter.format(wallet.getBlockChainHeight());
|
||||
ivSynced.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
@@ -412,9 +417,28 @@ public class WalletFragment extends Fragment
|
||||
super.onResume();
|
||||
Timber.d("onResume()");
|
||||
activityCallback.setTitle(walletTitle, walletSubtitle);
|
||||
activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE);
|
||||
//activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE); // TODO: Close button somewhere else
|
||||
activityCallback.setToolbarButton(Toolbar.BUTTON_NONE);
|
||||
setProgress(syncProgress);
|
||||
setProgress(syncText);
|
||||
showReceive();
|
||||
if (activityCallback.isSynced()) enableAccountsList(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
enableAccountsList(false);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
public interface DrawerLocker {
|
||||
void setDrawerEnabled(boolean enabled);
|
||||
}
|
||||
|
||||
private void enableAccountsList(boolean enable) {
|
||||
if (activityCallback instanceof DrawerLocker) {
|
||||
((DrawerLocker) activityCallback).setDrawerEnabled(enable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -18,8 +18,10 @@ package com.m2049r.xmrwallet;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
@@ -27,9 +29,21 @@ public class XmrWalletApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@@ -86,6 +86,10 @@ public class WalletNode {
|
||||
return name;
|
||||
}
|
||||
|
||||
public NetworkType getNetworkType() {
|
||||
return networkType;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return host + ":" + port;
|
||||
}
|
||||
|
@@ -0,0 +1,130 @@
|
||||
package com.m2049r.xmrwallet.dialog;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
* Copyright (C) 2018 m2049r
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ProgressDialog extends AlertDialog {
|
||||
|
||||
private ProgressBar pbBar;
|
||||
|
||||
private TextView tvMessage;
|
||||
|
||||
private TextView tvProgress;
|
||||
|
||||
private View rlProgressBar, pbCircle;
|
||||
|
||||
static private final String PROGRESS_FORMAT = "%1d/%2d";
|
||||
|
||||
private CharSequence message;
|
||||
private int maxValue, progressValue;
|
||||
private boolean indeterminate = true;
|
||||
|
||||
public ProgressDialog(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ledger_progress, null);
|
||||
pbCircle = view.findViewById(R.id.pbCircle);
|
||||
tvMessage = (TextView) view.findViewById(R.id.tvMessage);
|
||||
rlProgressBar = view.findViewById(R.id.rlProgressBar);
|
||||
pbBar = (ProgressBar) view.findViewById(R.id.pbBar);
|
||||
tvProgress = (TextView) view.findViewById(R.id.tvProgress);
|
||||
setView(view);
|
||||
//setTitle("blabla");
|
||||
//super.setMessage("bubbu");
|
||||
// view.invalidate();
|
||||
setIndeterminate(indeterminate);
|
||||
if (maxValue > 0) {
|
||||
setMax(maxValue);
|
||||
}
|
||||
if (progressValue > 0) {
|
||||
setProgress(progressValue);
|
||||
}
|
||||
if (message != null) {
|
||||
Timber.d("msg=%s", message);
|
||||
setMessage(message);
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
public void setProgress(int value, int max) {
|
||||
progressValue = value;
|
||||
maxValue = max;
|
||||
if (pbBar != null) {
|
||||
pbBar.setProgress(value);
|
||||
pbBar.setMax(max);
|
||||
tvProgress.setText(String.format(Locale.getDefault(), PROGRESS_FORMAT, value, maxValue));
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(int value) {
|
||||
progressValue = value;
|
||||
if (pbBar != null) {
|
||||
pbBar.setProgress(value);
|
||||
tvProgress.setText(String.format(Locale.getDefault(), PROGRESS_FORMAT, value, maxValue));
|
||||
}
|
||||
}
|
||||
|
||||
public void setMax(int max) {
|
||||
maxValue = max;
|
||||
if (pbBar != null) {
|
||||
pbBar.setMax(max);
|
||||
}
|
||||
}
|
||||
|
||||
public void setIndeterminate(boolean indeterminate) {
|
||||
if (this.indeterminate != indeterminate) {
|
||||
if (rlProgressBar != null) {
|
||||
if (indeterminate) {
|
||||
pbCircle.setVisibility(View.VISIBLE);
|
||||
rlProgressBar.setVisibility(View.GONE);
|
||||
} else {
|
||||
pbCircle.setVisibility(View.GONE);
|
||||
rlProgressBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
this.indeterminate = indeterminate;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMessage(CharSequence message) {
|
||||
this.message = message;
|
||||
if (tvMessage != null) {
|
||||
tvMessage.setText(message);
|
||||
}
|
||||
}
|
||||
}
|
@@ -233,7 +233,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
|
||||
private boolean isBitcoinAddress() {
|
||||
String address = etAddress.getEditText().getText().toString();
|
||||
if ((address.length() >= 27) && (address.length() <= 34))
|
||||
if ((address.length() >= 27) && (address.length() <= 35))
|
||||
return BitcoinAddressValidator.validate(address);
|
||||
else
|
||||
return false;
|
||||
|
@@ -20,6 +20,9 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
@@ -57,8 +60,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
|
||||
private TextView tvFunds;
|
||||
private ExchangeTextView evAmount;
|
||||
//private Button bSendAll;
|
||||
private NumberPadView numberPad;
|
||||
private View llAmount;
|
||||
private View rlSweep;
|
||||
private ImageButton ibSweep;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
@@ -73,37 +77,64 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
tvFunds = (TextView) view.findViewById(R.id.tvFunds);
|
||||
|
||||
evAmount = (ExchangeTextView) view.findViewById(R.id.evAmount);
|
||||
numberPad = (NumberPadView) view.findViewById(R.id.numberPad);
|
||||
numberPad.setListener(evAmount);
|
||||
((NumberPadView) view.findViewById(R.id.numberPad)).setListener(evAmount);
|
||||
|
||||
/*
|
||||
bSendAll = (Button) view.findViewById(R.id.bSendAll);
|
||||
bSendAll.setOnClickListener(new View.OnClickListener() {
|
||||
rlSweep = view.findViewById(R.id.rlSweep);
|
||||
llAmount = view.findViewById(R.id.llAmount);
|
||||
view.findViewById(R.id.ivSweep).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// TODO: send all - figure out how to display this
|
||||
sweepAll(false);
|
||||
}
|
||||
});
|
||||
|
||||
ibSweep = (ImageButton) view.findViewById(R.id.ibSweep);
|
||||
|
||||
ibSweep.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
sweepAll(true);
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
Helper.hideKeyboard(getActivity());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private boolean spendAllMode = false;
|
||||
|
||||
private void sweepAll(boolean spendAllMode) {
|
||||
if (spendAllMode) {
|
||||
ibSweep.setVisibility(View.INVISIBLE);
|
||||
llAmount.setVisibility(View.GONE);
|
||||
rlSweep.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
ibSweep.setVisibility(View.VISIBLE);
|
||||
llAmount.setVisibility(View.VISIBLE);
|
||||
rlSweep.setVisibility(View.GONE);
|
||||
}
|
||||
this.spendAllMode = spendAllMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onValidateFields() {
|
||||
if (!evAmount.validate(maxFunds)) {
|
||||
return false;
|
||||
}
|
||||
if (spendAllMode) {
|
||||
if (sendListener != null) {
|
||||
sendListener.getTxData().setAmount(Wallet.SWEEP_ALL);
|
||||
}
|
||||
} else {
|
||||
if (!evAmount.validate(maxFunds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sendListener != null) {
|
||||
String xmr = evAmount.getAmount();
|
||||
if (xmr != null) {
|
||||
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
|
||||
} else {
|
||||
sendListener.getTxData().setAmount(0L);
|
||||
if (sendListener != null) {
|
||||
String xmr = evAmount.getAmount();
|
||||
if (xmr != null) {
|
||||
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
|
||||
} else {
|
||||
sendListener.getTxData().setAmount(0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@@ -247,7 +247,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
|
||||
private XmrToApi xmrToApi = null;
|
||||
|
||||
private final XmrToApi getXmrToApi() {
|
||||
private XmrToApi getXmrToApi() {
|
||||
if (xmrToApi == null) {
|
||||
synchronized (this) {
|
||||
if (xmrToApi == null) {
|
||||
|
@@ -47,6 +47,7 @@ import com.m2049r.xmrwallet.layout.SpendViewPager;
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.NodeList;
|
||||
import com.m2049r.xmrwallet.util.Notice;
|
||||
import com.m2049r.xmrwallet.util.UserNotes;
|
||||
import com.m2049r.xmrwallet.widget.DotBar;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
@@ -119,27 +120,8 @@ public class SendFragment extends Fragment
|
||||
arrowPrev = getResources().getDrawable(R.drawable.ic_navigate_prev_white_24dp);
|
||||
arrowNext = getResources().getDrawable(R.drawable.ic_navigate_next_white_24dp);
|
||||
|
||||
llXmrToEnabled = view.findViewById(R.id.llXmrToEnabled);
|
||||
llXmrToEnabled.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HelpFragment.display(getChildFragmentManager(), R.string.help_xmrto);
|
||||
|
||||
}
|
||||
});
|
||||
ibXmrToInfoClose = view.findViewById(R.id.ibXmrToInfoClose);
|
||||
ibXmrToInfoClose.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
showXmrtoEnabled = false;
|
||||
saveXmrToPrefs();
|
||||
}
|
||||
});
|
||||
loadPrefs();
|
||||
if (!showXmrtoEnabled) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
}
|
||||
ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
|
||||
Notice.showAll(llNotice,".*_send");
|
||||
|
||||
spendViewPager = (SpendViewPager) view.findViewById(R.id.pager);
|
||||
pagerAdapter = new SpendPagerAdapter(getChildFragmentManager());
|
||||
@@ -291,7 +273,7 @@ public class SendFragment extends Fragment
|
||||
pagerAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
Timber.d("New Mode = " + mode.toString());
|
||||
Timber.d("New Mode = %s", mode.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +332,7 @@ public class SendFragment extends Fragment
|
||||
@Override
|
||||
public SendWizardFragment getItem(int position) {
|
||||
Timber.d("getItem(%d) CREATE", position);
|
||||
Timber.d("Mode=" + mode.toString());
|
||||
Timber.d("Mode=%s", mode.toString());
|
||||
if (mode == Mode.XMR) {
|
||||
switch (position) {
|
||||
case POS_ADDRESS:
|
||||
|
@@ -36,6 +36,7 @@ import com.m2049r.xmrwallet.util.UserNotes;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class SendSettingsWizardFragment extends SendWizardFragment {
|
||||
final static public int MIXIN = 6;
|
||||
|
||||
public static SendSettingsWizardFragment newInstance(Listener listener) {
|
||||
SendSettingsWizardFragment instance = new SendSettingsWizardFragment();
|
||||
@@ -54,15 +55,12 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
|
||||
TxData getTxData();
|
||||
}
|
||||
|
||||
// Mixin = Ringsize - 1
|
||||
final static int Mixins[] = {6, 9, 12, 25}; // must match the layout XML / "@array/mixin"
|
||||
final static PendingTransaction.Priority Priorities[] =
|
||||
{PendingTransaction.Priority.Priority_Default,
|
||||
PendingTransaction.Priority.Priority_Low,
|
||||
PendingTransaction.Priority.Priority_Medium,
|
||||
PendingTransaction.Priority.Priority_High}; // must match the layout XML
|
||||
|
||||
private Spinner sMixin;
|
||||
private Spinner sPriority;
|
||||
private EditText etNotes;
|
||||
private EditText etDummy;
|
||||
@@ -77,7 +75,6 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
|
||||
View view = inflater.inflate(
|
||||
R.layout.fragment_send_settings, container, false);
|
||||
|
||||
sMixin = (Spinner) view.findViewById(R.id.sMixin);
|
||||
sPriority = (Spinner) view.findViewById(R.id.sPriority);
|
||||
|
||||
etNotes = (EditText) view.findViewById(R.id.etNotes);
|
||||
@@ -104,7 +101,7 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
|
||||
if (sendListener != null) {
|
||||
TxData txData = sendListener.getTxData();
|
||||
txData.setPriority(Priorities[sPriority.getSelectedItemPosition()]);
|
||||
txData.setMixin(Mixins[sMixin.getSelectedItemPosition()]);
|
||||
txData.setMixin(MIXIN);
|
||||
txData.setUserNotes(new UserNotes(etNotes.getText().toString()));
|
||||
}
|
||||
return true;
|
||||
|
@@ -166,7 +166,11 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
}
|
||||
|
||||
if ((userNotes.note.isEmpty())) {
|
||||
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ? "" : infoItem.paymentId);
|
||||
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ?
|
||||
(infoItem.subaddress != 0 ?
|
||||
(context.getString(R.string.tx_subaddress, infoItem.subaddress)) :
|
||||
"") :
|
||||
infoItem.paymentId);
|
||||
} else {
|
||||
this.tvPaymentId.setText(userNotes.note);
|
||||
}
|
||||
|
142
app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java
Normal file
142
app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright (c) 2018 m2049r
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.ledger;
|
||||
|
||||
public enum Instruction {
|
||||
INS_NONE(0x00),
|
||||
INS_RESET(0x02),
|
||||
INS_GET_KEY(0x20),
|
||||
INS_PUT_KEY(0x22),
|
||||
INS_GET_CHACHA8_PREKEY(0x24),
|
||||
INS_VERIFY_KEY(0x26),
|
||||
|
||||
INS_SECRET_KEY_TO_PUBLIC_KEY(0x30),
|
||||
INS_GEN_KEY_DERIVATION(0x32),
|
||||
INS_DERIVATION_TO_SCALAR(0x34),
|
||||
INS_DERIVE_PUBLIC_KEY(0x36),
|
||||
INS_DERIVE_SECRET_KEY(0x38),
|
||||
INS_GEN_KEY_IMAGE(0x3A),
|
||||
INS_SECRET_KEY_ADD(0x3C),
|
||||
INS_SECRET_KEY_SUB(0x3E),
|
||||
INS_GENERATE_KEYPAIR(0x40),
|
||||
INS_SECRET_SCAL_MUL_KEY(0x42),
|
||||
INS_SECRET_SCAL_MUL_BASE(0x44),
|
||||
|
||||
INS_DERIVE_SUBADDRESS_PUBLIC_KEY(0x46),
|
||||
INS_GET_SUBADDRESS(0x48),
|
||||
INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY(0x4A),
|
||||
INS_GET_SUBADDRESS_SECRET_KEY(0x4C),
|
||||
|
||||
INS_OPEN_TX(0x70),
|
||||
INS_SET_SIGNATURE_MODE(0x72),
|
||||
INS_GET_ADDITIONAL_KEY(0x74),
|
||||
INS_STEALTH(0x76),
|
||||
INS_BLIND(0x78),
|
||||
INS_UNBLIND(0x7A),
|
||||
INS_VALIDATE(0x7C),
|
||||
INS_MLSAG(0x7E),
|
||||
INS_CLOSE_TX(0x80),
|
||||
|
||||
INS_GET_RESPONSE(0xc0),
|
||||
|
||||
INS_UNDEFINED(0xff);;
|
||||
|
||||
public static Instruction fromByte(byte n) {
|
||||
switch (n & 0xFF) {
|
||||
case 0x00:
|
||||
return INS_NONE;
|
||||
case 0x02:
|
||||
return INS_RESET;
|
||||
|
||||
case 0x20:
|
||||
return INS_GET_KEY;
|
||||
case 0x22:
|
||||
return INS_PUT_KEY;
|
||||
case 0x24:
|
||||
return INS_GET_CHACHA8_PREKEY;
|
||||
case 0x26:
|
||||
return INS_VERIFY_KEY;
|
||||
|
||||
case 0x30:
|
||||
return INS_SECRET_KEY_TO_PUBLIC_KEY;
|
||||
case 0x32:
|
||||
return INS_GEN_KEY_DERIVATION;
|
||||
case 0x34:
|
||||
return INS_DERIVATION_TO_SCALAR;
|
||||
case 0x36:
|
||||
return INS_DERIVE_PUBLIC_KEY;
|
||||
case 0x38:
|
||||
return INS_DERIVE_SECRET_KEY;
|
||||
case 0x3A:
|
||||
return INS_GEN_KEY_IMAGE;
|
||||
case 0x3C:
|
||||
return INS_SECRET_KEY_ADD;
|
||||
case 0x3E:
|
||||
return INS_SECRET_KEY_SUB;
|
||||
case 0x40:
|
||||
return INS_GENERATE_KEYPAIR;
|
||||
case 0x42:
|
||||
return INS_SECRET_SCAL_MUL_KEY;
|
||||
case 0x44:
|
||||
return INS_SECRET_SCAL_MUL_BASE;
|
||||
|
||||
case 0x46:
|
||||
return INS_DERIVE_SUBADDRESS_PUBLIC_KEY;
|
||||
case 0x48:
|
||||
return INS_GET_SUBADDRESS;
|
||||
case 0x4A:
|
||||
return INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY;
|
||||
case 0x4C:
|
||||
return INS_GET_SUBADDRESS_SECRET_KEY;
|
||||
|
||||
case 0x70:
|
||||
return INS_OPEN_TX;
|
||||
case 0x72:
|
||||
return INS_SET_SIGNATURE_MODE;
|
||||
case 0x74:
|
||||
return INS_GET_ADDITIONAL_KEY;
|
||||
case 0x76:
|
||||
return INS_STEALTH;
|
||||
case 0x78:
|
||||
return INS_BLIND;
|
||||
case 0x7A:
|
||||
return INS_UNBLIND;
|
||||
case 0x7C:
|
||||
return INS_VALIDATE;
|
||||
case 0x7E:
|
||||
return INS_MLSAG;
|
||||
case 0x80:
|
||||
return INS_CLOSE_TX;
|
||||
|
||||
case 0xc0:
|
||||
return INS_GET_RESPONSE;
|
||||
|
||||
default:
|
||||
return INS_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
private int value;
|
||||
|
||||
Instruction(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user