mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-08 04:10:49 +02:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c6d4de8599 | ||
![]() |
2ef7f8571c | ||
![]() |
d2612a26e5 | ||
![]() |
7e14572756 | ||
![]() |
6f840dcacf | ||
![]() |
36b389cd0f | ||
![]() |
ed2b95ea37 | ||
![]() |
8d41d1d03e | ||
![]() |
e2e9da3437 | ||
![]() |
7cd07d1988 | ||
![]() |
f5ae0525e3 | ||
![]() |
b32ed8caf2 | ||
![]() |
e0c2bfc4c4 | ||
![]() |
2986dfeaa7 | ||
![]() |
acb7398dca | ||
![]() |
c8322f5a83 | ||
![]() |
973472e0ef | ||
![]() |
2dad55e498 | ||
![]() |
8e82bd4cc8 | ||
![]() |
38a825d580 | ||
![]() |
be04185481 | ||
![]() |
5bfb920979 | ||
![]() |
29583fa40d | ||
![]() |
dc86f0469e | ||
![]() |
9e48f2bdcb | ||
![]() |
b71c260323 | ||
![]() |
46dbc0659b | ||
![]() |
85622b3958 | ||
![]() |
5a63481c09 | ||
![]() |
1cb558d78e | ||
![]() |
942c80e38b | ||
![]() |
24ec848f0f | ||
![]() |
0d1b1da5f3 | ||
![]() |
0840d2f350 | ||
![]() |
c7d933ea9d | ||
![]() |
c9b1800309 | ||
![]() |
e7b0b5999e | ||
![]() |
47e1871693 | ||
![]() |
900eab70c8 | ||
![]() |
b11357f379 | ||
![]() |
eba0156a6d | ||
![]() |
bf64d8bd89 | ||
![]() |
2d74281b31 | ||
![]() |
668cefb357 | ||
![]() |
1f5061df38 | ||
![]() |
51445f5941 | ||
![]() |
8c01ec36e8 | ||
![]() |
3cf84c599d |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -6,4 +6,11 @@
|
|||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/app/build
|
||||||
/app/release
|
/app/release
|
||||||
|
/app/alpha
|
||||||
|
/app/prod
|
||||||
|
/app/alphaMainnet
|
||||||
|
/app/prodMainnet
|
||||||
|
/app/alphaStagenet
|
||||||
|
/app/prodStagenet
|
||||||
|
3
app/.gitignore
vendored
3
app/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
.externalNativeBuild
|
|
||||||
build
|
|
||||||
app.iml
|
|
@@ -1,14 +1,15 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 27
|
compileSdkVersion 28
|
||||||
buildToolsVersion '28.0.2'
|
buildToolsVersion '28.0.3'
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.m2049r.xmrwallet"
|
applicationId "com.m2049r.xmrwallet"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 27
|
targetSdkVersion 28
|
||||||
versionCode 132
|
versionCode 159
|
||||||
versionName "1.8.2 'Bullets And Octane-Pirates'"
|
versionName "1.10.9 'Node-O-matiC'"
|
||||||
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
@@ -18,13 +19,23 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "version"
|
flavorDimensions 'type', 'net'
|
||||||
productFlavors {
|
productFlavors {
|
||||||
|
mainnet {
|
||||||
|
dimension 'net'
|
||||||
|
}
|
||||||
|
stagenet {
|
||||||
|
dimension 'net'
|
||||||
|
applicationIdSuffix '.stage'
|
||||||
|
versionNameSuffix ' (stage)'
|
||||||
|
}
|
||||||
alpha {
|
alpha {
|
||||||
applicationIdSuffix ".alpha"
|
dimension 'type'
|
||||||
versionNameSuffix " (alpha)"
|
applicationIdSuffix '.alpha'
|
||||||
|
versionNameSuffix ' (alpha)'
|
||||||
}
|
}
|
||||||
prod {
|
prod {
|
||||||
|
dimension 'type'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,9 +101,11 @@ dependencies {
|
|||||||
implementation "com.android.support:support-v4:$rootProject.ext.supportVersion"
|
implementation "com.android.support:support-v4:$rootProject.ext.supportVersion"
|
||||||
implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion"
|
implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion"
|
||||||
implementation "com.android.support:cardview-v7:$rootProject.ext.supportVersion"
|
implementation "com.android.support:cardview-v7:$rootProject.ext.supportVersion"
|
||||||
|
implementation "com.android.support:swiperefreshlayout:$rootProject.ext.supportVersion"
|
||||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||||
|
|
||||||
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
||||||
|
implementation "com.burgstaller:okhttp-digest:1.18"
|
||||||
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
|
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
|
||||||
|
|
||||||
implementation 'com.nulab-inc:zxcvbn:1.2.3'
|
implementation 'com.nulab-inc:zxcvbn:1.2.3'
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name" translatable="false">monerujo - Debug</string>
|
|
||||||
</resources>
|
|
@@ -7,8 +7,10 @@
|
|||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".XmrWalletApplication"
|
android:name=".XmrWalletApplication"
|
||||||
@@ -16,7 +18,8 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/MyMaterialTheme">
|
android:theme="@style/MyMaterialTheme"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".WalletActivity"
|
android:name=".WalletActivity"
|
||||||
@@ -43,7 +46,7 @@
|
|||||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||||
android:resource="@xml/usb_device_filter" />
|
android:resource="@xml/usb_device_filter" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.WalletService"
|
android:name=".service.WalletService"
|
||||||
android:description="@string/service_description"
|
android:description="@string/service_description"
|
||||||
|
@@ -417,8 +417,8 @@ Java_com_m2049r_xmrwallet_model_WalletManager_verifyWalletPassword(JNIEnv *env,
|
|||||||
//virtual int queryWalletHardware(const std::string &keys_file_name, const std::string &password) const = 0;
|
//virtual int queryWalletHardware(const std::string &keys_file_name, const std::string &password) const = 0;
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_WalletManager_queryWalletDeviceJ(JNIEnv *env, jobject instance,
|
Java_com_m2049r_xmrwallet_model_WalletManager_queryWalletDeviceJ(JNIEnv *env, jobject instance,
|
||||||
jstring keys_file_name,
|
jstring keys_file_name,
|
||||||
jstring password) {
|
jstring password) {
|
||||||
const char *_keys_file_name = env->GetStringUTFChars(keys_file_name, NULL);
|
const char *_keys_file_name = env->GetStringUTFChars(keys_file_name, NULL);
|
||||||
const char *_password = env->GetStringUTFChars(password, NULL);
|
const char *_password = env->GetStringUTFChars(password, NULL);
|
||||||
Bitmonero::Wallet::Device device_type;
|
Bitmonero::Wallet::Device device_type;
|
||||||
@@ -787,7 +787,7 @@ Java_com_m2049r_xmrwallet_model_Wallet_getDeviceTypeJ(JNIEnv *env, jobject insta
|
|||||||
|
|
||||||
//void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h
|
//void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h
|
||||||
JNIEXPORT jbyteArray JNICALL
|
JNIEXPORT jbyteArray JNICALL
|
||||||
Java_com_m2049r_xmrwallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jobject clazz,
|
Java_com_m2049r_xmrwallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jclass clazz,
|
||||||
jbyteArray data, jint brokenVariant) {
|
jbyteArray data, jint brokenVariant) {
|
||||||
char hash[HASH_SIZE];
|
char hash[HASH_SIZE];
|
||||||
jsize size = env->GetArrayLength(data);
|
jsize size = env->GetArrayLength(data);
|
||||||
@@ -813,13 +813,13 @@ Java_com_m2049r_xmrwallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jobject claz
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_Wallet_getDisplayAmount(JNIEnv *env, jobject clazz,
|
Java_com_m2049r_xmrwallet_model_Wallet_getDisplayAmount(JNIEnv *env, jclass clazz,
|
||||||
jlong amount) {
|
jlong amount) {
|
||||||
return env->NewStringUTF(Bitmonero::Wallet::displayAmount(amount).c_str());
|
return env->NewStringUTF(Bitmonero::Wallet::displayAmount(amount).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_Wallet_getAmountFromString(JNIEnv *env, jobject clazz,
|
Java_com_m2049r_xmrwallet_model_Wallet_getAmountFromString(JNIEnv *env, jclass clazz,
|
||||||
jstring amount) {
|
jstring amount) {
|
||||||
const char *_amount = env->GetStringUTFChars(amount, NULL);
|
const char *_amount = env->GetStringUTFChars(amount, NULL);
|
||||||
uint64_t x = Bitmonero::Wallet::amountFromString(_amount);
|
uint64_t x = Bitmonero::Wallet::amountFromString(_amount);
|
||||||
@@ -828,18 +828,18 @@ Java_com_m2049r_xmrwallet_model_Wallet_getAmountFromString(JNIEnv *env, jobject
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_Wallet_getAmountFromDouble(JNIEnv *env, jobject clazz,
|
Java_com_m2049r_xmrwallet_model_Wallet_getAmountFromDouble(JNIEnv *env, jclass clazz,
|
||||||
jdouble amount) {
|
jdouble amount) {
|
||||||
return Bitmonero::Wallet::amountFromDouble(amount);
|
return Bitmonero::Wallet::amountFromDouble(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_Wallet_generatePaymentId(JNIEnv *env, jobject clazz) {
|
Java_com_m2049r_xmrwallet_model_Wallet_generatePaymentId(JNIEnv *env, jclass clazz) {
|
||||||
return env->NewStringUTF(Bitmonero::Wallet::genPaymentId().c_str());
|
return env->NewStringUTF(Bitmonero::Wallet::genPaymentId().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_Wallet_isPaymentIdValid(JNIEnv *env, jobject clazz,
|
Java_com_m2049r_xmrwallet_model_Wallet_isPaymentIdValid(JNIEnv *env, jclass clazz,
|
||||||
jstring payment_id) {
|
jstring payment_id) {
|
||||||
const char *_payment_id = env->GetStringUTFChars(payment_id, NULL);
|
const char *_payment_id = env->GetStringUTFChars(payment_id, NULL);
|
||||||
bool isValid = Bitmonero::Wallet::paymentIdValid(_payment_id);
|
bool isValid = Bitmonero::Wallet::paymentIdValid(_payment_id);
|
||||||
@@ -848,7 +848,7 @@ Java_com_m2049r_xmrwallet_model_Wallet_isPaymentIdValid(JNIEnv *env, jobject cla
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jboolean JNICALL
|
JNIEXPORT jboolean JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_Wallet_isAddressValid(JNIEnv *env, jobject clazz,
|
Java_com_m2049r_xmrwallet_model_Wallet_isAddressValid(JNIEnv *env, jclass clazz,
|
||||||
jstring address, jint networkType) {
|
jstring address, jint networkType) {
|
||||||
const char *_address = env->GetStringUTFChars(address, NULL);
|
const char *_address = env->GetStringUTFChars(address, NULL);
|
||||||
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
|
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
|
||||||
@@ -858,7 +858,7 @@ Java_com_m2049r_xmrwallet_model_Wallet_isAddressValid(JNIEnv *env, jobject clazz
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_Wallet_getPaymentIdFromAddress(JNIEnv *env, jobject clazz,
|
Java_com_m2049r_xmrwallet_model_Wallet_getPaymentIdFromAddress(JNIEnv *env, jclass clazz,
|
||||||
jstring address,
|
jstring address,
|
||||||
jint networkType) {
|
jint networkType) {
|
||||||
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
|
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
|
||||||
@@ -869,7 +869,7 @@ Java_com_m2049r_xmrwallet_model_Wallet_getPaymentIdFromAddress(JNIEnv *env, jobj
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_Wallet_getMaximumAllowedAmount(JNIEnv *env, jobject clazz) {
|
Java_com_m2049r_xmrwallet_model_Wallet_getMaximumAllowedAmount(JNIEnv *env, jclass clazz) {
|
||||||
return Bitmonero::Wallet::maximumAllowedAmount();
|
return Bitmonero::Wallet::maximumAllowedAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1185,10 +1185,11 @@ jobject newTransferList(JNIEnv *env, Bitmonero::TransactionInfo *info) {
|
|||||||
|
|
||||||
jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) {
|
jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) {
|
||||||
jmethodID c = env->GetMethodID(class_TransactionInfo, "<init>",
|
jmethodID c = env->GetMethodID(class_TransactionInfo, "<init>",
|
||||||
"(IZZJJJLjava/lang/String;JLjava/lang/String;IIJLjava/util/List;)V");
|
"(IZZJJJLjava/lang/String;JLjava/lang/String;IIJLjava/lang/String;Ljava/util/List;)V");
|
||||||
jobject transfers = newTransferList(env, info);
|
jobject transfers = newTransferList(env, info);
|
||||||
jstring _hash = env->NewStringUTF(info->hash().c_str());
|
jstring _hash = env->NewStringUTF(info->hash().c_str());
|
||||||
jstring _paymentId = env->NewStringUTF(info->paymentId().c_str());
|
jstring _paymentId = env->NewStringUTF(info->paymentId().c_str());
|
||||||
|
jstring _label = env->NewStringUTF(info->label().c_str());
|
||||||
uint32_t subaddrIndex = 0;
|
uint32_t subaddrIndex = 0;
|
||||||
if (info->direction() == Bitmonero::TransactionInfo::Direction_In)
|
if (info->direction() == Bitmonero::TransactionInfo::Direction_In)
|
||||||
subaddrIndex = *(info->subaddrIndex().begin());
|
subaddrIndex = *(info->subaddrIndex().begin());
|
||||||
@@ -1205,6 +1206,7 @@ jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) {
|
|||||||
info->subaddrAccount(),
|
info->subaddrAccount(),
|
||||||
subaddrIndex,
|
subaddrIndex,
|
||||||
info->confirmations(),
|
info->confirmations(),
|
||||||
|
_label,
|
||||||
transfers);
|
transfers);
|
||||||
env->DeleteLocalRef(transfers);
|
env->DeleteLocalRef(transfers);
|
||||||
env->DeleteLocalRef(_hash);
|
env->DeleteLocalRef(_hash);
|
||||||
@@ -1311,7 +1313,7 @@ Java_com_m2049r_xmrwallet_model_PendingTransaction_getTxCount(JNIEnv *env, jobje
|
|||||||
//static void warning(const std::string &category, const std::string &str);
|
//static void warning(const std::string &category, const std::string &str);
|
||||||
//static void error(const std::string &category, const std::string &str);
|
//static void error(const std::string &category, const std::string &str);
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_WalletManager_initLogger(JNIEnv *env, jobject instance,
|
Java_com_m2049r_xmrwallet_model_WalletManager_initLogger(JNIEnv *env, jclass clazz,
|
||||||
jstring argv0,
|
jstring argv0,
|
||||||
jstring default_log_base_name) {
|
jstring default_log_base_name) {
|
||||||
|
|
||||||
@@ -1325,7 +1327,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_initLogger(JNIEnv *env, jobject in
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_WalletManager_logDebug(JNIEnv *env, jobject instance,
|
Java_com_m2049r_xmrwallet_model_WalletManager_logDebug(JNIEnv *env, jclass clazz,
|
||||||
jstring category, jstring message) {
|
jstring category, jstring message) {
|
||||||
|
|
||||||
const char *_category = env->GetStringUTFChars(category, NULL);
|
const char *_category = env->GetStringUTFChars(category, NULL);
|
||||||
@@ -1338,7 +1340,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_logDebug(JNIEnv *env, jobject inst
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_WalletManager_logInfo(JNIEnv *env, jobject instance,
|
Java_com_m2049r_xmrwallet_model_WalletManager_logInfo(JNIEnv *env, jclass clazz,
|
||||||
jstring category, jstring message) {
|
jstring category, jstring message) {
|
||||||
|
|
||||||
const char *_category = env->GetStringUTFChars(category, NULL);
|
const char *_category = env->GetStringUTFChars(category, NULL);
|
||||||
@@ -1351,7 +1353,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_logInfo(JNIEnv *env, jobject insta
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_WalletManager_logWarning(JNIEnv *env, jobject instance,
|
Java_com_m2049r_xmrwallet_model_WalletManager_logWarning(JNIEnv *env, jclass clazz,
|
||||||
jstring category, jstring message) {
|
jstring category, jstring message) {
|
||||||
|
|
||||||
const char *_category = env->GetStringUTFChars(category, NULL);
|
const char *_category = env->GetStringUTFChars(category, NULL);
|
||||||
@@ -1364,7 +1366,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_logWarning(JNIEnv *env, jobject in
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_WalletManager_logError(JNIEnv *env, jobject instance,
|
Java_com_m2049r_xmrwallet_model_WalletManager_logError(JNIEnv *env, jclass clazz,
|
||||||
jstring category, jstring message) {
|
jstring category, jstring message) {
|
||||||
|
|
||||||
const char *_category = env->GetStringUTFChars(category, NULL);
|
const char *_category = env->GetStringUTFChars(category, NULL);
|
||||||
@@ -1377,7 +1379,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_logError(JNIEnv *env, jobject inst
|
|||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jobject instance,
|
Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jclass clazz,
|
||||||
jint level) {
|
jint level) {
|
||||||
Bitmonero::WalletManagerFactory::setLogLevel(level);
|
Bitmonero::WalletManagerFactory::setLogLevel(level);
|
||||||
}
|
}
|
||||||
@@ -1397,7 +1399,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jobject i
|
|||||||
*
|
*
|
||||||
* @return length of received data in response or -1 if error
|
* @return length of received data in response or -1 if error
|
||||||
*/
|
*/
|
||||||
int LedgerExchange(
|
int LedgerExchange(
|
||||||
unsigned char *command,
|
unsigned char *command,
|
||||||
unsigned int cmd_len,
|
unsigned int cmd_len,
|
||||||
unsigned char *response,
|
unsigned char *response,
|
||||||
|
@@ -23,9 +23,9 @@ package com.btchip.comm;
|
|||||||
import com.btchip.BTChipException;
|
import com.btchip.BTChipException;
|
||||||
|
|
||||||
public interface BTChipTransport {
|
public interface BTChipTransport {
|
||||||
public byte[] exchange(byte[] command);
|
byte[] exchange(byte[] command);
|
||||||
|
|
||||||
public void close();
|
void close();
|
||||||
|
|
||||||
public void setDebug(boolean debugFlag);
|
void setDebug(boolean debugFlag);
|
||||||
}
|
}
|
||||||
|
145
app/src/main/java/com/m2049r/levin/data/Bucket.java
Normal file
145
app/src/main/java/com/m2049r/levin/data/Bucket.java
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* 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.levin.data;
|
||||||
|
|
||||||
|
import com.m2049r.levin.util.HexHelper;
|
||||||
|
import com.m2049r.levin.util.LevinReader;
|
||||||
|
|
||||||
|
import java.io.DataInput;
|
||||||
|
import java.io.DataOutput;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class Bucket {
|
||||||
|
|
||||||
|
// constants copied from monero p2p & epee
|
||||||
|
|
||||||
|
public final static int P2P_COMMANDS_POOL_BASE = 1000;
|
||||||
|
public final static int COMMAND_HANDSHAKE_ID = P2P_COMMANDS_POOL_BASE + 1;
|
||||||
|
public final static int COMMAND_TIMED_SYNC_ID = P2P_COMMANDS_POOL_BASE + 2;
|
||||||
|
public final static int COMMAND_PING_ID = P2P_COMMANDS_POOL_BASE + 3;
|
||||||
|
public final static int COMMAND_REQUEST_STAT_INFO_ID = P2P_COMMANDS_POOL_BASE + 4;
|
||||||
|
public final static int COMMAND_REQUEST_NETWORK_STATE_ID = P2P_COMMANDS_POOL_BASE + 5;
|
||||||
|
public final static int COMMAND_REQUEST_PEER_ID_ID = P2P_COMMANDS_POOL_BASE + 6;
|
||||||
|
public final static int COMMAND_REQUEST_SUPPORT_FLAGS_ID = P2P_COMMANDS_POOL_BASE + 7;
|
||||||
|
|
||||||
|
public final static long LEVIN_SIGNATURE = 0x0101010101012101L; // Bender's nightmare
|
||||||
|
|
||||||
|
public final static long LEVIN_DEFAULT_MAX_PACKET_SIZE = 100000000; // 100MB by default
|
||||||
|
|
||||||
|
public final static int LEVIN_PACKET_REQUEST = 0x00000001;
|
||||||
|
public final static int LEVIN_PACKET_RESPONSE = 0x00000002;
|
||||||
|
|
||||||
|
public final static int LEVIN_PROTOCOL_VER_0 = 0;
|
||||||
|
public final static int LEVIN_PROTOCOL_VER_1 = 1;
|
||||||
|
|
||||||
|
public final static int LEVIN_OK = 0;
|
||||||
|
public final static int LEVIN_ERROR_CONNECTION = -1;
|
||||||
|
public final static int LEVIN_ERROR_CONNECTION_NOT_FOUND = -2;
|
||||||
|
public final static int LEVIN_ERROR_CONNECTION_DESTROYED = -3;
|
||||||
|
public final static int LEVIN_ERROR_CONNECTION_TIMEDOUT = -4;
|
||||||
|
public final static int LEVIN_ERROR_CONNECTION_NO_DUPLEX_PROTOCOL = -5;
|
||||||
|
public final static int LEVIN_ERROR_CONNECTION_HANDLER_NOT_DEFINED = -6;
|
||||||
|
public final static int LEVIN_ERROR_FORMAT = -7;
|
||||||
|
|
||||||
|
public final static int P2P_SUPPORT_FLAG_FLUFFY_BLOCKS = 0x01;
|
||||||
|
public final static int P2P_SUPPORT_FLAGS = P2P_SUPPORT_FLAG_FLUFFY_BLOCKS;
|
||||||
|
|
||||||
|
final private long signature;
|
||||||
|
final private long cb;
|
||||||
|
final public boolean haveToReturnData;
|
||||||
|
final public int command;
|
||||||
|
final public int returnCode;
|
||||||
|
final private int flags;
|
||||||
|
final private int protcolVersion;
|
||||||
|
final byte[] payload;
|
||||||
|
|
||||||
|
final public Section payloadSection;
|
||||||
|
|
||||||
|
// create a request
|
||||||
|
public Bucket(int command, byte[] payload) throws IOException {
|
||||||
|
this.signature = LEVIN_SIGNATURE;
|
||||||
|
this.cb = payload.length;
|
||||||
|
this.haveToReturnData = true;
|
||||||
|
this.command = command;
|
||||||
|
this.returnCode = 0;
|
||||||
|
this.flags = LEVIN_PACKET_REQUEST;
|
||||||
|
this.protcolVersion = LEVIN_PROTOCOL_VER_1;
|
||||||
|
this.payload = payload;
|
||||||
|
payloadSection = LevinReader.readPayload(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a response
|
||||||
|
public Bucket(int command, byte[] payload, int rc) throws IOException {
|
||||||
|
this.signature = LEVIN_SIGNATURE;
|
||||||
|
this.cb = payload.length;
|
||||||
|
this.haveToReturnData = false;
|
||||||
|
this.command = command;
|
||||||
|
this.returnCode = rc;
|
||||||
|
this.flags = LEVIN_PACKET_RESPONSE;
|
||||||
|
this.protcolVersion = LEVIN_PROTOCOL_VER_1;
|
||||||
|
this.payload = payload;
|
||||||
|
payloadSection = LevinReader.readPayload(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bucket(DataInput in) throws IOException {
|
||||||
|
signature = in.readLong();
|
||||||
|
cb = in.readLong();
|
||||||
|
haveToReturnData = in.readBoolean();
|
||||||
|
command = in.readInt();
|
||||||
|
returnCode = in.readInt();
|
||||||
|
flags = in.readInt();
|
||||||
|
protcolVersion = in.readInt();
|
||||||
|
|
||||||
|
if (signature == Bucket.LEVIN_SIGNATURE) {
|
||||||
|
if (cb > Integer.MAX_VALUE)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
payload = new byte[(int) cb];
|
||||||
|
in.readFully(payload);
|
||||||
|
} else
|
||||||
|
throw new IllegalStateException();
|
||||||
|
payloadSection = LevinReader.readPayload(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Section getPayloadSection() {
|
||||||
|
return payloadSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(DataOutput out) throws IOException {
|
||||||
|
out.writeLong(signature);
|
||||||
|
out.writeLong(cb);
|
||||||
|
out.writeBoolean(haveToReturnData);
|
||||||
|
out.writeInt(command);
|
||||||
|
out.writeInt(returnCode);
|
||||||
|
out.writeInt(flags);
|
||||||
|
out.writeInt(protcolVersion);
|
||||||
|
out.write(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("sig: ").append(signature).append("\n");
|
||||||
|
sb.append("cb: ").append(cb).append("\n");
|
||||||
|
sb.append("call: ").append(haveToReturnData).append("\n");
|
||||||
|
sb.append("cmd: ").append(command).append("\n");
|
||||||
|
sb.append("rc: ").append(returnCode).append("\n");
|
||||||
|
sb.append("flags:").append(flags).append("\n");
|
||||||
|
sb.append("proto:").append(protcolVersion).append("\n");
|
||||||
|
sb.append(HexHelper.bytesToHex(payload)).append("\n");
|
||||||
|
sb.append(payloadSection.toString());
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
125
app/src/main/java/com/m2049r/levin/data/Section.java
Normal file
125
app/src/main/java/com/m2049r/levin/data/Section.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* 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.levin.data;
|
||||||
|
|
||||||
|
import com.m2049r.levin.util.HexHelper;
|
||||||
|
import com.m2049r.levin.util.LevinReader;
|
||||||
|
import com.m2049r.levin.util.LevinWriter;
|
||||||
|
import com.m2049r.levin.util.LittleEndianDataOutputStream;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutput;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class Section {
|
||||||
|
|
||||||
|
// constants copied from monero p2p & epee
|
||||||
|
|
||||||
|
static final public int PORTABLE_STORAGE_SIGNATUREA = 0x01011101;
|
||||||
|
static final public int PORTABLE_STORAGE_SIGNATUREB = 0x01020101;
|
||||||
|
|
||||||
|
static final public byte PORTABLE_STORAGE_FORMAT_VER = 1;
|
||||||
|
|
||||||
|
static final public byte PORTABLE_RAW_SIZE_MARK_MASK = 0x03;
|
||||||
|
static final public byte PORTABLE_RAW_SIZE_MARK_BYTE = 0;
|
||||||
|
static final public byte PORTABLE_RAW_SIZE_MARK_WORD = 1;
|
||||||
|
static final public byte PORTABLE_RAW_SIZE_MARK_DWORD = 2;
|
||||||
|
static final public byte PORTABLE_RAW_SIZE_MARK_INT64 = 3;
|
||||||
|
|
||||||
|
static final long MAX_STRING_LEN_POSSIBLE = 2000000000; // do not let string be so big
|
||||||
|
|
||||||
|
// data types
|
||||||
|
static final public byte SERIALIZE_TYPE_INT64 = 1;
|
||||||
|
static final public byte SERIALIZE_TYPE_INT32 = 2;
|
||||||
|
static final public byte SERIALIZE_TYPE_INT16 = 3;
|
||||||
|
static final public byte SERIALIZE_TYPE_INT8 = 4;
|
||||||
|
static final public byte SERIALIZE_TYPE_UINT64 = 5;
|
||||||
|
static final public byte SERIALIZE_TYPE_UINT32 = 6;
|
||||||
|
static final public byte SERIALIZE_TYPE_UINT16 = 7;
|
||||||
|
static final public byte SERIALIZE_TYPE_UINT8 = 8;
|
||||||
|
static final public byte SERIALIZE_TYPE_DUOBLE = 9;
|
||||||
|
static final public byte SERIALIZE_TYPE_STRING = 10;
|
||||||
|
static final public byte SERIALIZE_TYPE_BOOL = 11;
|
||||||
|
static final public byte SERIALIZE_TYPE_OBJECT = 12;
|
||||||
|
static final public byte SERIALIZE_TYPE_ARRAY = 13;
|
||||||
|
|
||||||
|
static final public byte SERIALIZE_FLAG_ARRAY = (byte) 0x80;
|
||||||
|
|
||||||
|
private final Map<String, Object> entries = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
public void add(String key, Object entry) {
|
||||||
|
entries.put(key, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return entries.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Map.Entry<String, Object>> entrySet() {
|
||||||
|
return entries.entrySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object get(String key) {
|
||||||
|
return entries.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("\n");
|
||||||
|
for (Map.Entry<String, Object> entry : entries.entrySet()) {
|
||||||
|
sb.append(entry.getKey()).append("=");
|
||||||
|
final Object value = entry.getValue();
|
||||||
|
if (value instanceof List) {
|
||||||
|
@SuppressWarnings("unchecked") final List<Object> list = (List<Object>) value;
|
||||||
|
for (Object listEntry : list) {
|
||||||
|
sb.append(listEntry.toString()).append("\n");
|
||||||
|
}
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
sb.append("(").append(value).append(")\n");
|
||||||
|
} else if (value instanceof byte[]) {
|
||||||
|
sb.append(HexHelper.bytesToHex((byte[]) value)).append("\n");
|
||||||
|
} else {
|
||||||
|
sb.append(value.toString()).append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Section fromByteArray(byte[] buffer) {
|
||||||
|
try {
|
||||||
|
return LevinReader.readPayload(buffer);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] asByteArray() {
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream bas = new ByteArrayOutputStream();
|
||||||
|
DataOutput out = new LittleEndianDataOutputStream(bas);
|
||||||
|
LevinWriter writer = new LevinWriter(out);
|
||||||
|
writer.writePayload(this);
|
||||||
|
return bas.toByteArray();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
197
app/src/main/java/com/m2049r/levin/scanner/Dispatcher.java
Normal file
197
app/src/main/java/com/m2049r/levin/scanner/Dispatcher.java
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* 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.levin.scanner;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.data.NodeInfo;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class Dispatcher implements PeerRetriever.OnGetPeers {
|
||||||
|
static final public int NUM_THREADS = 50;
|
||||||
|
static final public int MAX_PEERS = 1000;
|
||||||
|
static final public long MAX_TIME = 30000000000L; //30 seconds
|
||||||
|
|
||||||
|
private int peerCount = 0;
|
||||||
|
final private Set<NodeInfo> knownNodes = new HashSet<>(); // set of nodes to test
|
||||||
|
final private Set<NodeInfo> rpcNodes = new HashSet<>(); // set of RPC nodes we like
|
||||||
|
final private ExecutorService exeService = Executors.newFixedThreadPool(NUM_THREADS);
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
void onGet(NodeInfo nodeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Listener listener;
|
||||||
|
|
||||||
|
public Dispatcher(Listener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<NodeInfo> getRpcNodes() {
|
||||||
|
return rpcNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPeerCount() {
|
||||||
|
return peerCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getMorePeers() {
|
||||||
|
return peerCount < MAX_PEERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void awaitTermination(int nodesToFind) {
|
||||||
|
try {
|
||||||
|
final long t = System.nanoTime();
|
||||||
|
while (!jobs.isEmpty()) {
|
||||||
|
try {
|
||||||
|
Timber.d("Remaining jobs %d", jobs.size());
|
||||||
|
final PeerRetriever retrievedPeer = jobs.poll().get();
|
||||||
|
if (retrievedPeer.isGood() && getMorePeers())
|
||||||
|
retrievePeers(retrievedPeer);
|
||||||
|
final NodeInfo nodeInfo = retrievedPeer.getNodeInfo();
|
||||||
|
Timber.d("Retrieved %s", nodeInfo);
|
||||||
|
if ((nodeInfo.isValid() || nodeInfo.isFavourite())) {
|
||||||
|
nodeInfo.setName();
|
||||||
|
rpcNodes.add(nodeInfo);
|
||||||
|
Timber.d("RPC: %s", nodeInfo);
|
||||||
|
// the following is not totally correct but it works (otherwise we need to
|
||||||
|
// load much more before filtering - but we don't have time
|
||||||
|
if (listener != null) listener.onGet(nodeInfo);
|
||||||
|
if (rpcNodes.size() >= nodesToFind) {
|
||||||
|
Timber.d("are we done here?");
|
||||||
|
filterRpcNodes();
|
||||||
|
if (rpcNodes.size() >= nodesToFind) {
|
||||||
|
Timber.d("we're done here");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (System.nanoTime() - t > MAX_TIME) break; // watchdog
|
||||||
|
} catch (ExecutionException ex) {
|
||||||
|
Timber.d(ex); // tell us about it and continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Timber.d(ex);
|
||||||
|
} finally {
|
||||||
|
Timber.d("Shutting down!");
|
||||||
|
exeService.shutdownNow();
|
||||||
|
try {
|
||||||
|
exeService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Timber.d(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filterRpcNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final public int HEIGHT_WINDOW = 1;
|
||||||
|
|
||||||
|
private boolean testHeight(long height, long consensus) {
|
||||||
|
return (height >= (consensus - HEIGHT_WINDOW))
|
||||||
|
&& (height <= (consensus + HEIGHT_WINDOW));
|
||||||
|
}
|
||||||
|
|
||||||
|
private long calcConsensusHeight() {
|
||||||
|
Timber.d("Calc Consensus height from %d nodes", rpcNodes.size());
|
||||||
|
final Map<Long, Integer> nodeHeights = new TreeMap<Long, Integer>();
|
||||||
|
for (NodeInfo info : rpcNodes) {
|
||||||
|
if (!info.isValid()) continue;
|
||||||
|
Integer h = nodeHeights.get(info.getHeight());
|
||||||
|
if (h == null)
|
||||||
|
h = 0;
|
||||||
|
nodeHeights.put(info.getHeight(), h + 1);
|
||||||
|
}
|
||||||
|
long consensusHeight = 0;
|
||||||
|
long consensusCount = 0;
|
||||||
|
for (Map.Entry<Long, Integer> entry : nodeHeights.entrySet()) {
|
||||||
|
final long entryHeight = entry.getKey();
|
||||||
|
int count = 0;
|
||||||
|
for (long i = entryHeight - HEIGHT_WINDOW; i <= entryHeight + HEIGHT_WINDOW; i++) {
|
||||||
|
Integer v = nodeHeights.get(i);
|
||||||
|
if (v == null)
|
||||||
|
v = 0;
|
||||||
|
count += v;
|
||||||
|
}
|
||||||
|
if (count >= consensusCount) {
|
||||||
|
consensusCount = count;
|
||||||
|
consensusHeight = entryHeight;
|
||||||
|
}
|
||||||
|
Timber.d("%d - %d/%d", entryHeight, count, entry.getValue());
|
||||||
|
}
|
||||||
|
return consensusHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filterRpcNodes() {
|
||||||
|
long consensus = calcConsensusHeight();
|
||||||
|
Timber.d("Consensus Height = %d for %d nodes", consensus, rpcNodes.size());
|
||||||
|
for (Iterator<NodeInfo> iter = rpcNodes.iterator(); iter.hasNext(); ) {
|
||||||
|
NodeInfo info = iter.next();
|
||||||
|
// don't remove favourites
|
||||||
|
if (!info.isFavourite()) {
|
||||||
|
if (!testHeight(info.getHeight(), consensus)) {
|
||||||
|
iter.remove();
|
||||||
|
Timber.d("Removed %s", info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: does this NEED to be a ConcurrentLinkedDeque?
|
||||||
|
private ConcurrentLinkedDeque<Future<PeerRetriever>> jobs = new ConcurrentLinkedDeque<>();
|
||||||
|
|
||||||
|
private void retrievePeer(NodeInfo nodeInfo) {
|
||||||
|
if (knownNodes.add(nodeInfo)) {
|
||||||
|
Timber.d("\t%d:%s", knownNodes.size(), nodeInfo);
|
||||||
|
jobs.add(exeService.submit(new PeerRetriever(nodeInfo, this)));
|
||||||
|
peerCount++; // jobs.size() does not perform well
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void retrievePeers(PeerRetriever peer) {
|
||||||
|
for (InetSocketAddress socketAddress : peer.getPeers()) {
|
||||||
|
if (getMorePeers())
|
||||||
|
retrievePeer(new NodeInfo(socketAddress));
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seedPeers(Collection<NodeInfo> seedNodes) {
|
||||||
|
for (NodeInfo node : seedNodes) {
|
||||||
|
node.clear();
|
||||||
|
if (node.isFavourite()) {
|
||||||
|
rpcNodes.add(node);
|
||||||
|
if (listener != null) listener.onGet(node);
|
||||||
|
}
|
||||||
|
retrievePeer(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
226
app/src/main/java/com/m2049r/levin/scanner/PeerRetriever.java
Normal file
226
app/src/main/java/com/m2049r/levin/scanner/PeerRetriever.java
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* 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.levin.scanner;
|
||||||
|
|
||||||
|
import com.m2049r.levin.data.Bucket;
|
||||||
|
import com.m2049r.levin.data.Section;
|
||||||
|
import com.m2049r.levin.util.HexHelper;
|
||||||
|
import com.m2049r.levin.util.LittleEndianDataInputStream;
|
||||||
|
import com.m2049r.levin.util.LittleEndianDataOutputStream;
|
||||||
|
import com.m2049r.xmrwallet.data.NodeInfo;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
|
||||||
|
import java.io.DataInput;
|
||||||
|
import java.io.DataOutput;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class PeerRetriever implements Callable<PeerRetriever> {
|
||||||
|
static final public int CONNECT_TIMEOUT = 500; //ms
|
||||||
|
static final public int SOCKET_TIMEOUT = 500; //ms
|
||||||
|
static final public long PEER_ID = new Random().nextLong();
|
||||||
|
static final private byte[] HANDSHAKE = handshakeRequest().asByteArray();
|
||||||
|
static final private byte[] FLAGS_RESP = flagsResponse().asByteArray();
|
||||||
|
|
||||||
|
final private List<InetSocketAddress> peers = new ArrayList<>();
|
||||||
|
|
||||||
|
private NodeInfo nodeInfo;
|
||||||
|
private OnGetPeers onGetPeersCallback;
|
||||||
|
|
||||||
|
public interface OnGetPeers {
|
||||||
|
boolean getMorePeers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PeerRetriever(NodeInfo nodeInfo, OnGetPeers onGetPeers) {
|
||||||
|
this.nodeInfo = nodeInfo;
|
||||||
|
this.onGetPeersCallback = onGetPeers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeInfo getNodeInfo() {
|
||||||
|
return nodeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGood() {
|
||||||
|
return !peers.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InetSocketAddress> getPeers() {
|
||||||
|
return peers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PeerRetriever call() {
|
||||||
|
if (isGood()) // we have already been called?
|
||||||
|
throw new IllegalStateException();
|
||||||
|
// first check for an rpc service
|
||||||
|
nodeInfo.findRpcService();
|
||||||
|
if (onGetPeersCallback.getMorePeers())
|
||||||
|
try {
|
||||||
|
Timber.d("%s CONN", nodeInfo.getLevinSocketAddress());
|
||||||
|
if (!connect())
|
||||||
|
return this;
|
||||||
|
Bucket handshakeBucket = new Bucket(Bucket.COMMAND_HANDSHAKE_ID, HANDSHAKE);
|
||||||
|
handshakeBucket.send(getDataOutput());
|
||||||
|
|
||||||
|
while (true) {// wait for response (which may never come)
|
||||||
|
Bucket recv = new Bucket(getDataInput()); // times out after SOCKET_TIMEOUT
|
||||||
|
if ((recv.command == Bucket.COMMAND_HANDSHAKE_ID)
|
||||||
|
&& (!recv.haveToReturnData)) {
|
||||||
|
readAddressList(recv.payloadSection);
|
||||||
|
return this;
|
||||||
|
} else if ((recv.command == Bucket.COMMAND_REQUEST_SUPPORT_FLAGS_ID)
|
||||||
|
&& (recv.haveToReturnData)) {
|
||||||
|
Bucket flagsBucket = new Bucket(Bucket.COMMAND_REQUEST_SUPPORT_FLAGS_ID, FLAGS_RESP, 1);
|
||||||
|
flagsBucket.send(getDataOutput());
|
||||||
|
} else {// and ignore others
|
||||||
|
Timber.d("Ignored LEVIN COMMAND %d", recv.command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
} finally {
|
||||||
|
disconnect(); // we have what we want - byebye
|
||||||
|
Timber.d("%s DISCONN", nodeInfo.getLevinSocketAddress());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readAddressList(Section section) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Section> peerList = (List<Section>) section.get("local_peerlist_new");
|
||||||
|
if (peerList != null) {
|
||||||
|
for (Section peer : peerList) {
|
||||||
|
Section adr = (Section) peer.get("adr");
|
||||||
|
Byte type = (Byte) adr.get("type");
|
||||||
|
if ((type == null) || (type != 1))
|
||||||
|
continue;
|
||||||
|
Section addr = (Section) adr.get("addr");
|
||||||
|
if (addr == null)
|
||||||
|
continue;
|
||||||
|
Integer ip = (Integer) addr.get("m_ip");
|
||||||
|
if (ip == null)
|
||||||
|
continue;
|
||||||
|
Short sport = (Short) addr.get("m_port");
|
||||||
|
if (sport == null)
|
||||||
|
continue;
|
||||||
|
int port = sport;
|
||||||
|
if (port < 0) // port is unsigned
|
||||||
|
port = port + 0x10000;
|
||||||
|
InetAddress inet = HexHelper.toInetAddress(ip);
|
||||||
|
// make sure this is an address we want to talk to (i.e. a remote address)
|
||||||
|
if (!inet.isSiteLocalAddress() && !inet.isAnyLocalAddress()
|
||||||
|
&& !inet.isLoopbackAddress()
|
||||||
|
&& !inet.isMulticastAddress()
|
||||||
|
&& !inet.isLinkLocalAddress()) {
|
||||||
|
peers.add(new InetSocketAddress(inet, port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Socket socket = null;
|
||||||
|
|
||||||
|
private boolean connect() {
|
||||||
|
if (socket != null) throw new IllegalStateException();
|
||||||
|
try {
|
||||||
|
socket = new Socket();
|
||||||
|
socket.connect(nodeInfo.getLevinSocketAddress(), CONNECT_TIMEOUT);
|
||||||
|
socket.setSoTimeout(SOCKET_TIMEOUT);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
//Timber.d(ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isConnected() {
|
||||||
|
return socket.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disconnect() {
|
||||||
|
try {
|
||||||
|
dataInput = null;
|
||||||
|
dataOutput = null;
|
||||||
|
if ((socket != null) && (!socket.isClosed())) {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Timber.d(ex);
|
||||||
|
} finally {
|
||||||
|
socket = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataOutput dataOutput = null;
|
||||||
|
|
||||||
|
private DataOutput getDataOutput() throws IOException {
|
||||||
|
if (dataOutput == null)
|
||||||
|
synchronized (this) {
|
||||||
|
if (dataOutput == null)
|
||||||
|
dataOutput = new LittleEndianDataOutputStream(
|
||||||
|
socket.getOutputStream());
|
||||||
|
}
|
||||||
|
return dataOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataInput dataInput = null;
|
||||||
|
|
||||||
|
private DataInput getDataInput() throws IOException {
|
||||||
|
if (dataInput == null)
|
||||||
|
synchronized (this) {
|
||||||
|
if (dataInput == null)
|
||||||
|
dataInput = new LittleEndianDataInputStream(
|
||||||
|
socket.getInputStream());
|
||||||
|
}
|
||||||
|
return dataInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private Section handshakeRequest() {
|
||||||
|
Section section = new Section(); // root object
|
||||||
|
|
||||||
|
Section nodeData = new Section();
|
||||||
|
nodeData.add("local_time", (new Date()).getTime());
|
||||||
|
nodeData.add("my_port", 0);
|
||||||
|
byte[] networkId = Helper.hexToBytes("1230f171610441611731008216a1a110"); // mainnet
|
||||||
|
nodeData.add("network_id", networkId);
|
||||||
|
nodeData.add("peer_id", PEER_ID);
|
||||||
|
section.add("node_data", nodeData);
|
||||||
|
|
||||||
|
Section payloadData = new Section();
|
||||||
|
payloadData.add("cumulative_difficulty", 1L);
|
||||||
|
payloadData.add("current_height", 1L);
|
||||||
|
byte[] genesisHash =
|
||||||
|
Helper.hexToBytes("418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3");
|
||||||
|
payloadData.add("top_id", genesisHash);
|
||||||
|
payloadData.add("top_version", (byte) 1);
|
||||||
|
section.add("payload_data", payloadData);
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private Section flagsResponse() {
|
||||||
|
Section section = new Section(); // root object
|
||||||
|
section.add("support_flags", Bucket.P2P_SUPPORT_FLAGS);
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
}
|
42
app/src/main/java/com/m2049r/levin/util/HexHelper.java
Normal file
42
app/src/main/java/com/m2049r/levin/util/HexHelper.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.levin.util;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
public class HexHelper {
|
||||||
|
|
||||||
|
static public String bytesToHex(byte[] data) {
|
||||||
|
if ((data != null) && (data.length > 0))
|
||||||
|
return String.format("%0" + (data.length * 2) + "X",
|
||||||
|
new BigInteger(1, data));
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static public InetAddress toInetAddress(int ip) {
|
||||||
|
try {
|
||||||
|
String ipAddress = String.format("%d.%d.%d.%d", (ip & 0xff),
|
||||||
|
(ip >> 8 & 0xff), (ip >> 16 & 0xff), (ip >> 24 & 0xff));
|
||||||
|
return InetAddress.getByName(ipAddress);
|
||||||
|
} catch (UnknownHostException ex) {
|
||||||
|
throw new IllegalArgumentException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
182
app/src/main/java/com/m2049r/levin/util/LevinReader.java
Normal file
182
app/src/main/java/com/m2049r/levin/util/LevinReader.java
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* 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.levin.util;
|
||||||
|
|
||||||
|
import com.m2049r.levin.data.Section;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInput;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// Full Levin reader as seen on epee
|
||||||
|
|
||||||
|
public class LevinReader {
|
||||||
|
private DataInput in;
|
||||||
|
|
||||||
|
private LevinReader(byte[] buffer) {
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(buffer);
|
||||||
|
in = new LittleEndianDataInputStream(bis);
|
||||||
|
}
|
||||||
|
|
||||||
|
static public Section readPayload(byte[] payload) throws IOException {
|
||||||
|
LevinReader r = new LevinReader(payload);
|
||||||
|
return r.readPayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Section readPayload() throws IOException {
|
||||||
|
if (in.readInt() != Section.PORTABLE_STORAGE_SIGNATUREA)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
if (in.readInt() != Section.PORTABLE_STORAGE_SIGNATUREB)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
if (in.readByte() != Section.PORTABLE_STORAGE_FORMAT_VER)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
return readSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Section readSection() throws IOException {
|
||||||
|
Section section = new Section();
|
||||||
|
long count = readVarint();
|
||||||
|
while (count-- > 0) {
|
||||||
|
// read section name string
|
||||||
|
String sectionName = readSectionName();
|
||||||
|
section.add(sectionName, loadStorageEntry());
|
||||||
|
}
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object loadStorageArrayEntry(int type) throws IOException {
|
||||||
|
type &= ~Section.SERIALIZE_FLAG_ARRAY;
|
||||||
|
return readArrayEntry(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Object> readArrayEntry(int type) throws IOException {
|
||||||
|
List<Object> list = new ArrayList<Object>();
|
||||||
|
long size = readVarint();
|
||||||
|
while (size-- > 0)
|
||||||
|
list.add(read(type));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object read(int type) throws IOException {
|
||||||
|
switch (type) {
|
||||||
|
case Section.SERIALIZE_TYPE_UINT64:
|
||||||
|
case Section.SERIALIZE_TYPE_INT64:
|
||||||
|
return in.readLong();
|
||||||
|
case Section.SERIALIZE_TYPE_UINT32:
|
||||||
|
case Section.SERIALIZE_TYPE_INT32:
|
||||||
|
return in.readInt();
|
||||||
|
case Section.SERIALIZE_TYPE_UINT16:
|
||||||
|
case Section.SERIALIZE_TYPE_INT16:
|
||||||
|
return in.readShort();
|
||||||
|
case Section.SERIALIZE_TYPE_UINT8:
|
||||||
|
case Section.SERIALIZE_TYPE_INT8:
|
||||||
|
return in.readByte();
|
||||||
|
case Section.SERIALIZE_TYPE_OBJECT:
|
||||||
|
return readSection();
|
||||||
|
case Section.SERIALIZE_TYPE_STRING:
|
||||||
|
return readByteArray();
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("type " + type
|
||||||
|
+ " not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object loadStorageEntry() throws IOException {
|
||||||
|
int type = in.readUnsignedByte();
|
||||||
|
if ((type & Section.SERIALIZE_FLAG_ARRAY) != 0)
|
||||||
|
return loadStorageArrayEntry(type);
|
||||||
|
if (type == Section.SERIALIZE_TYPE_ARRAY)
|
||||||
|
return readStorageEntryArrayEntry();
|
||||||
|
else
|
||||||
|
return readStorageEntry(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object readStorageEntry(int type) throws IOException {
|
||||||
|
return read(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object readStorageEntryArrayEntry() throws IOException {
|
||||||
|
int type = in.readUnsignedByte();
|
||||||
|
if ((type & Section.SERIALIZE_FLAG_ARRAY) != 0)
|
||||||
|
throw new IllegalStateException("wrong type sequences");
|
||||||
|
return loadStorageArrayEntry(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readSectionName() throws IOException {
|
||||||
|
int nameLen = in.readUnsignedByte();
|
||||||
|
return readString(nameLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] read(long count) throws IOException {
|
||||||
|
if (count > Integer.MAX_VALUE)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
int len = (int) count;
|
||||||
|
final byte buffer[] = new byte[len];
|
||||||
|
in.readFully(buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readString(long count) throws IOException {
|
||||||
|
return new String(read(count), StandardCharsets.US_ASCII);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readByteArray(long count) throws IOException {
|
||||||
|
return read(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readByteArray() throws IOException {
|
||||||
|
long len = readVarint();
|
||||||
|
return readByteArray(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long readVarint() throws IOException {
|
||||||
|
long v = 0;
|
||||||
|
int b = in.readUnsignedByte();
|
||||||
|
int sizeMask = b & Section.PORTABLE_RAW_SIZE_MARK_MASK;
|
||||||
|
switch (sizeMask) {
|
||||||
|
case Section.PORTABLE_RAW_SIZE_MARK_BYTE:
|
||||||
|
v = b >>> 2;
|
||||||
|
break;
|
||||||
|
case Section.PORTABLE_RAW_SIZE_MARK_WORD:
|
||||||
|
v = readRest(b, 1) >>> 2;
|
||||||
|
break;
|
||||||
|
case Section.PORTABLE_RAW_SIZE_MARK_DWORD:
|
||||||
|
v = readRest(b, 3) >>> 2;
|
||||||
|
break;
|
||||||
|
case Section.PORTABLE_RAW_SIZE_MARK_INT64:
|
||||||
|
v = readRest(b, 7) >>> 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this should be in LittleEndianDataInputStream because it has little
|
||||||
|
// endian logic
|
||||||
|
private long readRest(int firstByte, int bytes) throws IOException {
|
||||||
|
long result = firstByte;
|
||||||
|
for (int i = 0; i < bytes; i++) {
|
||||||
|
result = result + (in.readUnsignedByte() << 8);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
98
app/src/main/java/com/m2049r/levin/util/LevinWriter.java
Normal file
98
app/src/main/java/com/m2049r/levin/util/LevinWriter.java
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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.levin.util;
|
||||||
|
|
||||||
|
import com.m2049r.levin.data.Section;
|
||||||
|
|
||||||
|
import java.io.DataOutput;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
// a simplified Levin Writer WITHOUT support for arrays
|
||||||
|
|
||||||
|
public class LevinWriter {
|
||||||
|
private DataOutput out;
|
||||||
|
|
||||||
|
public LevinWriter(DataOutput out) {
|
||||||
|
this.out = out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writePayload(Section section) throws IOException {
|
||||||
|
out.writeInt(Section.PORTABLE_STORAGE_SIGNATUREA);
|
||||||
|
out.writeInt(Section.PORTABLE_STORAGE_SIGNATUREB);
|
||||||
|
out.writeByte(Section.PORTABLE_STORAGE_FORMAT_VER);
|
||||||
|
putSection(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeSection(Section section) throws IOException {
|
||||||
|
out.writeByte(Section.SERIALIZE_TYPE_OBJECT);
|
||||||
|
putSection(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putSection(Section section) throws IOException {
|
||||||
|
writeVarint(section.size());
|
||||||
|
for (Map.Entry<String, Object> kv : section.entrySet()) {
|
||||||
|
byte[] key = kv.getKey().getBytes(StandardCharsets.US_ASCII);
|
||||||
|
out.writeByte(key.length);
|
||||||
|
out.write(key);
|
||||||
|
write(kv.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeVarint(long i) throws IOException {
|
||||||
|
if (i <= 63) {
|
||||||
|
out.writeByte(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_BYTE);
|
||||||
|
} else if (i <= 16383) {
|
||||||
|
out.writeShort(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_WORD);
|
||||||
|
} else if (i <= 1073741823) {
|
||||||
|
out.writeInt(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_DWORD);
|
||||||
|
} else {
|
||||||
|
if (i > 4611686018427387903L)
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
out.writeLong((i << 2) | Section.PORTABLE_RAW_SIZE_MARK_INT64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write(Object object) throws IOException {
|
||||||
|
if (object instanceof byte[]) {
|
||||||
|
byte[] value = (byte[]) object;
|
||||||
|
out.writeByte(Section.SERIALIZE_TYPE_STRING);
|
||||||
|
writeVarint(value.length);
|
||||||
|
out.write(value);
|
||||||
|
} else if (object instanceof String) {
|
||||||
|
byte[] value = ((String) object)
|
||||||
|
.getBytes(StandardCharsets.US_ASCII);
|
||||||
|
out.writeByte(Section.SERIALIZE_TYPE_STRING);
|
||||||
|
writeVarint(value.length);
|
||||||
|
out.write(value);
|
||||||
|
} else if (object instanceof Integer) {
|
||||||
|
out.writeByte(Section.SERIALIZE_TYPE_UINT32);
|
||||||
|
out.writeInt((int) object);
|
||||||
|
} else if (object instanceof Long) {
|
||||||
|
out.writeByte(Section.SERIALIZE_TYPE_UINT64);
|
||||||
|
out.writeLong((long) object);
|
||||||
|
} else if (object instanceof Byte) {
|
||||||
|
out.writeByte(Section.SERIALIZE_TYPE_UINT8);
|
||||||
|
out.writeByte((byte) object);
|
||||||
|
} else if (object instanceof Section) {
|
||||||
|
writeSection((Section) object);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -50,19 +50,19 @@ public class BaseActivity extends SecureActivity implements GenerateReviewFragme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showProgressDialog(int msgId) {
|
public void showProgressDialog(int msgId) {
|
||||||
showProgressDialog(msgId, 0);
|
showProgressDialog(msgId, 250); // don't show dialog for fast operations
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showProgressDialog(int msgId, long delay) {
|
public void showProgressDialog(int msgId, long delayMillis) {
|
||||||
dismissProgressDialog(); // just in case
|
dismissProgressDialog(); // just in case
|
||||||
progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
|
progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
|
||||||
if (delay > 0) {
|
if (delayMillis > 0) {
|
||||||
Handler handler = new Handler();
|
Handler handler = new Handler();
|
||||||
handler.postDelayed(new Runnable() {
|
handler.postDelayed(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
if (progressDialog != null) progressDialog.show();
|
if (progressDialog != null) progressDialog.show();
|
||||||
}
|
}
|
||||||
}, delay);
|
}, delayMillis);
|
||||||
} else {
|
} else {
|
||||||
progressDialog.show();
|
progressDialog.show();
|
||||||
}
|
}
|
||||||
|
@@ -85,15 +85,15 @@ public class GenerateFragment extends Fragment {
|
|||||||
|
|
||||||
View view = inflater.inflate(R.layout.fragment_generate, container, false);
|
View view = inflater.inflate(R.layout.fragment_generate, container, false);
|
||||||
|
|
||||||
etWalletName = (TextInputLayout) view.findViewById(R.id.etWalletName);
|
etWalletName = view.findViewById(R.id.etWalletName);
|
||||||
etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword);
|
etWalletPassword = view.findViewById(R.id.etWalletPassword);
|
||||||
llFingerprintAuth = (LinearLayout) view.findViewById(R.id.llFingerprintAuth);
|
llFingerprintAuth = view.findViewById(R.id.llFingerprintAuth);
|
||||||
etWalletMnemonic = (TextInputLayout) view.findViewById(R.id.etWalletMnemonic);
|
etWalletMnemonic = view.findViewById(R.id.etWalletMnemonic);
|
||||||
etWalletAddress = (TextInputLayout) view.findViewById(R.id.etWalletAddress);
|
etWalletAddress = view.findViewById(R.id.etWalletAddress);
|
||||||
etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey);
|
etWalletViewKey = view.findViewById(R.id.etWalletViewKey);
|
||||||
etWalletSpendKey = (TextInputLayout) view.findViewById(R.id.etWalletSpendKey);
|
etWalletSpendKey = view.findViewById(R.id.etWalletSpendKey);
|
||||||
etWalletRestoreHeight = (TextInputLayout) view.findViewById(R.id.etWalletRestoreHeight);
|
etWalletRestoreHeight = view.findViewById(R.id.etWalletRestoreHeight);
|
||||||
bGenerate = (Button) view.findViewById(R.id.bGenerate);
|
bGenerate = view.findViewById(R.id.bGenerate);
|
||||||
|
|
||||||
etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
|
etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||||
etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
|
@@ -91,22 +91,22 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
|
|
||||||
View view = inflater.inflate(R.layout.fragment_review, container, false);
|
View view = inflater.inflate(R.layout.fragment_review, container, false);
|
||||||
|
|
||||||
scrollview = (ScrollView) view.findViewById(R.id.scrollview);
|
scrollview = view.findViewById(R.id.scrollview);
|
||||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
pbProgress = view.findViewById(R.id.pbProgress);
|
||||||
tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword);
|
tvWalletPassword = view.findViewById(R.id.tvWalletPassword);
|
||||||
tvWalletAddress = (TextView) view.findViewById(R.id.tvWalletAddress);
|
tvWalletAddress = view.findViewById(R.id.tvWalletAddress);
|
||||||
tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey);
|
tvWalletViewKey = view.findViewById(R.id.tvWalletViewKey);
|
||||||
tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey);
|
tvWalletSpendKey = view.findViewById(R.id.tvWalletSpendKey);
|
||||||
tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic);
|
tvWalletMnemonic = view.findViewById(R.id.tvWalletMnemonic);
|
||||||
bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
|
bCopyAddress = view.findViewById(R.id.bCopyAddress);
|
||||||
bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
|
bAdvancedInfo = view.findViewById(R.id.bAdvancedInfo);
|
||||||
llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
|
llAdvancedInfo = view.findViewById(R.id.llAdvancedInfo);
|
||||||
llPassword = (LinearLayout) view.findViewById(R.id.llPassword);
|
llPassword = view.findViewById(R.id.llPassword);
|
||||||
llMnemonic = (LinearLayout) view.findViewById(R.id.llMnemonic);
|
llMnemonic = view.findViewById(R.id.llMnemonic);
|
||||||
llSpendKey = (LinearLayout) view.findViewById(R.id.llSpendKey);
|
llSpendKey = view.findViewById(R.id.llSpendKey);
|
||||||
llViewKey = (LinearLayout) view.findViewById(R.id.llViewKey);
|
llViewKey = view.findViewById(R.id.llViewKey);
|
||||||
|
|
||||||
bAccept = (Button) view.findViewById(R.id.bAccept);
|
bAccept = view.findViewById(R.id.bAccept);
|
||||||
|
|
||||||
boolean allowCopy = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
|
boolean allowCopy = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
|
||||||
tvWalletMnemonic.setTextIsSelectable(allowCopy);
|
tvWalletMnemonic.setTextIsSelectable(allowCopy);
|
||||||
@@ -481,13 +481,13 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
|
||||||
alertDialogBuilder.setView(promptsView);
|
alertDialogBuilder.setView(promptsView);
|
||||||
|
|
||||||
final TextInputLayout etPasswordA = (TextInputLayout) promptsView.findViewById(R.id.etWalletPasswordA);
|
final TextInputLayout etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA);
|
||||||
etPasswordA.setHint(getString(R.string.prompt_changepw, walletName));
|
etPasswordA.setHint(getString(R.string.prompt_changepw, walletName));
|
||||||
|
|
||||||
final TextInputLayout etPasswordB = (TextInputLayout) promptsView.findViewById(R.id.etWalletPasswordB);
|
final TextInputLayout etPasswordB = promptsView.findViewById(R.id.etWalletPasswordB);
|
||||||
etPasswordB.setHint(getString(R.string.prompt_changepwB, walletName));
|
etPasswordB.setHint(getString(R.string.prompt_changepwB, walletName));
|
||||||
|
|
||||||
LinearLayout llFingerprintAuth = (LinearLayout) promptsView.findViewById(R.id.llFingerprintAuth);
|
LinearLayout llFingerprintAuth = promptsView.findViewById(R.id.llFingerprintAuth);
|
||||||
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0);
|
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0);
|
||||||
if (FingerprintHelper.isDeviceSupported(getActivity())) {
|
if (FingerprintHelper.isDeviceSupported(getActivity())) {
|
||||||
llFingerprintAuth.setVisibility(View.VISIBLE);
|
llFingerprintAuth.setVisibility(View.VISIBLE);
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
550
app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java
Normal file
550
app/src/main/java/com/m2049r/xmrwallet/NodeFragment.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -92,17 +92,17 @@ public class ReceiveFragment extends Fragment {
|
|||||||
|
|
||||||
View view = inflater.inflate(R.layout.fragment_receive, container, false);
|
View view = inflater.inflate(R.layout.fragment_receive, container, false);
|
||||||
|
|
||||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
pbProgress = view.findViewById(R.id.pbProgress);
|
||||||
tvAddressLabel = (TextView) view.findViewById(R.id.tvAddressLabel);
|
tvAddressLabel = view.findViewById(R.id.tvAddressLabel);
|
||||||
tvAddress = (TextView) view.findViewById(R.id.tvAddress);
|
tvAddress = view.findViewById(R.id.tvAddress);
|
||||||
etNotes = (TextInputLayout) view.findViewById(R.id.etNotes);
|
etNotes = view.findViewById(R.id.etNotes);
|
||||||
evAmount = (ExchangeView) view.findViewById(R.id.evAmount);
|
evAmount = view.findViewById(R.id.evAmount);
|
||||||
qrCode = (ImageView) view.findViewById(R.id.qrCode);
|
qrCode = view.findViewById(R.id.qrCode);
|
||||||
tvQrCode = (TextView) view.findViewById(R.id.tvQrCode);
|
tvQrCode = view.findViewById(R.id.tvQrCode);
|
||||||
qrCodeFull = (ImageView) view.findViewById(R.id.qrCodeFull);
|
qrCodeFull = view.findViewById(R.id.qrCodeFull);
|
||||||
etDummy = (EditText) view.findViewById(R.id.etDummy);
|
etDummy = view.findViewById(R.id.etDummy);
|
||||||
bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
|
bCopyAddress = view.findViewById(R.id.bCopyAddress);
|
||||||
bSubaddress = (Button) view.findViewById(R.id.bSubaddress);
|
bSubaddress = view.findViewById(R.id.bSubaddress);
|
||||||
|
|
||||||
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
|
|
||||||
@@ -438,7 +438,7 @@ public class ReceiveFragment extends Fragment {
|
|||||||
Bitmap logoBitmap = Bitmap.createBitmap(qrWidth, qrHeight, Bitmap.Config.ARGB_8888);
|
Bitmap logoBitmap = Bitmap.createBitmap(qrWidth, qrHeight, Bitmap.Config.ARGB_8888);
|
||||||
Canvas canvas = new Canvas(logoBitmap);
|
Canvas canvas = new Canvas(logoBitmap);
|
||||||
canvas.drawBitmap(qrBitmap, 0, 0, null);
|
canvas.drawBitmap(qrBitmap, 0, 0, null);
|
||||||
canvas.save(Canvas.ALL_SAVE_FLAG);
|
canvas.save();
|
||||||
// figure out how to scale the logo
|
// figure out how to scale the logo
|
||||||
float scaleSize = 1.0f;
|
float scaleSize = 1.0f;
|
||||||
while ((logoWidth / scaleSize) > (qrWidth / 5) || (logoHeight / scaleSize) > (qrHeight / 5)) {
|
while ((logoWidth / scaleSize) > (qrWidth / 5) || (logoHeight / scaleSize) > (qrHeight / 5)) {
|
||||||
@@ -482,7 +482,6 @@ public class ReceiveFragment extends Fragment {
|
|||||||
if (context instanceof GenerateReviewFragment.ProgressListener) {
|
if (context instanceof GenerateReviewFragment.ProgressListener) {
|
||||||
this.progressCallback = (GenerateReviewFragment.ProgressListener) context;
|
this.progressCallback = (GenerateReviewFragment.ProgressListener) context;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -31,7 +31,7 @@ public abstract class SecureActivity extends AppCompatActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// set FLAG_SECURE to prevent screenshots in Release Mode
|
// set FLAG_SECURE to prevent screenshots in Release Mode
|
||||||
if (!BuildConfig.DEBUG) {
|
if (!BuildConfig.DEBUG && !BuildConfig.FLAVOR_type.equals("alpha")) {
|
||||||
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -60,6 +60,7 @@ public class TxFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private TextView tvAccount;
|
private TextView tvAccount;
|
||||||
|
private TextView tvAddress;
|
||||||
private TextView tvTxTimestamp;
|
private TextView tvTxTimestamp;
|
||||||
private TextView tvTxId;
|
private TextView tvTxId;
|
||||||
private TextView tvTxKey;
|
private TextView tvTxKey;
|
||||||
@@ -85,22 +86,23 @@ public class TxFragment extends Fragment {
|
|||||||
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
|
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
|
||||||
|
|
||||||
cvXmrTo = view.findViewById(R.id.cvXmrTo);
|
cvXmrTo = view.findViewById(R.id.cvXmrTo);
|
||||||
tvTxXmrToKey = (TextView) view.findViewById(R.id.tvTxXmrToKey);
|
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
|
||||||
tvDestinationBtc = (TextView) view.findViewById(R.id.tvDestinationBtc);
|
tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
|
||||||
tvTxAmountBtc = (TextView) view.findViewById(R.id.tvTxAmountBtc);
|
tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc);
|
||||||
|
|
||||||
tvAccount = (TextView) view.findViewById(R.id.tvAccount);
|
tvAccount = view.findViewById(R.id.tvAccount);
|
||||||
tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
|
tvAddress = view.findViewById(R.id.tvAddress);
|
||||||
tvTxId = (TextView) view.findViewById(R.id.tvTxId);
|
tvTxTimestamp = view.findViewById(R.id.tvTxTimestamp);
|
||||||
tvTxKey = (TextView) view.findViewById(R.id.tvTxKey);
|
tvTxId = view.findViewById(R.id.tvTxId);
|
||||||
tvDestination = (TextView) view.findViewById(R.id.tvDestination);
|
tvTxKey = view.findViewById(R.id.tvTxKey);
|
||||||
tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId);
|
tvDestination = view.findViewById(R.id.tvDestination);
|
||||||
tvTxBlockheight = (TextView) view.findViewById(R.id.tvTxBlockheight);
|
tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
|
||||||
tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount);
|
tvTxBlockheight = view.findViewById(R.id.tvTxBlockheight);
|
||||||
tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
|
tvTxAmount = view.findViewById(R.id.tvTxAmount);
|
||||||
tvTxTransfers = (TextView) view.findViewById(R.id.tvTxTransfers);
|
tvTxFee = view.findViewById(R.id.tvTxFee);
|
||||||
etTxNotes = (TextView) view.findViewById(R.id.etTxNotes);
|
tvTxTransfers = view.findViewById(R.id.tvTxTransfers);
|
||||||
bTxNotes = (Button) view.findViewById(R.id.bTxNotes);
|
etTxNotes = view.findViewById(R.id.etTxNotes);
|
||||||
|
bTxNotes = view.findViewById(R.id.bTxNotes);
|
||||||
|
|
||||||
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||||
|
|
||||||
@@ -219,12 +221,16 @@ public class TxFragment extends Fragment {
|
|||||||
if (info.txKey == null) {
|
if (info.txKey == null) {
|
||||||
info.txKey = activityCallback.getTxKey(info.hash);
|
info.txKey = activityCallback.getTxKey(info.hash);
|
||||||
}
|
}
|
||||||
|
if (info.address == null) {
|
||||||
|
info.address = activityCallback.getTxAddress(info.account, info.subaddress);
|
||||||
|
}
|
||||||
loadNotes(info);
|
loadNotes(info);
|
||||||
|
|
||||||
activityCallback.setSubtitle(getString(R.string.tx_title));
|
activityCallback.setSubtitle(getString(R.string.tx_title));
|
||||||
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||||
|
|
||||||
tvAccount.setText(getString(R.string.tx_account_formatted, info.account, info.subaddress));
|
tvAccount.setText(getString(R.string.tx_account_formatted, info.account, info.subaddress));
|
||||||
|
tvAddress.setText(info.address);
|
||||||
|
|
||||||
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
|
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
|
||||||
tvTxId.setText(info.hash);
|
tvTxId.setText(info.hash);
|
||||||
@@ -331,6 +337,8 @@ public class TxFragment extends Fragment {
|
|||||||
|
|
||||||
String getTxNotes(String hash);
|
String getTxNotes(String hash);
|
||||||
|
|
||||||
|
String getTxAddress(int major, int minor);
|
||||||
|
|
||||||
void onSetNote(String txId, String notes);
|
void onSetNote(String txId, String notes);
|
||||||
|
|
||||||
void setToolbarButton(int type);
|
void setToolbarButton(int type);
|
||||||
|
@@ -51,7 +51,6 @@ import com.m2049r.xmrwallet.dialog.CreditsFragment;
|
|||||||
import com.m2049r.xmrwallet.dialog.HelpFragment;
|
import com.m2049r.xmrwallet.dialog.HelpFragment;
|
||||||
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
|
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
|
||||||
import com.m2049r.xmrwallet.fragment.send.SendFragment;
|
import com.m2049r.xmrwallet.fragment.send.SendFragment;
|
||||||
import com.m2049r.xmrwallet.ledger.Ledger;
|
|
||||||
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
|
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
|
||||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||||
import com.m2049r.xmrwallet.model.TransactionInfo;
|
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||||
@@ -81,6 +80,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
public static final String REQUEST_ID = "id";
|
public static final String REQUEST_ID = "id";
|
||||||
public static final String REQUEST_PW = "pw";
|
public static final String REQUEST_PW = "pw";
|
||||||
public static final String REQUEST_FINGERPRINT_USED = "fingerprint";
|
public static final String REQUEST_FINGERPRINT_USED = "fingerprint";
|
||||||
|
public static final String REQUEST_STREETMODE = "streetmode";
|
||||||
|
|
||||||
private NavigationView accountsView;
|
private NavigationView accountsView;
|
||||||
private DrawerLayout drawer;
|
private DrawerLayout drawer;
|
||||||
@@ -88,9 +88,12 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
|
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
private boolean needVerifyIdentity;
|
private boolean needVerifyIdentity;
|
||||||
|
private boolean requestStreetMode = false;
|
||||||
|
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
private long streetMode = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPasswordChanged(String newPassword) {
|
public void onPasswordChanged(String newPassword) {
|
||||||
password = newPassword;
|
password = newPassword;
|
||||||
@@ -129,6 +132,27 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
return synced;
|
return synced;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStreetMode() {
|
||||||
|
return streetMode > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableStreetMode(boolean enable) {
|
||||||
|
if (enable) {
|
||||||
|
needVerifyIdentity = true;
|
||||||
|
streetMode = getWallet().getDaemonBlockChainHeight();
|
||||||
|
} else {
|
||||||
|
streetMode = 0;
|
||||||
|
}
|
||||||
|
updateAccountsBalance();
|
||||||
|
forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getStreetModeHeight() {
|
||||||
|
return streetMode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWatchOnly() {
|
public boolean isWatchOnly() {
|
||||||
return getWallet().isWatchOnly();
|
return getWallet().isWatchOnly();
|
||||||
@@ -144,6 +168,11 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
return getWallet().getUserNote(txId);
|
return getWallet().getUserNote(txId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTxAddress(int major, int minor) {
|
||||||
|
return getWallet().getSubaddress(major, minor);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
@@ -156,6 +185,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
acquireWakeLock();
|
acquireWakeLock();
|
||||||
String walletId = extras.getString(REQUEST_ID);
|
String walletId = extras.getString(REQUEST_ID);
|
||||||
needVerifyIdentity = extras.getBoolean(REQUEST_FINGERPRINT_USED);
|
needVerifyIdentity = extras.getBoolean(REQUEST_FINGERPRINT_USED);
|
||||||
|
// we can set the streetmode height AFTER opening the wallet
|
||||||
|
requestStreetMode = extras.getBoolean(REQUEST_STREETMODE);
|
||||||
password = extras.getString(REQUEST_PW);
|
password = extras.getString(REQUEST_PW);
|
||||||
connectWalletService(walletId, password);
|
connectWalletService(walletId, password);
|
||||||
} else {
|
} else {
|
||||||
@@ -196,7 +227,14 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
MenuItem renameItem = menu.findItem(R.id.action_rename);
|
MenuItem renameItem = menu.findItem(R.id.action_rename);
|
||||||
if (renameItem != null)
|
if (renameItem != null)
|
||||||
renameItem.setVisible(hasWallet() && getWallet().isSynchronized());
|
renameItem.setVisible(hasWallet() && getWallet().isSynchronized());
|
||||||
return true;
|
MenuItem streetmodeItem = menu.findItem(R.id.action_streetmode);
|
||||||
|
if (streetmodeItem != null)
|
||||||
|
if (isStreetMode()) {
|
||||||
|
streetmodeItem.setIcon(R.drawable.gunther_csi_24dp);
|
||||||
|
} else {
|
||||||
|
streetmodeItem.setIcon(R.drawable.gunther_24dp);
|
||||||
|
}
|
||||||
|
return super.onPrepareOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -229,11 +267,49 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
case R.id.action_rename:
|
case R.id.action_rename:
|
||||||
onAccountRename();
|
onAccountRename();
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_streetmode:
|
||||||
|
if (isStreetMode()) { // disable streetmode
|
||||||
|
onDisableStreetMode();
|
||||||
|
} else {
|
||||||
|
onEnableStreetMode();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateStreetMode() {
|
||||||
|
if (isStreetMode()) {
|
||||||
|
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_streetmode);
|
||||||
|
} else {
|
||||||
|
showNet();
|
||||||
|
}
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onEnableStreetMode() {
|
||||||
|
enableStreetMode(true);
|
||||||
|
updateStreetMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDisableStreetMode() {
|
||||||
|
Helper.promptPassword(WalletActivity.this, getWallet().getName(), false, new Helper.PasswordAction() {
|
||||||
|
@Override
|
||||||
|
public void action(String walletName, String password, boolean fingerprintUsed) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
enableStreetMode(false);
|
||||||
|
updateStreetMode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void onWalletChangePassword() {
|
public void onWalletChangePassword() {
|
||||||
try {
|
try {
|
||||||
GenerateReviewFragment detailsFragment = (GenerateReviewFragment)
|
GenerateReviewFragment detailsFragment = (GenerateReviewFragment)
|
||||||
@@ -260,7 +336,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
}
|
}
|
||||||
|
|
||||||
setContentView(R.layout.activity_wallet);
|
setContentView(R.layout.activity_wallet);
|
||||||
toolbar = (Toolbar) findViewById(R.id.toolbar);
|
toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||||
|
|
||||||
@@ -289,13 +365,13 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
drawer = findViewById(R.id.drawer_layout);
|
||||||
drawerToggle = new ActionBarDrawerToggle(this, drawer, toolbar, 0, 0);
|
drawerToggle = new ActionBarDrawerToggle(this, drawer, toolbar, 0, 0);
|
||||||
drawer.addDrawerListener(drawerToggle);
|
drawer.addDrawerListener(drawerToggle);
|
||||||
drawerToggle.syncState();
|
drawerToggle.syncState();
|
||||||
setDrawerEnabled(false); // disable until synced
|
setDrawerEnabled(false); // disable until synced
|
||||||
|
|
||||||
accountsView = (NavigationView) findViewById(R.id.accounts_nav);
|
accountsView = findViewById(R.id.accounts_nav);
|
||||||
accountsView.setNavigationItemSelectedListener(this);
|
accountsView.setNavigationItemSelectedListener(this);
|
||||||
|
|
||||||
showNet();
|
showNet();
|
||||||
@@ -325,6 +401,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Wallet getWallet() {
|
public Wallet getWallet() {
|
||||||
if (mBoundService == null) throw new IllegalStateException("WalletService not bound.");
|
if (mBoundService == null) throw new IllegalStateException("WalletService not bound.");
|
||||||
return mBoundService.getWallet();
|
return mBoundService.getWallet();
|
||||||
@@ -556,6 +633,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
haveWallet = true;
|
haveWallet = true;
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
|
|
||||||
|
enableStreetMode(requestStreetMode);
|
||||||
|
|
||||||
final WalletFragment walletFragment = (WalletFragment)
|
final WalletFragment walletFragment = (WalletFragment)
|
||||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
@@ -971,15 +1050,18 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
// drawer stuff
|
// drawer stuff
|
||||||
|
|
||||||
void updateAccountsBalance() {
|
void updateAccountsBalance() {
|
||||||
final Wallet wallet = getWallet();
|
final TextView tvBalance = accountsView.getHeaderView(0).findViewById(R.id.tvBalance);
|
||||||
final TextView tvBalance = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvBalance);
|
if (!isStreetMode()) {
|
||||||
tvBalance.setText(getString(R.string.accounts_balance,
|
tvBalance.setText(getString(R.string.accounts_balance,
|
||||||
Helper.getDisplayAmount(wallet.getBalanceAll(), 5)));
|
Helper.getDisplayAmount(getWallet().getBalanceAll(), 5)));
|
||||||
|
} else {
|
||||||
|
tvBalance.setText(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateAccountsHeader() {
|
void updateAccountsHeader() {
|
||||||
final Wallet wallet = getWallet();
|
final Wallet wallet = getWallet();
|
||||||
final TextView tvName = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvName);
|
final TextView tvName = accountsView.getHeaderView(0).findViewById(R.id.tvName);
|
||||||
tvName.setText(wallet.getName());
|
tvName.setText(wallet.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1020,8 +1102,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
|||||||
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
|
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
|
||||||
alertDialogBuilder.setView(promptsView);
|
alertDialogBuilder.setView(promptsView);
|
||||||
|
|
||||||
final EditText etRename = (EditText) promptsView.findViewById(R.id.etRename);
|
final EditText etRename = promptsView.findViewById(R.id.etRename);
|
||||||
final TextView tvRenameLabel = (TextView) promptsView.findViewById(R.id.tvRenameLabel);
|
final TextView tvRenameLabel = promptsView.findViewById(R.id.tvRenameLabel);
|
||||||
final Wallet wallet = getWallet();
|
final Wallet wallet = getWallet();
|
||||||
tvRenameLabel.setText(getString(R.string.prompt_rename, wallet.getAccountLabel()));
|
tvRenameLabel.setText(getString(R.string.prompt_rename, wallet.getAccountLabel()));
|
||||||
|
|
||||||
|
@@ -33,6 +33,7 @@ import android.widget.ArrayAdapter;
|
|||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@@ -47,7 +48,10 @@ import com.m2049r.xmrwallet.util.Helper;
|
|||||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@@ -56,6 +60,8 @@ public class WalletFragment extends Fragment
|
|||||||
private TransactionInfoAdapter adapter;
|
private TransactionInfoAdapter adapter;
|
||||||
private NumberFormat formatter = NumberFormat.getInstance();
|
private NumberFormat formatter = NumberFormat.getInstance();
|
||||||
|
|
||||||
|
private TextView tvStreetView;
|
||||||
|
private LinearLayout llBalance;
|
||||||
private FrameLayout flExchange;
|
private FrameLayout flExchange;
|
||||||
private TextView tvBalance;
|
private TextView tvBalance;
|
||||||
private TextView tvUnconfirmedAmount;
|
private TextView tvUnconfirmedAmount;
|
||||||
@@ -85,28 +91,30 @@ public class WalletFragment extends Fragment
|
|||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_wallet, container, false);
|
View view = inflater.inflate(R.layout.fragment_wallet, container, false);
|
||||||
|
|
||||||
flExchange = (FrameLayout) view.findViewById(R.id.flExchange);
|
tvStreetView = view.findViewById(R.id.tvStreetView);
|
||||||
|
llBalance = view.findViewById(R.id.llBalance);
|
||||||
|
flExchange = view.findViewById(R.id.flExchange);
|
||||||
((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable().
|
((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable().
|
||||||
setColorFilter(getResources().getColor(R.color.trafficGray),
|
setColorFilter(getResources().getColor(R.color.trafficGray),
|
||||||
android.graphics.PorterDuff.Mode.MULTIPLY);
|
android.graphics.PorterDuff.Mode.MULTIPLY);
|
||||||
|
|
||||||
tvProgress = (TextView) view.findViewById(R.id.tvProgress);
|
tvProgress = view.findViewById(R.id.tvProgress);
|
||||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
pbProgress = view.findViewById(R.id.pbProgress);
|
||||||
tvBalance = (TextView) view.findViewById(R.id.tvBalance);
|
tvBalance = view.findViewById(R.id.tvBalance);
|
||||||
tvBalance.setText(Helper.getDisplayAmount(0));
|
showBalance(Helper.getDisplayAmount(0));
|
||||||
tvUnconfirmedAmount = (TextView) view.findViewById(R.id.tvUnconfirmedAmount);
|
tvUnconfirmedAmount = view.findViewById(R.id.tvUnconfirmedAmount);
|
||||||
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, Helper.getDisplayAmount(0)));
|
showUnconfirmed(0);
|
||||||
ivSynced = (ImageView) view.findViewById(R.id.ivSynced);
|
ivSynced = view.findViewById(R.id.ivSynced);
|
||||||
|
|
||||||
sCurrency = (Spinner) view.findViewById(R.id.sCurrency);
|
sCurrency = view.findViewById(R.id.sCurrency);
|
||||||
ArrayAdapter currencyAdapter = 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);
|
currencyAdapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item);
|
||||||
sCurrency.setAdapter(currencyAdapter);
|
sCurrency.setAdapter(currencyAdapter);
|
||||||
|
|
||||||
bSend = (Button) view.findViewById(R.id.bSend);
|
bSend = view.findViewById(R.id.bSend);
|
||||||
bReceive = (Button) view.findViewById(R.id.bReceive);
|
bReceive = view.findViewById(R.id.bReceive);
|
||||||
|
|
||||||
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
|
RecyclerView recyclerView = view.findViewById(R.id.list);
|
||||||
|
|
||||||
this.adapter = new TransactionInfoAdapter(getActivity(), this);
|
this.adapter = new TransactionInfoAdapter(getActivity(), this);
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
@@ -145,6 +153,26 @@ public class WalletFragment extends Fragment
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showBalance(String balance) {
|
||||||
|
tvBalance.setText(balance);
|
||||||
|
if (!activityCallback.isStreetMode()) {
|
||||||
|
llBalance.setVisibility(View.VISIBLE);
|
||||||
|
tvStreetView.setVisibility(View.INVISIBLE);
|
||||||
|
} else {
|
||||||
|
llBalance.setVisibility(View.INVISIBLE);
|
||||||
|
tvStreetView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showUnconfirmed(double unconfirmedAmount) {
|
||||||
|
if (!activityCallback.isStreetMode()) {
|
||||||
|
String unconfirmed = Helper.getFormattedAmount(unconfirmedAmount, true);
|
||||||
|
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed));
|
||||||
|
} else {
|
||||||
|
tvUnconfirmedAmount.setText(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void updateBalance() {
|
void updateBalance() {
|
||||||
if (isExchanging) return; // wait for exchange to finish - it will fire this itself then.
|
if (isExchanging) return; // wait for exchange to finish - it will fire this itself then.
|
||||||
// at this point selection is XMR in case of error
|
// at this point selection is XMR in case of error
|
||||||
@@ -156,7 +184,7 @@ public class WalletFragment extends Fragment
|
|||||||
} else { // XMR
|
} else { // XMR
|
||||||
displayB = Helper.getFormattedAmount(amountA, true);
|
displayB = Helper.getFormattedAmount(amountA, true);
|
||||||
}
|
}
|
||||||
tvBalance.setText(displayB);
|
showBalance(displayB);
|
||||||
}
|
}
|
||||||
|
|
||||||
String balanceCurrency = Helper.CRYPTO;
|
String balanceCurrency = Helper.CRYPTO;
|
||||||
@@ -165,9 +193,11 @@ public class WalletFragment extends Fragment
|
|||||||
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
|
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
|
||||||
|
|
||||||
void refreshBalance() {
|
void refreshBalance() {
|
||||||
|
double unconfirmedXmr = Double.parseDouble(Helper.getDisplayAmount(balance - unlockedBalance));
|
||||||
|
showUnconfirmed(unconfirmedXmr);
|
||||||
if (sCurrency.getSelectedItemPosition() == 0) { // XMR
|
if (sCurrency.getSelectedItemPosition() == 0) { // XMR
|
||||||
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
|
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
|
||||||
tvBalance.setText(Helper.getFormattedAmount(amountXmr, true));
|
showBalance(Helper.getFormattedAmount(amountXmr, true));
|
||||||
} else { // not XMR
|
} else { // not XMR
|
||||||
String currency = (String) sCurrency.getSelectedItem();
|
String currency = (String) sCurrency.getSelectedItem();
|
||||||
Timber.d(currency);
|
Timber.d(currency);
|
||||||
@@ -223,7 +253,7 @@ public class WalletFragment extends Fragment
|
|||||||
public void exchangeFailed() {
|
public void exchangeFailed() {
|
||||||
sCurrency.setSelection(0, true); // default to XMR
|
sCurrency.setSelection(0, true); // default to XMR
|
||||||
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
|
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail!
|
||||||
tvBalance.setText(Helper.getFormattedAmount(amountXmr, true));
|
showBalance(Helper.getFormattedAmount(amountXmr, true));
|
||||||
hideExchanging();
|
hideExchanging();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +289,13 @@ public class WalletFragment extends Fragment
|
|||||||
public void onRefreshed(final Wallet wallet, final boolean full) {
|
public void onRefreshed(final Wallet wallet, final boolean full) {
|
||||||
Timber.d("onRefreshed(%b)", full);
|
Timber.d("onRefreshed(%b)", full);
|
||||||
if (full) {
|
if (full) {
|
||||||
List<TransactionInfo> list = wallet.getHistory().getAll();
|
List<TransactionInfo> list = new ArrayList<>();
|
||||||
|
final long streetHeight = activityCallback.getStreetModeHeight();
|
||||||
|
Timber.d("StreetHeight=%d", streetHeight);
|
||||||
|
for (TransactionInfo info : wallet.getHistory().getAll()) {
|
||||||
|
Timber.d("TxHeight=%d", info.blockheight);
|
||||||
|
if (info.isPending || (info.blockheight >= streetHeight)) list.add(info);
|
||||||
|
}
|
||||||
adapter.setInfos(list);
|
adapter.setInfos(list);
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
@@ -324,6 +360,7 @@ public class WalletFragment extends Fragment
|
|||||||
private String walletTitle = null;
|
private String walletTitle = null;
|
||||||
private String walletSubtitle = null;
|
private String walletSubtitle = null;
|
||||||
private long unlockedBalance = 0;
|
private long unlockedBalance = 0;
|
||||||
|
private long balance = 0;
|
||||||
|
|
||||||
private int accountIdx = -1;
|
private int accountIdx = -1;
|
||||||
|
|
||||||
@@ -334,23 +371,21 @@ public class WalletFragment extends Fragment
|
|||||||
accountIdx = wallet.getAccountIndex();
|
accountIdx = wallet.getAccountIndex();
|
||||||
setActivityTitle(wallet);
|
setActivityTitle(wallet);
|
||||||
}
|
}
|
||||||
long balance = wallet.getBalance();
|
balance = wallet.getBalance();
|
||||||
unlockedBalance = wallet.getUnlockedBalance();
|
unlockedBalance = wallet.getUnlockedBalance();
|
||||||
refreshBalance();
|
refreshBalance();
|
||||||
double amountXmr = Double.parseDouble(Helper.getDisplayAmount(balance - unlockedBalance)); // assume this cannot fail!
|
|
||||||
String unconfirmed = Helper.getFormattedAmount(amountXmr, true);
|
|
||||||
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed));
|
|
||||||
String sync = "";
|
String sync = "";
|
||||||
if (!activityCallback.hasBoundService())
|
if (!activityCallback.hasBoundService())
|
||||||
throw new IllegalStateException("WalletService not bound.");
|
throw new IllegalStateException("WalletService not bound.");
|
||||||
Wallet.ConnectionStatus daemonConnected = activityCallback.getConnectionStatus();
|
Wallet.ConnectionStatus daemonConnected = activityCallback.getConnectionStatus();
|
||||||
if (daemonConnected == Wallet.ConnectionStatus.ConnectionStatus_Connected) {
|
if (daemonConnected == Wallet.ConnectionStatus.ConnectionStatus_Connected) {
|
||||||
long daemonHeight = activityCallback.getDaemonHeight();
|
|
||||||
if (!wallet.isSynchronized()) {
|
if (!wallet.isSynchronized()) {
|
||||||
long n = daemonHeight - wallet.getBlockChainHeight();
|
long daemonHeight = activityCallback.getDaemonHeight();
|
||||||
|
long walletHeight = wallet.getBlockChainHeight();
|
||||||
|
long n = daemonHeight - walletHeight;
|
||||||
sync = getString(R.string.status_syncing) + " " + formatter.format(n) + " " + getString(R.string.status_remaining);
|
sync = getString(R.string.status_syncing) + " " + formatter.format(n) + " " + getString(R.string.status_remaining);
|
||||||
if (firstBlock == 0) {
|
if (firstBlock == 0) {
|
||||||
firstBlock = wallet.getBlockChainHeight();
|
firstBlock = walletHeight;
|
||||||
}
|
}
|
||||||
int x = 100 - Math.round(100f * n / (1f * daemonHeight - firstBlock));
|
int x = 100 - Math.round(100f * n / (1f * daemonHeight - firstBlock));
|
||||||
if (x == 0) x = 101; // indeterminate
|
if (x == 0) x = 101; // indeterminate
|
||||||
@@ -386,6 +421,10 @@ public class WalletFragment extends Fragment
|
|||||||
|
|
||||||
boolean isSynced();
|
boolean isSynced();
|
||||||
|
|
||||||
|
boolean isStreetMode();
|
||||||
|
|
||||||
|
long getStreetModeHeight();
|
||||||
|
|
||||||
boolean isWatchOnly();
|
boolean isWatchOnly();
|
||||||
|
|
||||||
String getTxKey(String txId);
|
String getTxKey(String txId);
|
||||||
@@ -394,6 +433,8 @@ public class WalletFragment extends Fragment
|
|||||||
|
|
||||||
boolean hasWallet();
|
boolean hasWallet();
|
||||||
|
|
||||||
|
Wallet getWallet();
|
||||||
|
|
||||||
void setToolbarButton(int type);
|
void setToolbarButton(int type);
|
||||||
|
|
||||||
void setTitle(String title, String subtitle);
|
void setTitle(String title, String subtitle);
|
||||||
|
@@ -21,6 +21,7 @@ import android.app.Application;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.model.NetworkType;
|
||||||
import com.m2049r.xmrwallet.util.LocaleHelper;
|
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
@@ -46,4 +47,17 @@ public class XmrWalletApplication extends Application {
|
|||||||
LocaleHelper.updateSystemDefaultLocale(configuration.locale);
|
LocaleHelper.updateSystemDefaultLocale(configuration.locale);
|
||||||
LocaleHelper.setLocale(XmrWalletApplication.this, LocaleHelper.getLocale(XmrWalletApplication.this));
|
LocaleHelper.setLocale(XmrWalletApplication.this, LocaleHelper.getLocale(XmrWalletApplication.this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public NetworkType getNetworkType() {
|
||||||
|
switch (BuildConfig.FLAVOR_net) {
|
||||||
|
case "mainnet":
|
||||||
|
return NetworkType.NetworkType_Mainnet;
|
||||||
|
case "stagenet":
|
||||||
|
return NetworkType.NetworkType_Stagenet;
|
||||||
|
case "testnet":
|
||||||
|
return NetworkType.NetworkType_Testnet;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("unknown net flavor " + BuildConfig.FLAVOR_net);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
333
app/src/main/java/com/m2049r/xmrwallet/data/Node.java
Normal file
333
app/src/main/java/com/m2049r/xmrwallet/data/Node.java
Normal file
File diff suppressed because it is too large
Load Diff
266
app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java
Normal file
266
app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.data;
|
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.model.NetworkType;
|
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
|
|
||||||
public class WalletNode {
|
|
||||||
private final String name;
|
|
||||||
private final String host;
|
|
||||||
private final int port;
|
|
||||||
private final String user;
|
|
||||||
private final String password;
|
|
||||||
private final NetworkType networkType;
|
|
||||||
|
|
||||||
public WalletNode(String walletName, String daemon, NetworkType networkType) {
|
|
||||||
if ((daemon == null) || daemon.isEmpty())
|
|
||||||
throw new IllegalArgumentException("daemon is empty");
|
|
||||||
this.name = walletName;
|
|
||||||
String daemonAddress;
|
|
||||||
String a[] = daemon.split("@");
|
|
||||||
if (a.length == 1) { // no credentials
|
|
||||||
daemonAddress = a[0];
|
|
||||||
user = "";
|
|
||||||
password = "";
|
|
||||||
} else if (a.length == 2) { // credentials
|
|
||||||
String userPassword[] = a[0].split(":");
|
|
||||||
if (userPassword.length != 2)
|
|
||||||
throw new IllegalArgumentException("User:Password invalid");
|
|
||||||
user = userPassword[0];
|
|
||||||
if (!user.isEmpty()) {
|
|
||||||
password = userPassword[1];
|
|
||||||
} else {
|
|
||||||
password = "";
|
|
||||||
}
|
|
||||||
daemonAddress = a[1];
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Too many @");
|
|
||||||
}
|
|
||||||
|
|
||||||
String da[] = daemonAddress.split(":");
|
|
||||||
if ((da.length > 2) || (da.length < 1))
|
|
||||||
throw new IllegalArgumentException("Too many ':' or too few");
|
|
||||||
host = da[0];
|
|
||||||
if (da.length == 2) {
|
|
||||||
try {
|
|
||||||
port = Integer.parseInt(da[1]);
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new IllegalArgumentException("Port not numeric");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (networkType) {
|
|
||||||
case NetworkType_Mainnet:
|
|
||||||
port = 18081;
|
|
||||||
break;
|
|
||||||
case NetworkType_Testnet:
|
|
||||||
port = 28081;
|
|
||||||
break;
|
|
||||||
case NetworkType_Stagenet:
|
|
||||||
port = 38081;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
port = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.networkType = networkType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkType getNetworkType() {
|
|
||||||
return networkType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return host + ":" + port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SocketAddress getSocketAddress() {
|
|
||||||
return new InetSocketAddress(host, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValid() {
|
|
||||||
return !host.isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
@@ -57,10 +57,10 @@ public class ProgressDialog extends AlertDialog {
|
|||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ledger_progress, null);
|
final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ledger_progress, null);
|
||||||
pbCircle = view.findViewById(R.id.pbCircle);
|
pbCircle = view.findViewById(R.id.pbCircle);
|
||||||
tvMessage = (TextView) view.findViewById(R.id.tvMessage);
|
tvMessage = view.findViewById(R.id.tvMessage);
|
||||||
rlProgressBar = view.findViewById(R.id.rlProgressBar);
|
rlProgressBar = view.findViewById(R.id.rlProgressBar);
|
||||||
pbBar = (ProgressBar) view.findViewById(R.id.pbBar);
|
pbBar = view.findViewById(R.id.pbBar);
|
||||||
tvProgress = (TextView) view.findViewById(R.id.tvProgress);
|
tvProgress = view.findViewById(R.id.tvProgress);
|
||||||
setView(view);
|
setView(view);
|
||||||
//setTitle("blabla");
|
//setTitle("blabla");
|
||||||
//super.setMessage("bubbu");
|
//super.setMessage("bubbu");
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user