mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-04 17:28:42 +02:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
beba0f497b | ||
![]() |
9d1827ff0d | ||
![]() |
c04b192753 | ||
![]() |
888b5edaec | ||
![]() |
5ee5a81926 | ||
![]() |
c4e361a873 | ||
![]() |
dba6cb057e | ||
![]() |
9e1167c5b9 | ||
![]() |
12546a1ade | ||
![]() |
e9313bc235 | ||
![]() |
7e9bf84640 | ||
![]() |
94f87a5193 | ||
![]() |
8a8fc5ec9e | ||
![]() |
036d4ebf6c | ||
![]() |
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 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -6,4 +6,11 @@
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.DS_Store
|
||||
/app/build
|
||||
/app/release
|
||||
/app/alpha
|
||||
/app/prod
|
||||
/app/alphaMainnet
|
||||
/app/prodMainnet
|
||||
/app/alphaStagenet
|
||||
/app/prodStagenet
|
||||
|
5
app/.gitignore
vendored
5
app/.gitignore
vendored
@@ -1,5 +0,0 @@
|
||||
.externalNativeBuild
|
||||
build
|
||||
app.iml
|
||||
prod
|
||||
alpha
|
@@ -1,14 +1,14 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
defaultConfig {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 27
|
||||
versionCode 140
|
||||
versionName "1.9.0 'We Comin' Rougher'"
|
||||
targetSdkVersion 28
|
||||
versionCode 163
|
||||
versionName "1.10.13 'Node-O-matiC'"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
@@ -19,13 +19,23 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions "version"
|
||||
flavorDimensions 'type', 'net'
|
||||
productFlavors {
|
||||
mainnet {
|
||||
dimension 'net'
|
||||
}
|
||||
stagenet {
|
||||
dimension 'net'
|
||||
applicationIdSuffix '.stage'
|
||||
versionNameSuffix ' (stage)'
|
||||
}
|
||||
alpha {
|
||||
applicationIdSuffix ".alpha"
|
||||
versionNameSuffix " (alpha)"
|
||||
dimension 'type'
|
||||
applicationIdSuffix '.alpha'
|
||||
versionNameSuffix ' (alpha)'
|
||||
}
|
||||
prod {
|
||||
dimension 'type'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,9 +101,11 @@ dependencies {
|
||||
implementation "com.android.support:support-v4:$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:swiperefreshlayout:$rootProject.ext.supportVersion"
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
|
||||
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.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.WAKE_LOCK" />
|
||||
<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.NFC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:name=".XmrWalletApplication"
|
||||
@@ -16,7 +18,8 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/MyMaterialTheme">
|
||||
android:theme="@style/MyMaterialTheme"
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
<activity
|
||||
android:name=".WalletActivity"
|
||||
@@ -43,7 +46,7 @@
|
||||
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||
android:resource="@xml/usb_device_filter" />
|
||||
</activity>
|
||||
|
||||
|
||||
<service
|
||||
android:name=".service.WalletService"
|
||||
android:description="@string/service_description"
|
||||
|
@@ -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;
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_WalletManager_queryWalletDeviceJ(JNIEnv *env, jobject instance,
|
||||
jstring keys_file_name,
|
||||
jstring password) {
|
||||
jstring keys_file_name,
|
||||
jstring password) {
|
||||
const char *_keys_file_name = env->GetStringUTFChars(keys_file_name, NULL);
|
||||
const char *_password = env->GetStringUTFChars(password, NULL);
|
||||
Bitmonero::Wallet::Device device_type;
|
||||
@@ -1185,10 +1185,11 @@ jobject newTransferList(JNIEnv *env, Bitmonero::TransactionInfo *info) {
|
||||
|
||||
jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) {
|
||||
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);
|
||||
jstring _hash = env->NewStringUTF(info->hash().c_str());
|
||||
jstring _paymentId = env->NewStringUTF(info->paymentId().c_str());
|
||||
jstring _label = env->NewStringUTF(info->label().c_str());
|
||||
uint32_t subaddrIndex = 0;
|
||||
if (info->direction() == Bitmonero::TransactionInfo::Direction_In)
|
||||
subaddrIndex = *(info->subaddrIndex().begin());
|
||||
@@ -1205,6 +1206,7 @@ jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) {
|
||||
info->subaddrAccount(),
|
||||
subaddrIndex,
|
||||
info->confirmations(),
|
||||
_label,
|
||||
transfers);
|
||||
env->DeleteLocalRef(transfers);
|
||||
env->DeleteLocalRef(_hash);
|
||||
@@ -1397,7 +1399,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jclass cl
|
||||
*
|
||||
* @return length of received data in response or -1 if error
|
||||
*/
|
||||
int LedgerExchange(
|
||||
int LedgerExchange(
|
||||
unsigned char *command,
|
||||
unsigned int cmd_len,
|
||||
unsigned char *response,
|
||||
|
@@ -23,9 +23,9 @@ package com.btchip.comm;
|
||||
import com.btchip.BTChipException;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
196
app/src/main/java/com/m2049r/levin/scanner/Dispatcher.java
Normal file
196
app/src/main/java/com/m2049r/levin/scanner/Dispatcher.java
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.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 (LevinPeer levinPeer : peer.getPeers()) {
|
||||
if (getMorePeers())
|
||||
retrievePeer(new NodeInfo(levinPeer));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
23
app/src/main/java/com/m2049r/levin/scanner/LevinPeer.java
Normal file
23
app/src/main/java/com/m2049r/levin/scanner/LevinPeer.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package com.m2049r.levin.scanner;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class LevinPeer {
|
||||
final public InetSocketAddress socketAddress;
|
||||
final public int version;
|
||||
final public long height;
|
||||
final public String top;
|
||||
|
||||
|
||||
public InetSocketAddress getSocketAddress() {
|
||||
return socketAddress;
|
||||
}
|
||||
|
||||
LevinPeer(InetAddress address, int port, int version, long height, String top) {
|
||||
this.socketAddress = new InetSocketAddress(address, port);
|
||||
this.version = version;
|
||||
this.height = height;
|
||||
this.top = top;
|
||||
}
|
||||
}
|
231
app/src/main/java/com/m2049r/levin/scanner/PeerRetriever.java
Normal file
231
app/src/main/java/com/m2049r/levin/scanner/PeerRetriever.java
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* 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.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<LevinPeer> 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<LevinPeer> 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) {
|
||||
Section data = (Section) section.get("payload_data");
|
||||
int topVersion = (Integer) data.get("top_version");
|
||||
long currentHeight = (Long) data.get("current_height");
|
||||
String topId = HexHelper.bytesToHex((byte[]) data.get("top_id"));
|
||||
Timber.d("PAYLOAD_DATA %d/%d/%s", topVersion, currentHeight, topId);
|
||||
|
||||
@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");
|
||||
Integer type = (Integer) 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;
|
||||
Integer sport = (Integer) 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 LevinPeer(inet, port, topVersion, currentHeight, topId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
184
app/src/main/java/com/m2049r/levin/util/LevinReader.java
Normal file
184
app/src/main/java/com/m2049r/levin/util/LevinReader.java
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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:
|
||||
return in.readUnsignedShort();
|
||||
case Section.SERIALIZE_TYPE_INT16:
|
||||
return in.readShort();
|
||||
case Section.SERIALIZE_TYPE_UINT8:
|
||||
return in.readUnsignedByte();
|
||||
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
|
||||
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
|
||||
progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
|
||||
if (delay > 0) {
|
||||
if (delayMillis > 0) {
|
||||
Handler handler = new Handler();
|
||||
handler.postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
if (progressDialog != null) progressDialog.show();
|
||||
}
|
||||
}, delay);
|
||||
}, delayMillis);
|
||||
} else {
|
||||
progressDialog.show();
|
||||
}
|
||||
|
@@ -85,15 +85,15 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
View view = inflater.inflate(R.layout.fragment_generate, container, false);
|
||||
|
||||
etWalletName = (TextInputLayout) view.findViewById(R.id.etWalletName);
|
||||
etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword);
|
||||
llFingerprintAuth = (LinearLayout) view.findViewById(R.id.llFingerprintAuth);
|
||||
etWalletMnemonic = (TextInputLayout) view.findViewById(R.id.etWalletMnemonic);
|
||||
etWalletAddress = (TextInputLayout) view.findViewById(R.id.etWalletAddress);
|
||||
etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey);
|
||||
etWalletSpendKey = (TextInputLayout) view.findViewById(R.id.etWalletSpendKey);
|
||||
etWalletRestoreHeight = (TextInputLayout) view.findViewById(R.id.etWalletRestoreHeight);
|
||||
bGenerate = (Button) view.findViewById(R.id.bGenerate);
|
||||
etWalletName = view.findViewById(R.id.etWalletName);
|
||||
etWalletPassword = view.findViewById(R.id.etWalletPassword);
|
||||
llFingerprintAuth = view.findViewById(R.id.llFingerprintAuth);
|
||||
etWalletMnemonic = view.findViewById(R.id.etWalletMnemonic);
|
||||
etWalletAddress = view.findViewById(R.id.etWalletAddress);
|
||||
etWalletViewKey = view.findViewById(R.id.etWalletViewKey);
|
||||
etWalletSpendKey = view.findViewById(R.id.etWalletSpendKey);
|
||||
etWalletRestoreHeight = view.findViewById(R.id.etWalletRestoreHeight);
|
||||
bGenerate = view.findViewById(R.id.bGenerate);
|
||||
|
||||
etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
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);
|
||||
|
||||
scrollview = (ScrollView) view.findViewById(R.id.scrollview);
|
||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
||||
tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword);
|
||||
tvWalletAddress = (TextView) view.findViewById(R.id.tvWalletAddress);
|
||||
tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey);
|
||||
tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey);
|
||||
tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic);
|
||||
bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
|
||||
bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
|
||||
llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
|
||||
llPassword = (LinearLayout) view.findViewById(R.id.llPassword);
|
||||
llMnemonic = (LinearLayout) view.findViewById(R.id.llMnemonic);
|
||||
llSpendKey = (LinearLayout) view.findViewById(R.id.llSpendKey);
|
||||
llViewKey = (LinearLayout) view.findViewById(R.id.llViewKey);
|
||||
scrollview = view.findViewById(R.id.scrollview);
|
||||
pbProgress = view.findViewById(R.id.pbProgress);
|
||||
tvWalletPassword = view.findViewById(R.id.tvWalletPassword);
|
||||
tvWalletAddress = view.findViewById(R.id.tvWalletAddress);
|
||||
tvWalletViewKey = view.findViewById(R.id.tvWalletViewKey);
|
||||
tvWalletSpendKey = view.findViewById(R.id.tvWalletSpendKey);
|
||||
tvWalletMnemonic = view.findViewById(R.id.tvWalletMnemonic);
|
||||
bCopyAddress = view.findViewById(R.id.bCopyAddress);
|
||||
bAdvancedInfo = view.findViewById(R.id.bAdvancedInfo);
|
||||
llAdvancedInfo = view.findViewById(R.id.llAdvancedInfo);
|
||||
llPassword = view.findViewById(R.id.llPassword);
|
||||
llMnemonic = view.findViewById(R.id.llMnemonic);
|
||||
llSpendKey = view.findViewById(R.id.llSpendKey);
|
||||
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;
|
||||
tvWalletMnemonic.setTextIsSelectable(allowCopy);
|
||||
@@ -481,13 +481,13 @@ public class GenerateReviewFragment extends Fragment {
|
||||
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
|
||||
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));
|
||||
|
||||
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));
|
||||
|
||||
LinearLayout llFingerprintAuth = (LinearLayout) promptsView.findViewById(R.id.llFingerprintAuth);
|
||||
LinearLayout llFingerprintAuth = promptsView.findViewById(R.id.llFingerprintAuth);
|
||||
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0);
|
||||
if (FingerprintHelper.isDeviceSupported(getActivity())) {
|
||||
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);
|
||||
|
||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
||||
tvAddressLabel = (TextView) view.findViewById(R.id.tvAddressLabel);
|
||||
tvAddress = (TextView) view.findViewById(R.id.tvAddress);
|
||||
etNotes = (TextInputLayout) view.findViewById(R.id.etNotes);
|
||||
evAmount = (ExchangeView) view.findViewById(R.id.evAmount);
|
||||
qrCode = (ImageView) view.findViewById(R.id.qrCode);
|
||||
tvQrCode = (TextView) view.findViewById(R.id.tvQrCode);
|
||||
qrCodeFull = (ImageView) view.findViewById(R.id.qrCodeFull);
|
||||
etDummy = (EditText) view.findViewById(R.id.etDummy);
|
||||
bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
|
||||
bSubaddress = (Button) view.findViewById(R.id.bSubaddress);
|
||||
pbProgress = view.findViewById(R.id.pbProgress);
|
||||
tvAddressLabel = view.findViewById(R.id.tvAddressLabel);
|
||||
tvAddress = view.findViewById(R.id.tvAddress);
|
||||
etNotes = view.findViewById(R.id.etNotes);
|
||||
evAmount = view.findViewById(R.id.evAmount);
|
||||
qrCode = view.findViewById(R.id.qrCode);
|
||||
tvQrCode = view.findViewById(R.id.tvQrCode);
|
||||
qrCodeFull = view.findViewById(R.id.qrCodeFull);
|
||||
etDummy = view.findViewById(R.id.etDummy);
|
||||
bCopyAddress = view.findViewById(R.id.bCopyAddress);
|
||||
bSubaddress = view.findViewById(R.id.bSubaddress);
|
||||
|
||||
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);
|
||||
Canvas canvas = new Canvas(logoBitmap);
|
||||
canvas.drawBitmap(qrBitmap, 0, 0, null);
|
||||
canvas.save(Canvas.ALL_SAVE_FLAG);
|
||||
canvas.save();
|
||||
// figure out how to scale the logo
|
||||
float scaleSize = 1.0f;
|
||||
while ((logoWidth / scaleSize) > (qrWidth / 5) || (logoHeight / scaleSize) > (qrHeight / 5)) {
|
||||
@@ -482,7 +482,6 @@ public class ReceiveFragment extends Fragment {
|
||||
if (context instanceof GenerateReviewFragment.ProgressListener) {
|
||||
this.progressCallback = (GenerateReviewFragment.ProgressListener) context;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -31,7 +31,7 @@ public abstract class SecureActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@@ -60,6 +60,7 @@ public class TxFragment extends Fragment {
|
||||
}
|
||||
|
||||
private TextView tvAccount;
|
||||
private TextView tvAddress;
|
||||
private TextView tvTxTimestamp;
|
||||
private TextView tvTxId;
|
||||
private TextView tvTxKey;
|
||||
@@ -85,22 +86,23 @@ public class TxFragment extends Fragment {
|
||||
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
|
||||
|
||||
cvXmrTo = view.findViewById(R.id.cvXmrTo);
|
||||
tvTxXmrToKey = (TextView) view.findViewById(R.id.tvTxXmrToKey);
|
||||
tvDestinationBtc = (TextView) view.findViewById(R.id.tvDestinationBtc);
|
||||
tvTxAmountBtc = (TextView) view.findViewById(R.id.tvTxAmountBtc);
|
||||
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
|
||||
tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
|
||||
tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc);
|
||||
|
||||
tvAccount = (TextView) view.findViewById(R.id.tvAccount);
|
||||
tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
|
||||
tvTxId = (TextView) view.findViewById(R.id.tvTxId);
|
||||
tvTxKey = (TextView) view.findViewById(R.id.tvTxKey);
|
||||
tvDestination = (TextView) view.findViewById(R.id.tvDestination);
|
||||
tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId);
|
||||
tvTxBlockheight = (TextView) view.findViewById(R.id.tvTxBlockheight);
|
||||
tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount);
|
||||
tvTxFee = (TextView) view.findViewById(R.id.tvTxFee);
|
||||
tvTxTransfers = (TextView) view.findViewById(R.id.tvTxTransfers);
|
||||
etTxNotes = (TextView) view.findViewById(R.id.etTxNotes);
|
||||
bTxNotes = (Button) view.findViewById(R.id.bTxNotes);
|
||||
tvAccount = view.findViewById(R.id.tvAccount);
|
||||
tvAddress = view.findViewById(R.id.tvAddress);
|
||||
tvTxTimestamp = view.findViewById(R.id.tvTxTimestamp);
|
||||
tvTxId = view.findViewById(R.id.tvTxId);
|
||||
tvTxKey = view.findViewById(R.id.tvTxKey);
|
||||
tvDestination = view.findViewById(R.id.tvDestination);
|
||||
tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
|
||||
tvTxBlockheight = view.findViewById(R.id.tvTxBlockheight);
|
||||
tvTxAmount = view.findViewById(R.id.tvTxAmount);
|
||||
tvTxFee = view.findViewById(R.id.tvTxFee);
|
||||
tvTxTransfers = view.findViewById(R.id.tvTxTransfers);
|
||||
etTxNotes = view.findViewById(R.id.etTxNotes);
|
||||
bTxNotes = view.findViewById(R.id.bTxNotes);
|
||||
|
||||
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
|
||||
@@ -219,12 +221,16 @@ public class TxFragment extends Fragment {
|
||||
if (info.txKey == null) {
|
||||
info.txKey = activityCallback.getTxKey(info.hash);
|
||||
}
|
||||
if (info.address == null) {
|
||||
info.address = activityCallback.getTxAddress(info.account, info.subaddress);
|
||||
}
|
||||
loadNotes(info);
|
||||
|
||||
activityCallback.setSubtitle(getString(R.string.tx_title));
|
||||
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||
|
||||
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)));
|
||||
tvTxId.setText(info.hash);
|
||||
@@ -331,6 +337,8 @@ public class TxFragment extends Fragment {
|
||||
|
||||
String getTxNotes(String hash);
|
||||
|
||||
String getTxAddress(int major, int minor);
|
||||
|
||||
void onSetNote(String txId, String notes);
|
||||
|
||||
void setToolbarButton(int type);
|
||||
|
@@ -80,6 +80,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
public static final String REQUEST_ID = "id";
|
||||
public static final String REQUEST_PW = "pw";
|
||||
public static final String REQUEST_FINGERPRINT_USED = "fingerprint";
|
||||
public static final String REQUEST_STREETMODE = "streetmode";
|
||||
|
||||
private NavigationView accountsView;
|
||||
private DrawerLayout drawer;
|
||||
@@ -87,6 +88,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
|
||||
private Toolbar toolbar;
|
||||
private boolean needVerifyIdentity;
|
||||
private boolean requestStreetMode = false;
|
||||
|
||||
private String password;
|
||||
|
||||
@@ -135,13 +137,13 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
return streetMode > 0;
|
||||
}
|
||||
|
||||
public void toggleStreetMode() {
|
||||
if (streetMode == 0) {
|
||||
private void enableStreetMode(boolean enable) {
|
||||
if (enable) {
|
||||
needVerifyIdentity = true;
|
||||
streetMode = getWallet().getDaemonBlockChainHeight();
|
||||
} else {
|
||||
streetMode = 0;
|
||||
}
|
||||
Timber.e("streetMode=" + streetMode);
|
||||
updateAccountsBalance();
|
||||
forceUpdate();
|
||||
}
|
||||
@@ -166,6 +168,11 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
return getWallet().getUserNote(txId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTxAddress(int major, int minor) {
|
||||
return getWallet().getSubaddress(major, minor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
@@ -178,6 +185,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
acquireWakeLock();
|
||||
String walletId = extras.getString(REQUEST_ID);
|
||||
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);
|
||||
connectWalletService(walletId, password);
|
||||
} else {
|
||||
@@ -259,19 +268,48 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
onAccountRename();
|
||||
return true;
|
||||
case R.id.action_streetmode:
|
||||
toggleStreetMode();
|
||||
if (isStreetMode()) {
|
||||
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_streetmode);
|
||||
if (isStreetMode()) { // disable streetmode
|
||||
onDisableStreetMode();
|
||||
} else {
|
||||
showNet();
|
||||
onEnableStreetMode();
|
||||
}
|
||||
invalidateOptionsMenu();
|
||||
return true;
|
||||
default:
|
||||
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() {
|
||||
try {
|
||||
GenerateReviewFragment detailsFragment = (GenerateReviewFragment)
|
||||
@@ -298,7 +336,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_wallet);
|
||||
toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
|
||||
@@ -327,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);
|
||||
drawer.addDrawerListener(drawerToggle);
|
||||
drawerToggle.syncState();
|
||||
setDrawerEnabled(false); // disable until synced
|
||||
|
||||
accountsView = (NavigationView) findViewById(R.id.accounts_nav);
|
||||
accountsView = findViewById(R.id.accounts_nav);
|
||||
accountsView.setNavigationItemSelectedListener(this);
|
||||
|
||||
showNet();
|
||||
@@ -595,6 +633,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
haveWallet = true;
|
||||
invalidateOptionsMenu();
|
||||
|
||||
enableStreetMode(requestStreetMode);
|
||||
|
||||
final WalletFragment walletFragment = (WalletFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
runOnUiThread(new Runnable() {
|
||||
@@ -1021,7 +1061,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
|
||||
void updateAccountsHeader() {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1062,8 +1102,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
|
||||
alertDialogBuilder.setView(promptsView);
|
||||
|
||||
final EditText etRename = (EditText) promptsView.findViewById(R.id.etRename);
|
||||
final TextView tvRenameLabel = (TextView) promptsView.findViewById(R.id.tvRenameLabel);
|
||||
final EditText etRename = promptsView.findViewById(R.id.etRename);
|
||||
final TextView tvRenameLabel = promptsView.findViewById(R.id.tvRenameLabel);
|
||||
final Wallet wallet = getWallet();
|
||||
tvRenameLabel.setText(getString(R.string.prompt_rename, wallet.getAccountLabel()));
|
||||
|
||||
@@ -1173,16 +1213,4 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public void invalidateOptionsMenu() {
|
||||
// super.invalidateOptionsMenu();
|
||||
// if (isStreetMode()) {
|
||||
// item.setIcon(R.drawable.gunther_csi_24dp);
|
||||
// toolbar.setBackgroundResource(R.drawable.backgound_toolbar_streetmode);
|
||||
// } else {
|
||||
// item.setIcon(R.drawable.gunther_24dp);
|
||||
// showNet();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@@ -379,12 +379,13 @@ public class WalletFragment extends Fragment
|
||||
throw new IllegalStateException("WalletService not bound.");
|
||||
Wallet.ConnectionStatus daemonConnected = activityCallback.getConnectionStatus();
|
||||
if (daemonConnected == Wallet.ConnectionStatus.ConnectionStatus_Connected) {
|
||||
long daemonHeight = activityCallback.getDaemonHeight();
|
||||
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);
|
||||
if (firstBlock == 0) {
|
||||
firstBlock = wallet.getBlockChainHeight();
|
||||
firstBlock = walletHeight;
|
||||
}
|
||||
int x = 100 - Math.round(100f * n / (1f * daemonHeight - firstBlock));
|
||||
if (x == 0) x = 101; // indeterminate
|
||||
|
@@ -21,6 +21,7 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
|
||||
import com.m2049r.xmrwallet.model.NetworkType;
|
||||
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||
|
||||
import timber.log.Timber;
|
||||
@@ -46,4 +47,17 @@ public class XmrWalletApplication extends Application {
|
||||
LocaleHelper.updateSystemDefaultLocale(configuration.locale);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -38,6 +38,7 @@ public class BarcodeData {
|
||||
public static final String OA_BTC_ASSET = "btc";
|
||||
|
||||
static final String BTC_SCHEME = "bitcoin:";
|
||||
static final String BTC_DESCRIPTION = "message";
|
||||
static final String BTC_AMOUNT = "amount";
|
||||
|
||||
public enum Asset {
|
||||
@@ -50,28 +51,32 @@ public class BarcodeData {
|
||||
OA_DNSSEC
|
||||
}
|
||||
|
||||
public Asset asset = null;
|
||||
public String addressName = null;
|
||||
public String address = null;
|
||||
public String paymentId = null;
|
||||
public String amount = null;
|
||||
public String description = null;
|
||||
public Security security = Security.NORMAL;
|
||||
|
||||
public BarcodeData(String uri) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
}
|
||||
final public Asset asset;
|
||||
final public String address;
|
||||
final public String addressName;
|
||||
final public String paymentId;
|
||||
final public String amount;
|
||||
final public String description;
|
||||
final public Security security;
|
||||
|
||||
public BarcodeData(Asset asset, String address) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
amount = null;
|
||||
paymentId = null;
|
||||
addressName = null;
|
||||
description = null;
|
||||
this.security = Security.NORMAL;
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String amount) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
this.amount = amount;
|
||||
paymentId = null;
|
||||
addressName = null;
|
||||
description = null;
|
||||
this.security = Security.NORMAL;
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String paymentId, String amount) {
|
||||
@@ -79,6 +84,9 @@ public class BarcodeData {
|
||||
this.address = address;
|
||||
this.paymentId = paymentId;
|
||||
this.amount = amount;
|
||||
addressName = null;
|
||||
description = null;
|
||||
this.security = Security.NORMAL;
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String paymentId, String description, String amount) {
|
||||
@@ -87,14 +95,18 @@ public class BarcodeData {
|
||||
this.paymentId = paymentId;
|
||||
this.description = description;
|
||||
this.amount = amount;
|
||||
addressName = null;
|
||||
this.security = Security.NORMAL;
|
||||
}
|
||||
|
||||
public void setAddressName(String name) {
|
||||
addressName = name;
|
||||
}
|
||||
|
||||
public void setSecurity(Security security) {
|
||||
this.security = security;
|
||||
public BarcodeData(Asset asset, String address, String addressName, String paymentId, String description, String amount, Security sec) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
this.addressName = addressName;
|
||||
this.paymentId = paymentId;
|
||||
this.description = description;
|
||||
this.amount = amount;
|
||||
this.security = sec;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
@@ -140,7 +152,7 @@ public class BarcodeData {
|
||||
}
|
||||
// check for OpenAlias
|
||||
if (bcData == null) {
|
||||
bcData = parseOpenAlias(qrCode);
|
||||
bcData = parseOpenAlias(qrCode, false);
|
||||
}
|
||||
return bcData;
|
||||
}
|
||||
@@ -175,7 +187,11 @@ public class BarcodeData {
|
||||
}
|
||||
}
|
||||
String address = monero.getPath();
|
||||
|
||||
String paymentId = parms.get(XMR_PAYMENTID);
|
||||
// deal with empty payment_id created by non-spec-conforming apps
|
||||
if ((paymentId != null) && paymentId.isEmpty()) paymentId = null;
|
||||
|
||||
String description = parms.get(XMR_DESCRIPTION);
|
||||
String amount = parms.get(XMR_AMOUNT);
|
||||
if (amount != null) {
|
||||
@@ -235,6 +251,7 @@ public class BarcodeData {
|
||||
}
|
||||
}
|
||||
String address = bitcoin.getPath();
|
||||
String description = parms.get(BTC_DESCRIPTION);
|
||||
String amount = parms.get(BTC_AMOUNT);
|
||||
if (amount != null) {
|
||||
try {
|
||||
@@ -248,7 +265,7 @@ public class BarcodeData {
|
||||
Timber.d("address invalid");
|
||||
return null;
|
||||
}
|
||||
return new BarcodeData(BarcodeData.Asset.BTC, address, amount);
|
||||
return new BarcodeData(BarcodeData.Asset.BTC, address, null, description, amount);
|
||||
}
|
||||
|
||||
static public BarcodeData parseBitcoinNaked(String address) {
|
||||
@@ -264,7 +281,7 @@ public class BarcodeData {
|
||||
return new BarcodeData(BarcodeData.Asset.BTC, address);
|
||||
}
|
||||
|
||||
static public BarcodeData parseOpenAlias(String oaString) {
|
||||
static public BarcodeData parseOpenAlias(String oaString, boolean dnssec) {
|
||||
Timber.d("parseOpenAlias=%s", oaString);
|
||||
if (oaString == null) return null;
|
||||
|
||||
@@ -316,8 +333,8 @@ public class BarcodeData {
|
||||
return null;
|
||||
}
|
||||
|
||||
BarcodeData bc = new BarcodeData(asset, address, paymentId, description, amount);
|
||||
bc.setAddressName(addressName);
|
||||
return bc;
|
||||
Security sec = dnssec ? BarcodeData.Security.OA_DNSSEC : BarcodeData.Security.OA_NO_DNSSEC;
|
||||
|
||||
return new BarcodeData(asset, address, addressName, paymentId, description, amount, sec);
|
||||
}
|
||||
}
|
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
264
app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java
Normal file
264
app/src/main/java/com/m2049r/xmrwallet/data/NodeInfo.java
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user