mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-04 00:53:36 +02:00
Compare commits
84 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 |
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 86
|
||||
versionName "1.4.6 'Monero Spedner'"
|
||||
versionCode 100
|
||||
versionName "1.5.10 'Maximum Nacho'"
|
||||
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 {
|
||||
@@ -52,59 +48,34 @@ android {
|
||||
// APKs for the same app that all have the same version information.
|
||||
android.applicationVariants.all { variant ->
|
||||
// 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,6 +7,7 @@
|
||||
<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:allowBackup="true"
|
||||
|
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
|
||||
|
@@ -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;
|
||||
|
||||
@@ -59,6 +65,7 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
private TextInputLayout etWalletName;
|
||||
private TextInputLayout etWalletPassword;
|
||||
private LinearLayout llFingerprintAuth;
|
||||
private TextInputLayout etWalletAddress;
|
||||
private TextInputLayout etWalletMnemonic;
|
||||
private TextInputLayout etWalletViewKey;
|
||||
@@ -79,6 +86,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 +154,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() {
|
||||
@@ -423,18 +455,28 @@ 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_KEY) || type.equals(TYPE_VIEWONLY)) {
|
||||
if (checkAddress() && checkViewKey() && checkSpendKey()) {
|
||||
bGenerate.setEnabled(false);
|
||||
@@ -444,7 +486,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -43,18 +43,20 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.m2049r.xmrwallet.dialog.HelpFragment;
|
||||
import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
|
||||
import com.m2049r.xmrwallet.model.NetworkType;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.NodeList;
|
||||
import com.m2049r.xmrwallet.util.Notice;
|
||||
import com.m2049r.xmrwallet.widget.DropDownEditText;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -71,9 +73,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
private DropDownEditText etDaemonAddress;
|
||||
private ArrayAdapter<String> nodeAdapter;
|
||||
|
||||
private View llXmrToEnabled;
|
||||
private View ibXmrToInfoClose;
|
||||
|
||||
private Listener activityCallback;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
@@ -173,23 +172,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
|
||||
etDummy = (EditText) view.findViewById(R.id.etDummy);
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
|
||||
Notice.showAll(llNotice,".*_login");
|
||||
|
||||
etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress);
|
||||
nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line);
|
||||
@@ -237,9 +221,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
});
|
||||
|
||||
loadPrefs();
|
||||
if (!showXmrtoEnabled) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -326,6 +307,17 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
ivGunther.setImageDrawable(null);
|
||||
}
|
||||
}
|
||||
|
||||
// remove information of non-existent wallet
|
||||
Set<String> removedWallets = getActivity()
|
||||
.getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.getAll().keySet();
|
||||
for (WalletManager.WalletInfo s : walletList) {
|
||||
removedWallets.remove(s.name);
|
||||
}
|
||||
for (String name : removedWallets) {
|
||||
KeyStoreHelper.removeWalletUserPass(getActivity(), name);
|
||||
}
|
||||
}
|
||||
|
||||
private void showInfo(@NonNull String name) {
|
||||
@@ -345,81 +337,65 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.list_menu, menu);
|
||||
menu.findItem(R.id.action_testnet).setChecked(testnetCheckMenu);
|
||||
menu.findItem(R.id.action_stagenet).setChecked(stagenetCheckMenu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
private boolean testnetCheckMenu = BuildConfig.DEBUG;
|
||||
private boolean stagenetCheckMenu = BuildConfig.DEBUG;
|
||||
|
||||
//boolean isTestnet() {
|
||||
// return testnet;
|
||||
//}
|
||||
|
||||
public boolean onTestnetMenuItem() {
|
||||
boolean lastState = testnetCheckMenu;
|
||||
public boolean onStagenetMenuItem() {
|
||||
boolean lastState = stagenetCheckMenu;
|
||||
setNet(!lastState, true); // set and save
|
||||
return !lastState;
|
||||
}
|
||||
|
||||
public void setNet(boolean testnetChecked, boolean save) {
|
||||
this.testnetCheckMenu = testnetChecked;
|
||||
NetworkType net = testnetChecked ? NetworkType.NetworkType_Testnet : NetworkType.NetworkType_Mainnet;
|
||||
public void setNet(boolean stagenetChecked, boolean save) {
|
||||
this.stagenetCheckMenu = stagenetChecked;
|
||||
NetworkType net = stagenetChecked ? NetworkType.NetworkType_Stagenet : NetworkType.NetworkType_Mainnet;
|
||||
activityCallback.setNetworkType(net);
|
||||
activityCallback.showNet();
|
||||
if (save) {
|
||||
savePrefs(true); // use previous state as we just clicked it
|
||||
}
|
||||
if (testnetChecked) {
|
||||
setDaemon(daemonTestNet);
|
||||
if (stagenetChecked) {
|
||||
setDaemon(daemonStageNet);
|
||||
} else {
|
||||
setDaemon(daemonMainNet);
|
||||
}
|
||||
loadList();
|
||||
}
|
||||
|
||||
private static final String PREF_DAEMON_TESTNET = "daemon_testnet";
|
||||
private static final String PREF_DAEMON_STAGENET = "daemon_stagenet";
|
||||
private static final String PREF_DAEMON_MAINNET = "daemon_mainnet";
|
||||
private static final String PREF_SHOW_XMRTO_ENABLED = "info_xmrto_enabled_login";
|
||||
|
||||
private static final String PREF_DAEMONLIST_MAINNET =
|
||||
"node.moneroworld.com:18089;node.xmrbackb.one;node.xmr.be";
|
||||
|
||||
private static final String PREF_DAEMONLIST_TESTNET =
|
||||
"testnet.xmrchain.net";
|
||||
private static final String PREF_DAEMONLIST_STAGENET =
|
||||
"stagenet.monerujo.io;stagenet.xmr-tw.org";
|
||||
|
||||
private NodeList daemonTestNet;
|
||||
private NodeList daemonStageNet;
|
||||
private NodeList daemonMainNet;
|
||||
|
||||
boolean showXmrtoEnabled = true;
|
||||
|
||||
void loadPrefs() {
|
||||
SharedPreferences sharedPref = activityCallback.getPrefs();
|
||||
|
||||
daemonMainNet = new NodeList(sharedPref.getString(PREF_DAEMON_MAINNET, PREF_DAEMONLIST_MAINNET));
|
||||
daemonTestNet = new NodeList(sharedPref.getString(PREF_DAEMON_TESTNET, PREF_DAEMONLIST_TESTNET));
|
||||
setNet(testnetCheckMenu, false);
|
||||
|
||||
showXmrtoEnabled = sharedPref.getBoolean(PREF_SHOW_XMRTO_ENABLED, true);
|
||||
}
|
||||
|
||||
void saveXmrToPrefs() {
|
||||
SharedPreferences sharedPref = activityCallback.getPrefs();
|
||||
SharedPreferences.Editor editor = sharedPref.edit();
|
||||
editor.putBoolean(PREF_SHOW_XMRTO_ENABLED, showXmrtoEnabled);
|
||||
editor.apply();
|
||||
daemonStageNet = new NodeList(sharedPref.getString(PREF_DAEMON_STAGENET, PREF_DAEMONLIST_STAGENET));
|
||||
setNet(stagenetCheckMenu, false);
|
||||
}
|
||||
|
||||
void savePrefs() {
|
||||
savePrefs(false);
|
||||
}
|
||||
|
||||
void savePrefs(boolean usePreviousTestnetState) {
|
||||
Timber.d("SAVE / %s", usePreviousTestnetState);
|
||||
void savePrefs(boolean usePreviousNetState) {
|
||||
Timber.d("SAVE / %s", usePreviousNetState);
|
||||
// save the daemon address for the net
|
||||
boolean testnet = testnetCheckMenu ^ usePreviousTestnetState;
|
||||
boolean stagenet = stagenetCheckMenu ^ usePreviousNetState;
|
||||
String daemon = getDaemon();
|
||||
if (testnet) {
|
||||
daemonTestNet.setRecent(daemon);
|
||||
if (stagenet) {
|
||||
daemonStageNet.setRecent(daemon);
|
||||
} else {
|
||||
daemonMainNet.setRecent(daemon);
|
||||
}
|
||||
@@ -427,8 +403,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
SharedPreferences sharedPref = activityCallback.getPrefs();
|
||||
SharedPreferences.Editor editor = sharedPref.edit();
|
||||
editor.putString(PREF_DAEMON_MAINNET, daemonMainNet.toString());
|
||||
editor.putString(PREF_DAEMON_TESTNET, daemonTestNet.toString());
|
||||
editor.putBoolean(PREF_SHOW_XMRTO_ENABLED, showXmrtoEnabled);
|
||||
editor.putString(PREF_DAEMON_STAGENET, daemonStageNet.toString());
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
|
@@ -62,6 +62,8 @@ import timber.log.Timber;
|
||||
public class ReceiveFragment extends Fragment {
|
||||
|
||||
private ProgressBar pbProgress;
|
||||
private View llAddress;
|
||||
private TextView tvAddressLabel;
|
||||
private TextView tvAddress;
|
||||
private TextInputLayout etPaymentId;
|
||||
private ExchangeView evAmount;
|
||||
@@ -71,6 +73,10 @@ public class ReceiveFragment extends Fragment {
|
||||
private ImageView qrCodeFull;
|
||||
private EditText etDummy;
|
||||
private ImageButton bCopyAddress;
|
||||
private Button bSubaddress;
|
||||
|
||||
private Wallet wallet = null;
|
||||
private boolean isMyWallet = false;
|
||||
|
||||
public interface Listener {
|
||||
void setToolbarButton(int type);
|
||||
@@ -87,6 +93,8 @@ public class ReceiveFragment extends Fragment {
|
||||
View view = inflater.inflate(R.layout.fragment_receive, container, false);
|
||||
|
||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
||||
llAddress = view.findViewById(R.id.llAddress);
|
||||
tvAddressLabel = (TextView) view.findViewById(R.id.tvAddressLabel);
|
||||
tvAddress = (TextView) view.findViewById(R.id.tvAddress);
|
||||
etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId);
|
||||
evAmount = (ExchangeView) view.findViewById(R.id.evAmount);
|
||||
@@ -96,6 +104,7 @@ public class ReceiveFragment extends Fragment {
|
||||
qrCodeFull = (ImageView) view.findViewById(R.id.qrCodeFull);
|
||||
etDummy = (EditText) view.findViewById(R.id.etDummy);
|
||||
bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
|
||||
bSubaddress = (Button) view.findViewById(R.id.bSubaddress);
|
||||
|
||||
etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
@@ -106,7 +115,7 @@ public class ReceiveFragment extends Fragment {
|
||||
copyAddress();
|
||||
}
|
||||
});
|
||||
bCopyAddress.setClickable(false);
|
||||
enableCopyAddress(false);
|
||||
|
||||
evAmount.setOnNewAmountListener(new ExchangeView.OnNewAmountListener() {
|
||||
@Override
|
||||
@@ -162,6 +171,37 @@ public class ReceiveFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
bSubaddress.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
enableSubaddressButton(false);
|
||||
enableCopyAddress(false);
|
||||
|
||||
final Runnable resetSize = new Runnable() {
|
||||
public void run() {
|
||||
tvAddress.animate().setDuration(125).scaleX(1).scaleY(1).start();
|
||||
}
|
||||
};
|
||||
|
||||
final Runnable newAddress = new Runnable() {
|
||||
public void run() {
|
||||
tvAddress.setText(wallet.getNewSubaddress());
|
||||
tvAddressLabel.setText(getString(R.string.generate_address_label_sub,
|
||||
wallet.getNumSubaddresses() - 1));
|
||||
storeWallet();
|
||||
generateQr();
|
||||
enableCopyAddress(true);
|
||||
tvAddress.animate().alpha(1).setDuration(125)
|
||||
.scaleX(1.2f).scaleY(1.2f)
|
||||
.withEndAction(resetSize).start();
|
||||
}
|
||||
};
|
||||
|
||||
tvAddress.animate().alpha(0).setDuration(250)
|
||||
.withEndAction(newAddress).start();
|
||||
}
|
||||
});
|
||||
|
||||
qrCode.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -194,11 +234,25 @@ public class ReceiveFragment extends Fragment {
|
||||
String password = b.getString("password");
|
||||
loadAndShow(path, password);
|
||||
} else {
|
||||
show(walletName, address);
|
||||
if (getActivity() instanceof GenerateReviewFragment.ListenerWithWallet) {
|
||||
wallet = ((GenerateReviewFragment.ListenerWithWallet) getActivity()).getWallet();
|
||||
show();
|
||||
} else {
|
||||
throw new IllegalStateException("no wallet info");
|
||||
}
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
void enableSubaddressButton(boolean enable) {
|
||||
bSubaddress.setEnabled(enable);
|
||||
if (enable) {
|
||||
bSubaddress.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.ic_settings_orange_24dp, 0, 0);
|
||||
} else {
|
||||
bSubaddress.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.ic_settings_gray_24dp, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void copyAddress() {
|
||||
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_address), tvAddress.getText().toString());
|
||||
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
|
||||
@@ -228,25 +282,38 @@ public class ReceiveFragment extends Fragment {
|
||||
super.onResume();
|
||||
Timber.d("onResume()");
|
||||
listenerCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||
listenerCallback.setSubtitle(getString(R.string.receive_title));
|
||||
generateQr();
|
||||
if (wallet != null) {
|
||||
listenerCallback.setSubtitle(wallet.getAccountLabel());
|
||||
generateQr();
|
||||
} else {
|
||||
listenerCallback.setSubtitle(getString(R.string.status_wallet_loading));
|
||||
clearQR();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLoaded = false;
|
||||
|
||||
private void show(String name, String address) {
|
||||
Timber.d("name=%s", name);
|
||||
private void show() {
|
||||
Timber.d("name=%s", wallet.getName());
|
||||
isLoaded = true;
|
||||
listenerCallback.setTitle(name);
|
||||
tvAddress.setText(address);
|
||||
listenerCallback.setTitle(wallet.getName());
|
||||
listenerCallback.setSubtitle(wallet.getAccountLabel());
|
||||
tvAddress.setText(wallet.getAddress());
|
||||
etPaymentId.setEnabled(true);
|
||||
bPaymentId.setEnabled(true);
|
||||
bCopyAddress.setClickable(true);
|
||||
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
|
||||
enableCopyAddress(true);
|
||||
hideProgress();
|
||||
generateQr();
|
||||
}
|
||||
|
||||
private void enableCopyAddress(boolean enable) {
|
||||
bCopyAddress.setClickable(enable);
|
||||
if (enable)
|
||||
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
|
||||
else
|
||||
bCopyAddress.setImageResource(R.drawable.ic_content_nocopy_black_24dp);
|
||||
}
|
||||
|
||||
private void loadAndShow(String walletPath, String password) {
|
||||
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR,
|
||||
walletPath, password);
|
||||
@@ -254,18 +321,14 @@ public class ReceiveFragment extends Fragment {
|
||||
|
||||
private class AsyncShow extends AsyncTask<String, Void, Boolean> {
|
||||
String password;
|
||||
String address;
|
||||
String name;
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(String... params) {
|
||||
if (params.length != 2) return false;
|
||||
String walletPath = params[0];
|
||||
password = params[1];
|
||||
Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password);
|
||||
address = wallet.getAddress();
|
||||
name = wallet.getName();
|
||||
wallet.close();
|
||||
wallet = WalletManager.getInstance().openWallet(walletPath, password);
|
||||
isMyWallet = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -274,7 +337,7 @@ public class ReceiveFragment extends Fragment {
|
||||
super.onPostExecute(result);
|
||||
if (!isAdded()) return; // never mind
|
||||
if (result) {
|
||||
show(name, address);
|
||||
show();
|
||||
} else {
|
||||
Toast.makeText(getActivity(), getString(R.string.receive_cannot_open), Toast.LENGTH_LONG).show();
|
||||
hideProgress();
|
||||
@@ -282,6 +345,27 @@ public class ReceiveFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void storeWallet() {
|
||||
new AsyncStore().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private class AsyncStore extends AsyncTask<String, Void, Boolean> {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(String... params) {
|
||||
if (params.length != 0) return false;
|
||||
if (wallet != null) wallet.store();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
enableSubaddressButton(true);
|
||||
super.onPostExecute(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean checkPaymentId() {
|
||||
String paymentId = etPaymentId.getEditText().getText().toString();
|
||||
boolean ok = paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId);
|
||||
@@ -335,6 +419,7 @@ public class ReceiveFragment extends Fragment {
|
||||
}
|
||||
|
||||
public Bitmap generate(String text, int width, int height) {
|
||||
if ((width <= 0) || (height <= 0)) return null;
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
|
||||
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
|
||||
@@ -417,4 +502,15 @@ public class ReceiveFragment extends Fragment {
|
||||
Timber.d("onPause()");
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
Timber.d("onDetach()");
|
||||
if ((wallet != null) && (isMyWallet)) {
|
||||
wallet.close();
|
||||
wallet = null;
|
||||
isMyWallet = false;
|
||||
}
|
||||
super.onDetach();
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
enableAccountsList(true);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -86,6 +86,10 @@ public class WalletNode {
|
||||
return name;
|
||||
}
|
||||
|
||||
public NetworkType getNetworkType() {
|
||||
return networkType;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return host + ":" + port;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -17,8 +17,11 @@
|
||||
package com.m2049r.xmrwallet.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class TransactionHistory {
|
||||
static {
|
||||
System.loadLibrary("monerujo");
|
||||
@@ -26,8 +29,18 @@ public class TransactionHistory {
|
||||
|
||||
private long handle;
|
||||
|
||||
public TransactionHistory(long handle) {
|
||||
int accountIndex;
|
||||
|
||||
public void setAccountFor(Wallet wallet) {
|
||||
if (accountIndex != wallet.getAccountIndex()) {
|
||||
this.accountIndex = wallet.getAccountIndex();
|
||||
refreshWithNotes(wallet);
|
||||
}
|
||||
}
|
||||
|
||||
public TransactionHistory(long handle, int accountIndex) {
|
||||
this.handle = handle;
|
||||
this.accountIndex = accountIndex;
|
||||
}
|
||||
|
||||
public void loadNotes(Wallet wallet) {
|
||||
@@ -36,7 +49,7 @@ public class TransactionHistory {
|
||||
}
|
||||
}
|
||||
|
||||
public native int getCount();
|
||||
public native int getCount(); // over all accounts/subaddresses
|
||||
|
||||
//private native long getTransactionByIndexJ(int i);
|
||||
|
||||
@@ -53,8 +66,23 @@ public class TransactionHistory {
|
||||
loadNotes(wallet);
|
||||
}
|
||||
|
||||
// public void refresh() {
|
||||
// transactions = refreshJ();
|
||||
// }
|
||||
|
||||
public void refresh() {
|
||||
transactions = refreshJ();
|
||||
List<TransactionInfo> t = refreshJ();
|
||||
Timber.d("refreshed %d", t.size());
|
||||
for (Iterator<TransactionInfo> iterator = t.iterator(); iterator.hasNext(); ) {
|
||||
TransactionInfo info = iterator.next();
|
||||
if (info.account != accountIndex) {
|
||||
iterator.remove();
|
||||
Timber.d("removed %s", info.hash);
|
||||
} else {
|
||||
Timber.d("kept %s", info.hash);
|
||||
}
|
||||
}
|
||||
transactions = t;
|
||||
}
|
||||
|
||||
private native List<TransactionInfo> refreshJ();
|
||||
|
@@ -53,6 +53,11 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
||||
}
|
||||
}
|
||||
|
||||
// virtual std::set<uint32_t> subaddrIndex() const = 0;
|
||||
// virtual uint32_t subaddrAccount() const = 0;
|
||||
// virtual std::string label() const = 0;
|
||||
// virtual uint64_t confirmations() const = 0;
|
||||
|
||||
public Direction direction;
|
||||
public boolean isPending;
|
||||
public boolean isFailed;
|
||||
@@ -62,6 +67,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
||||
public String hash;
|
||||
public long timestamp;
|
||||
public String paymentId;
|
||||
public int account;
|
||||
public int subaddress;
|
||||
public long confirmations;
|
||||
public List<Transfer> transfers;
|
||||
|
||||
@@ -78,6 +85,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
||||
String hash,
|
||||
long timestamp,
|
||||
String paymentId,
|
||||
int account,
|
||||
int subaddress,
|
||||
long confirmations,
|
||||
List<Transfer> transfers) {
|
||||
this.direction = Direction.values()[direction];
|
||||
@@ -89,6 +98,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
||||
this.hash = hash;
|
||||
this.timestamp = timestamp;
|
||||
this.paymentId = paymentId;
|
||||
this.account = account;
|
||||
this.subaddress = subaddress;
|
||||
this.confirmations = confirmations;
|
||||
this.transfers = transfers;
|
||||
}
|
||||
@@ -108,6 +119,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
||||
out.writeString(hash);
|
||||
out.writeLong(timestamp);
|
||||
out.writeString(paymentId);
|
||||
out.writeInt(account);
|
||||
out.writeInt(subaddress);
|
||||
out.writeLong(confirmations);
|
||||
out.writeList(transfers);
|
||||
out.writeString(txKey);
|
||||
@@ -134,6 +147,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
||||
hash = in.readString();
|
||||
timestamp = in.readLong();
|
||||
paymentId = in.readString();
|
||||
account = in.readInt();
|
||||
subaddress = in.readInt();
|
||||
confirmations = in.readLong();
|
||||
transfers = in.readArrayList(Transfer.class.getClassLoader());
|
||||
txKey = in.readString();
|
||||
|
@@ -19,13 +19,30 @@ package com.m2049r.xmrwallet.model;
|
||||
import com.m2049r.xmrwallet.data.TxData;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Wallet {
|
||||
final static public long SWEEP_ALL = Long.MAX_VALUE;
|
||||
|
||||
static {
|
||||
System.loadLibrary("monerujo");
|
||||
}
|
||||
|
||||
static final String TAG = "Wallet";
|
||||
private int accountIndex = 0;
|
||||
|
||||
public int getAccountIndex() {
|
||||
return accountIndex;
|
||||
}
|
||||
|
||||
public void setAccountIndex(int accountIndex) {
|
||||
Timber.d("setAccountIndex(%d)", accountIndex);
|
||||
this.accountIndex = accountIndex;
|
||||
getHistory().setAccountFor(this);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return new File(getPath()).getName();
|
||||
@@ -38,6 +55,11 @@ public class Wallet {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
Wallet(long handle, int accountIndex) {
|
||||
this.handle = handle;
|
||||
this.accountIndex = accountIndex;
|
||||
}
|
||||
|
||||
public enum Status {
|
||||
Status_Ok,
|
||||
Status_Error,
|
||||
@@ -66,16 +88,23 @@ public class Wallet {
|
||||
|
||||
public native boolean setPassword(String password);
|
||||
|
||||
private String address = null;
|
||||
|
||||
public String getAddress() {
|
||||
if (address == null) {
|
||||
address = getAddressJ();
|
||||
}
|
||||
return address;
|
||||
return getAddress(accountIndex);
|
||||
}
|
||||
|
||||
private native String getAddressJ();
|
||||
public String getAddress(int accountIndex) {
|
||||
return getAddressJ(accountIndex, 0);
|
||||
}
|
||||
|
||||
public String getSubaddress(int addressIndex) {
|
||||
return getAddressJ(accountIndex, addressIndex);
|
||||
}
|
||||
|
||||
public String getSubaddress(int accountIndex, int addressIndex) {
|
||||
return getAddressJ(accountIndex, addressIndex);
|
||||
}
|
||||
|
||||
private native String getAddressJ(int accountIndex, int addressIndex);
|
||||
|
||||
public native String getPath();
|
||||
|
||||
@@ -95,7 +124,9 @@ public class Wallet {
|
||||
public native String getSecretSpendKey();
|
||||
|
||||
public boolean store() {
|
||||
return store("");
|
||||
final boolean ok = store("");
|
||||
Timber.d("stored");
|
||||
return ok;
|
||||
}
|
||||
|
||||
public native boolean store(String path);
|
||||
@@ -132,9 +163,21 @@ public class Wallet {
|
||||
//TODO virtual void setTrustedDaemon(bool arg) = 0;
|
||||
//TODO virtual bool trustedDaemon() const = 0;
|
||||
|
||||
public native long getBalance();
|
||||
public long getBalance() {
|
||||
return getBalance(accountIndex);
|
||||
}
|
||||
|
||||
public native long getUnlockedBalance();
|
||||
public native long getBalance(int accountIndex);
|
||||
|
||||
public native long getBalanceAll();
|
||||
|
||||
public long getUnlockedBalance() {
|
||||
return getUnlockedBalance(accountIndex);
|
||||
}
|
||||
|
||||
public native long getUnlockedBalanceAll();
|
||||
|
||||
public native long getUnlockedBalance(int accountIndex);
|
||||
|
||||
public native boolean isWatchOnly();
|
||||
|
||||
@@ -164,9 +207,7 @@ public class Wallet {
|
||||
|
||||
public static native boolean isAddressValid(String address, int networkType);
|
||||
|
||||
//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);
|
||||
|
||||
public static native String getPaymentIdFromAddress(String address, boolean isTestNet);
|
||||
public static native String getPaymentIdFromAddress(String address, int networkType);
|
||||
|
||||
public static native long getMaximumAllowedAmount();
|
||||
|
||||
@@ -209,14 +250,23 @@ public class Wallet {
|
||||
PendingTransaction.Priority priority) {
|
||||
disposePendingTransaction();
|
||||
int _priority = priority.getValue();
|
||||
long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority);
|
||||
long txHandle =
|
||||
(amount == SWEEP_ALL ?
|
||||
createSweepTransaction(dst_addr, payment_id, mixin_count, _priority,
|
||||
accountIndex) :
|
||||
createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority,
|
||||
accountIndex));
|
||||
pendingTransaction = new PendingTransaction(txHandle);
|
||||
return pendingTransaction;
|
||||
}
|
||||
|
||||
private native long createTransactionJ(String dst_addr, String payment_id,
|
||||
long amount, int mixin_count,
|
||||
int priority);
|
||||
int priority, int accountIndex);
|
||||
|
||||
private native long createSweepTransaction(String dst_addr, String payment_id,
|
||||
int mixin_count,
|
||||
int priority, int accountIndex);
|
||||
|
||||
|
||||
public PendingTransaction createSweepUnmixableTransaction() {
|
||||
@@ -243,7 +293,7 @@ public class Wallet {
|
||||
|
||||
public TransactionHistory getHistory() {
|
||||
if (history == null) {
|
||||
history = new TransactionHistory(getHistoryJ());
|
||||
history = new TransactionHistory(getHistoryJ(), accountIndex);
|
||||
}
|
||||
return history;
|
||||
}
|
||||
@@ -275,5 +325,68 @@ public class Wallet {
|
||||
//virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0;
|
||||
//virtual bool rescanSpent() = 0;
|
||||
|
||||
private static final String NEW_ACCOUNT_NAME = "Untitled account"; // src/wallet/wallet2.cpp:941
|
||||
|
||||
public void addAccount() {
|
||||
addAccount(NEW_ACCOUNT_NAME);
|
||||
}
|
||||
|
||||
public native void addAccount(String label);
|
||||
|
||||
public String getAccountLabel() {
|
||||
return getAccountLabel(accountIndex);
|
||||
}
|
||||
|
||||
public String getAccountLabel(int accountIndex) {
|
||||
String label = getSubaddressLabel(accountIndex, 0);
|
||||
if (label.equals(NEW_ACCOUNT_NAME)) {
|
||||
String address = getAddress(accountIndex);
|
||||
int len = address.length();
|
||||
return address.substring(0, 6) +
|
||||
"\u2026" + address.substring(len - 6, len);
|
||||
} else return label;
|
||||
}
|
||||
|
||||
public String getSubaddressLabel(int addressIndex) {
|
||||
return getSubaddressLabel(accountIndex, addressIndex);
|
||||
}
|
||||
|
||||
public native String getSubaddressLabel(int accountIndex, int addressIndex);
|
||||
|
||||
public void setAccountLabel(String label) {
|
||||
setAccountLabel(accountIndex, label);
|
||||
}
|
||||
|
||||
public void setAccountLabel(int accountIndex, String label) {
|
||||
setSubaddressLabel(accountIndex, 0, label);
|
||||
}
|
||||
|
||||
public native void setSubaddressLabel(int accountIndex, int addressIndex, String label);
|
||||
|
||||
public native int getNumAccounts();
|
||||
|
||||
public int getNumSubaddresses() {
|
||||
return getNumSubaddresses(accountIndex);
|
||||
}
|
||||
|
||||
public native int getNumSubaddresses(int accountIndex);
|
||||
|
||||
public String getNewSubaddress() {
|
||||
return getNewSubaddress(accountIndex);
|
||||
}
|
||||
|
||||
public String getNewSubaddress(int accountIndex) {
|
||||
String timeStamp = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).format(new Date());
|
||||
addSubaddress(accountIndex, timeStamp);
|
||||
String subaddress = getLastSubaddress(accountIndex);
|
||||
Timber.d("%d: %s", getNumSubaddresses(accountIndex) - 1, subaddress);
|
||||
return subaddress;
|
||||
}
|
||||
|
||||
public native void addSubaddress(int accountIndex, String label);
|
||||
|
||||
public String getLastSubaddress(int accountIndex) {
|
||||
return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -41,6 +41,7 @@ public class WalletManager {
|
||||
if (WalletManager.Instance == null) {
|
||||
WalletManager.Instance = new WalletManager();
|
||||
}
|
||||
|
||||
return WalletManager.Instance;
|
||||
}
|
||||
|
||||
@@ -79,6 +80,13 @@ public class WalletManager {
|
||||
|
||||
private native long createWalletJ(String path, String password, String language, int networkType);
|
||||
|
||||
public Wallet openAccount(String path, int accountIndex, String password) {
|
||||
long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
|
||||
Wallet wallet = new Wallet(walletHandle, accountIndex);
|
||||
manageWallet(wallet);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
public Wallet openWallet(String path, String password) {
|
||||
long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
@@ -215,7 +223,7 @@ public class WalletManager {
|
||||
//public void setDaemon(String address, NetworkType networkType, String username, String password) {
|
||||
public void setDaemon(WalletNode walletNode) {
|
||||
this.daemonAddress = walletNode.getAddress();
|
||||
this.networkType = networkType;
|
||||
this.networkType = walletNode.getNetworkType();
|
||||
this.daemonUsername = walletNode.getUsername();
|
||||
this.daemonPassword = walletNode.getPassword();
|
||||
setDaemonAddressJ(daemonAddress);
|
||||
@@ -227,7 +235,6 @@ public class WalletManager {
|
||||
|
||||
public String getDaemonAddress() {
|
||||
if (daemonAddress == null) {
|
||||
// assume testnet not explicitly initialised
|
||||
throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
|
||||
}
|
||||
return this.daemonAddress;
|
||||
@@ -235,13 +242,13 @@ public class WalletManager {
|
||||
|
||||
private native void setDaemonAddressJ(String address);
|
||||
|
||||
String daemonUsername = "";
|
||||
private String daemonUsername = "";
|
||||
|
||||
public String getDaemonUsername() {
|
||||
return daemonUsername;
|
||||
}
|
||||
|
||||
String daemonPassword = "";
|
||||
private String daemonPassword = "";
|
||||
|
||||
public String getDaemonPassword() {
|
||||
return daemonPassword;
|
||||
@@ -269,5 +276,23 @@ public class WalletManager {
|
||||
|
||||
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
|
||||
|
||||
static public native void initLogger(String argv0, String defaultLogBaseName);
|
||||
|
||||
//TODO: maybe put these in an enum like in monero core - but why?
|
||||
static public int LOGLEVEL_SILENT = -1;
|
||||
static public int LOGLEVEL_WARN = 0;
|
||||
static public int LOGLEVEL_INFO = 1;
|
||||
static public int LOGLEVEL_DEBUG = 2;
|
||||
static public int LOGLEVEL_TRACE = 3;
|
||||
static public int LOGLEVEL_MAX = 4;
|
||||
|
||||
static public native void setLogLevel(int level);
|
||||
|
||||
static public native void logDebug(String category, String message);
|
||||
|
||||
static public native void logInfo(String category, String message);
|
||||
|
||||
static public native void logWarning(String category, String message);
|
||||
|
||||
static public native void logError(String category, String message);
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017 m2049r et al.
|
||||
* Copyright (c) 2017-2018 m2049r et al.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.service.exchange.kraken;
|
||||
package com.m2049r.xmrwallet.service.exchange.coinmarketcap;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
@@ -23,6 +23,7 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeException;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@@ -37,6 +38,7 @@ import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class ExchangeApiImpl implements ExchangeApi {
|
||||
static final String CRYPTO_ID = "328";
|
||||
|
||||
@NonNull
|
||||
private final OkHttpClient okHttpClient;
|
||||
@@ -52,7 +54,7 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
}
|
||||
|
||||
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
|
||||
this(okHttpClient, HttpUrl.parse("https://api.kraken.com/0/public/Ticker"));
|
||||
this(okHttpClient, HttpUrl.parse("https://api.coinmarketcap.com/v2/ticker/"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,12 +69,12 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
boolean inverse = false;
|
||||
String fiat = null;
|
||||
|
||||
if (baseCurrency.equals("XMR")) {
|
||||
if (baseCurrency.equals(Helper.CRYPTO)) {
|
||||
fiat = quoteCurrency;
|
||||
inverse = false;
|
||||
}
|
||||
|
||||
if (quoteCurrency.equals("XMR")) {
|
||||
if (quoteCurrency.equals(Helper.CRYPTO)) {
|
||||
fiat = baseCurrency;
|
||||
inverse = true;
|
||||
}
|
||||
@@ -85,7 +87,8 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
final boolean swapAssets = inverse;
|
||||
|
||||
final HttpUrl url = baseUrl.newBuilder()
|
||||
.addQueryParameter("pair", "XMR" + fiat)
|
||||
.addEncodedPathSegments(CRYPTO_ID + "/")
|
||||
.addQueryParameter("convert", fiat)
|
||||
.build();
|
||||
|
||||
final Request httpRequest = createHttpRequest(url);
|
||||
@@ -101,12 +104,12 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
if (response.isSuccessful()) {
|
||||
try {
|
||||
final JSONObject json = new JSONObject(response.body().string());
|
||||
final JSONArray jsonError = json.getJSONArray("error");
|
||||
if (jsonError.length() > 0) {
|
||||
final String errorMsg = jsonError.getString(0);
|
||||
callback.onError(new ExchangeException(response.code(), errorMsg));
|
||||
final JSONObject metadata = json.getJSONObject("metadata");
|
||||
if (!metadata.isNull("error")) {
|
||||
final String errorMsg = metadata.getString("error");
|
||||
callback.onError(new ExchangeException(response.code(), (String) errorMsg));
|
||||
} else {
|
||||
final JSONObject jsonResult = json.getJSONObject("result");
|
||||
final JSONObject jsonResult = json.getJSONObject("data");
|
||||
reportSuccess(jsonResult, swapAssets, callback);
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
@@ -130,7 +133,6 @@ public class ExchangeApiImpl implements ExchangeApi {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Request createHttpRequest(final HttpUrl url) {
|
||||
return new Request.Builder()
|
||||
.url(url)
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017 m2049r et al.
|
||||
* Copyright (c) 2017-2018 m2049r et al.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.service.exchange.kraken;
|
||||
package com.m2049r.xmrwallet.service.exchange.coinmarketcap;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -37,7 +38,7 @@ class ExchangeRateImpl implements ExchangeRate {
|
||||
|
||||
@Override
|
||||
public String getServiceName() {
|
||||
return "kraken.com";
|
||||
return "coinmarketcap.com";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,29 +65,21 @@ class ExchangeRateImpl implements ExchangeRate {
|
||||
|
||||
ExchangeRateImpl(final JSONObject jsonObject, final boolean swapAssets) throws JSONException, ExchangeException {
|
||||
try {
|
||||
final String key = jsonObject.keys().next(); // we expect only one
|
||||
Pattern pattern = Pattern.compile("^X(.*?)Z(.*?)$");
|
||||
Matcher matcher = pattern.matcher(key);
|
||||
if (matcher.find()) {
|
||||
this.baseCurrency = swapAssets ? matcher.group(2) : matcher.group(1);
|
||||
this.quoteCurrency = swapAssets ? matcher.group(1) : matcher.group(2);
|
||||
} else {
|
||||
throw new ExchangeException("no pair returned!");
|
||||
}
|
||||
|
||||
JSONObject pair = jsonObject.getJSONObject(key);
|
||||
JSONArray close = pair.getJSONArray("c");
|
||||
String closePrice = close.getString(0);
|
||||
if (closePrice != null) {
|
||||
try {
|
||||
double rate = Double.parseDouble(closePrice);
|
||||
this.rate = swapAssets ? (1 / rate) : rate;
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new ExchangeException(ex.getLocalizedMessage());
|
||||
}
|
||||
} else {
|
||||
throw new ExchangeException("no close price returned!");
|
||||
final String baseC = jsonObject.getString("symbol");
|
||||
final JSONObject quotes = jsonObject.getJSONObject("quotes");
|
||||
final Iterator<String> keys = quotes.keys();
|
||||
String key = null;
|
||||
// get key which is not USD unless it is the only one
|
||||
while (keys.hasNext()) {
|
||||
key = keys.next();
|
||||
if (!key.equals("USD")) break;
|
||||
}
|
||||
final String quoteC = key;
|
||||
baseCurrency = swapAssets ? quoteC : baseC;
|
||||
quoteCurrency = swapAssets ? baseC : quoteC;
|
||||
JSONObject quote = quotes.getJSONObject(key);
|
||||
double price = quote.getDouble("price");
|
||||
this.rate = swapAssets ? (1d / price) : price;
|
||||
} catch (NoSuchElementException ex) {
|
||||
throw new ExchangeException(ex.getLocalizedMessage());
|
||||
}
|
@@ -66,6 +66,8 @@ public class BitcoinAddressValidator {
|
||||
|
||||
byte[] result = new byte[25];
|
||||
byte[] numBytes = num.toByteArray();
|
||||
if (num.bitLength() > 200) return null;
|
||||
|
||||
if (num.bitLength() == 200) {
|
||||
System.arraycopy(numBytes, 1, result, 0, 25);
|
||||
} else {
|
||||
|
@@ -0,0 +1,54 @@
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class CrazyPassEncoder {
|
||||
static final String BASE = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
||||
static final int PW_CHARS = 52;
|
||||
|
||||
// this takes a 32 byte buffer and converts it to 52 alphnumeric characters
|
||||
// separated by blanks every 4 characters = 13 groups of 4
|
||||
// always (padding by Xs if need be
|
||||
static public String encode(byte[] data) {
|
||||
if (data.length != 32) throw new IllegalArgumentException("data[] is not 32 bytes long");
|
||||
BigInteger rest = new BigInteger(1, data);
|
||||
BigInteger remainder;
|
||||
final StringBuilder result = new StringBuilder();
|
||||
final BigInteger base = BigInteger.valueOf(BASE.length());
|
||||
int i = 0;
|
||||
do {
|
||||
if ((i > 0) && (i % 4 == 0)) result.append(' ');
|
||||
i++;
|
||||
remainder = rest.remainder(base);
|
||||
rest = rest.divide(base);
|
||||
result.append(BASE.charAt(remainder.intValue()));
|
||||
} while (!BigInteger.ZERO.equals(rest));
|
||||
// pad it
|
||||
while (i < PW_CHARS) {
|
||||
if ((i > 0) && (i % 4 == 0)) result.append(' ');
|
||||
result.append('2');
|
||||
i++;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
static public String reformat(String password) {
|
||||
// maybe this is a CrAzYpass without blanks? or lowercase letters
|
||||
String noBlanks = password.toUpperCase().replaceAll(" ", "");
|
||||
if (noBlanks.length() == PW_CHARS) { // looks like a CrAzYpass
|
||||
// insert blanks every 4 characters
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < PW_CHARS; i++) {
|
||||
if ((i > 0) && (i % 4 == 0)) sb.append(' ');
|
||||
char c = noBlanks.charAt(i);
|
||||
if (BASE.indexOf(c) < 0) return null; // invalid character found
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
} else {
|
||||
return null; // not a CrAzYpass
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
|
||||
public class FingerprintHelper {
|
||||
|
||||
public static boolean isDeviceSupported(Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FingerprintManager fingerprintManager = context.getSystemService(FingerprintManager.class);
|
||||
KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
|
||||
|
||||
return (keyguardManager != null) && (fingerprintManager != null) &&
|
||||
keyguardManager.isKeyguardSecure() &&
|
||||
fingerprintManager.isHardwareDetected() &&
|
||||
fingerprintManager.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
public static boolean isFingerPassValid(Context context, String wallet) {
|
||||
try {
|
||||
KeyStoreHelper.loadWalletUserPass(context, wallet);
|
||||
return true;
|
||||
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void authenticate(Context context, CancellationSignal cancelSignal,
|
||||
FingerprintManager.AuthenticationCallback callback) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return;
|
||||
}
|
||||
|
||||
FingerprintManager manager = context.getSystemService(FingerprintManager.class);
|
||||
if (manager != null) {
|
||||
manager.authenticate(null, cancelSignal, 0, callback, null);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
353
app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
Normal file
353
app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user