mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-03 08:23:04 +02:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 156
|
||||
versionName "1.10.6 '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,9 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:name=".XmrWalletApplication"
|
||||
@@ -16,7 +17,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 +45,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"
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
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
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -85,22 +85,22 @@ 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);
|
||||
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);
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
@@ -178,6 +180,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 +263,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 +331,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 +360,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 +628,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 +1056,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 +1097,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 +1208,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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) {
|
||||
final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ledger_progress, null);
|
||||
pbCircle = view.findViewById(R.id.pbCircle);
|
||||
tvMessage = (TextView) view.findViewById(R.id.tvMessage);
|
||||
tvMessage = view.findViewById(R.id.tvMessage);
|
||||
rlProgressBar = view.findViewById(R.id.rlProgressBar);
|
||||
pbBar = (ProgressBar) view.findViewById(R.id.pbBar);
|
||||
tvProgress = (TextView) view.findViewById(R.id.tvProgress);
|
||||
pbBar = view.findViewById(R.id.pbBar);
|
||||
tvProgress = view.findViewById(R.id.tvProgress);
|
||||
setView(view);
|
||||
//setTitle("blabla");
|
||||
//super.setMessage("bubbu");
|
||||
|
@@ -106,10 +106,10 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
tvPaymentIdIntegrated = view.findViewById(R.id.tvPaymentIdIntegrated);
|
||||
llPaymentId = view.findViewById(R.id.llPaymentId);
|
||||
llXmrTo = view.findViewById(R.id.llXmrTo);
|
||||
tvXmrTo = (TextView) view.findViewById(R.id.tvXmrTo);
|
||||
tvXmrTo = view.findViewById(R.id.tvXmrTo);
|
||||
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto)));
|
||||
|
||||
etAddress = (TextInputLayout) view.findViewById(R.id.etAddress);
|
||||
etAddress = view.findViewById(R.id.etAddress);
|
||||
etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
@@ -168,7 +168,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
});
|
||||
|
||||
etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId);
|
||||
etPaymentId = view.findViewById(R.id.etPaymentId);
|
||||
etPaymentId.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etPaymentId.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
@@ -197,7 +197,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
});
|
||||
|
||||
bPaymentId = (Button) view.findViewById(R.id.bPaymentId);
|
||||
bPaymentId = view.findViewById(R.id.bPaymentId);
|
||||
bPaymentId.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -205,7 +205,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
});
|
||||
|
||||
etNotes = (TextInputLayout) view.findViewById(R.id.etNotes);
|
||||
etNotes = view.findViewById(R.id.etNotes);
|
||||
etNotes.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etNotes.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
@@ -219,7 +219,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
}
|
||||
});
|
||||
|
||||
cvScan = (CardView) view.findViewById(R.id.bScan);
|
||||
cvScan = view.findViewById(R.id.bScan);
|
||||
cvScan.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -228,7 +228,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
});
|
||||
|
||||
|
||||
etDummy = (EditText) view.findViewById(R.id.etDummy);
|
||||
etDummy = view.findViewById(R.id.etDummy);
|
||||
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etDummy.requestFocus();
|
||||
Helper.hideKeyboard(getActivity());
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user