1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-05 09:58:42 +02:00

Compare commits

..

74 Commits

Author SHA1 Message Date
m2049r
7cc2f6fafb bump version v1.11.6 2019-05-19 21:34:26 +02:00
m2049r
9a3ee0eda8 fix update of account balances (#590) 2019-05-19 20:27:35 +02:00
m2049r
6e898939a3 show account balances (#589)
and refactor displayAmount
2019-05-19 17:33:19 +02:00
m2049r
40ae39d647 update studio 2019-05-19 10:28:18 +02:00
jindouyunz
ca81e652e5 Update strings.xml_Chinese (#575)
* Update strings.xml

updated on 4/22/2019 version 2, Jindouyunz.

* Update string.xml_Chinese 2019-5-12

Hi, I made several pieces of modification based on lafudoci's review, thanks.

* Update string.xml-zh-rCN 2019-5-13

removed all lines with "translatable=false", and currency array
2019-05-18 08:40:31 +02:00
jindouyunz
796048be4e Update about.xml (#574)
updated on 4/21/2019, Jindouyunz.
2019-05-18 08:39:49 +02:00
jindouyunz
441bf995c8 Update help.xml_Chinese (#573)
* Update help.xml

updated on 4/21/2019, version 2, Jindouyunz

* Update help.xml 2019-5-11

Hi, I add changes on line 200, based on lafudoci's review, thanks.
2019-05-18 08:39:28 +02:00
m2049r
e8860ab8eb focus on password if no fingerprint (#587) 2019-05-12 00:17:21 +02:00
m2049r
525b38ff53 remove save button for notes (#586) 2019-05-12 00:17:07 +02:00
m2049r
ba79bf87aa Update rpc checks (#585)
* check rpc version

* update protocol version check
2019-05-11 21:15:35 +02:00
m2049r
3fe6571e7d update restore heights for 4 & 5.2019 (#583) 2019-05-11 11:41:16 +02:00
erciccione
364e6a8137 update values-it/strings.xml (Italian translation) (#580) 2019-05-11 11:40:20 +02:00
vp11
cb69ce99d6 update pt-BR translation (#581)
* update pt-BR translation

* missing block
2019-05-11 11:40:01 +02:00
m2049r
1f976872fc increase sanity check size limit (#576) 2019-04-23 20:52:43 +02:00
kic0
27f266b6f7 PT-PT translations updated (#569) 2019-04-20 11:03:57 +02:00
m2049r
168928d54a Update studio (#572)
* gradle 5.1

* remove ref to @id/ibBookmark
2019-04-19 15:51:25 +02:00
Leza89
b3f61072aa V1.11.4 - german translations (#556)
* Corrections on the translation without translating

* Added missing commas, hyphens
* replaced diverging terminology with Monero GUI (i.e. Remote Node → Drittanbieter-Node, Saldo → Guthaben, Daemon/Dienst → Hintergrunddienst)
* grammatical adjustments
* unifying the gender of certain words (i.e. "die" Wallet)

* Translations - Draft for peer review

* Corrections according to rodolfo912's review

en-dash as a symbol vs. unicode ;#8211; is yet to be resolved

* Reverting case of "Wallet" from "die" to "das"

As requested by m2049r the article of "Wallet" was reverted back from "die Wallet" to "das Wallet" and corrected in places where it was inconsistent before.
2019-04-19 14:57:49 +02:00
Jack Rogers Lewis
ccb64aded0 SK update help.xml (#568) 2019-04-19 14:49:25 +02:00
Jack Rogers Lewis
e98fa089f2 update new strings (#567)
podadresa = subadresa
2019-04-19 14:48:47 +02:00
Scott Anecito
884878b7a7 Update Japanese localization, fix English typo (#564) 2019-04-19 14:43:56 +02:00
v1docq47
4e23f0ef3a Update for Russian translation #444 (#558)
* Update for Russian translation #444

* fix

added translation of missing lines (help.xml / strings.xml)
2019-03-19 20:12:37 +01:00
0140454
6ea4e3d998 Update zh-rTW translation (#557)
* Update zh-rTW translation

* Update translation for "Using a payment link"
2019-03-19 20:12:16 +01:00
m2049r
971c90f35b Merge pull request #559 from m2049r/feature_enable_ledger
Feature enable ledger
2019-03-19 20:06:17 +01:00
m2049r
f0523c403c bump version 2019-03-19 19:34:04 +01:00
m2049r
966ed23b87 enable ledger again 2019-03-19 19:32:38 +01:00
m2049r
95f2ca74a6 clean code (#555) 2019-03-10 22:00:57 +01:00
m2049r
81d94478f2 update wallet api (#554) 2019-03-10 21:49:57 +01:00
el00ruobuob
16ff779ebc New strings & help update to FR (#552) 2019-03-09 21:58:10 +01:00
m2049r
6b7bb164f4 v1.11.3 (#550) 2019-03-09 00:07:37 +01:00
m2049r
da1d4ea1bf convert ledger seed dialog (#549) 2019-03-09 00:02:58 +01:00
m2049r
d5a967f690 upgrade gradle version (#548) 2019-03-06 16:46:52 +01:00
m2049r
de8de02f9f disable ledger support (#547) 2019-03-05 23:35:11 +01:00
m2049r
06456e33e4 v1.11.1 (#546)
for use with b087cbf995
2019-03-05 21:59:19 +01:00
m2049r
d97b36aa44 v1.11 (#545) 2019-03-02 12:26:36 +01:00
m2049r
8fa06e5b37 2019-03 restore height (#544) 2019-03-02 09:44:40 +01:00
m2049r
8d95de828b prevent screenshots on dialogs (#543) 2019-03-02 09:31:06 +01:00
m2049r
93a7be0452 update slow_hash signature (#542) 2019-03-02 09:07:27 +01:00
m2049r
25c8ec1229 fix reading rest (#541) 2019-02-28 22:07:22 +01:00
m2049r
0bf0444dce seed is a password (#540) 2019-02-28 18:43:32 +01:00
m2049r
b74f9c6bd7 upgrade monero core (#539) 2019-02-28 18:33:16 +01:00
m2049r
f843bb1685 share: multiple send & save only when fired (#537) 2019-02-25 21:55:57 +01:00
m2049r
7d9d49c29e landscape mode on tablets (#535) 2019-02-18 19:05:45 +01:00
m2049r
4ca9328949 bump version (#534) 2019-02-17 20:23:00 +01:00
m2049r
08b5a87f19 get rid of keystore exception on first call (#533) 2019-02-14 23:45:01 +01:00
m2049r
445d8acc38 open on monero: & bitcoin: uri (#532) 2019-02-14 23:34:38 +01:00
m2049r
9385ac8c31 update gradle version 2019-02-11 18:50:43 +01:00
Keksoj
4c7ebd8402 translated 'help', 'strings' and 'about' to esperanto (#531) 2019-02-11 07:41:50 +01:00
m2049r
67f3c5f948 bump version 2019-02-04 19:41:28 +01:00
m2049r
cd67a7e2bf 2019-02 height 2019-02-04 19:41:28 +01:00
0140454
fa5fe313ea Update zh-rTW translation (#529) 2019-02-03 07:54:41 +01:00
jaro Lee
ed4957a3cc sk strings update (#523) 2019-01-29 22:33:09 +01:00
jaro Lee
3e0eeebd51 sk help.xml update (#527)
sk-translation - help_send update
2019-01-29 22:32:40 +01:00
m2049r
0d213a1eb4 hide sweep amount in street mode (#526) 2019-01-28 01:16:00 +01:00
m2049r
39d048fd5e Feature bitpay (#525)
* support bitcoin payment protocol (BIP70/72)

* prep translations
2019-01-27 21:32:06 +01:00
m2049r
1a5d2d0399 Update Android Studio v3.3 (#522)
* Update Android Studio v3.3

* also update circleci script to accept licenses
2019-01-21 17:38:59 +01:00
m2049r
028057a672 bump version (#519) 2019-01-14 12:52:28 +01:00
TheFuzzStone
909ff8ca5e Ukranian translation for Monerujo (#517)
* Ukranian translation

* Update for Russian language (fixed small typos)
2019-01-12 19:56:59 +01:00
m2049r
ffd61e4495 remove getErrorString method (#516) 2019-01-11 19:24:33 +01:00
m2049r
003dee382e Merge pull request #515 from m2049r/fix_license
SwipeableRecyclerView license
2019-01-11 19:23:56 +01:00
m2049r
be0498c67d SwipeableRecyclerView license 2019-01-11 19:23:25 +01:00
m2049r
06227a4a83 Merge pull request #514 from m2049r/feature_dismissTx
Allow swipe-to-dismiss transactions in street mode
2019-01-10 19:33:26 +01:00
m2049r
4409087bd0 update dependencies 2019-01-10 19:31:34 +01:00
m2049r
965e52d8a5 allow to dismiss tx in streetmode 2019-01-10 19:31:15 +01:00
m2049r
beba0f497b Fix bech32 (#513)
* fix bech32 recognition

* bump version
2019-01-09 20:54:14 +01:00
m2049r
9d1827ff0d show current node (#511)
& bump version
2019-01-08 22:13:25 +01:00
m2049r
c04b192753 keep node once found (#510) 2019-01-06 10:57:25 +01:00
m2049r
888b5edaec height for jan19 (#509) 2019-01-06 09:56:00 +01:00
netrik182
5ee5a81926 updated pt-BR translation help and node strings (#491)
* updated pt-BR translation for #440

* update help.xml after review

* update strings.xml after review

* update help.xml after further peer review
2019-01-05 13:42:49 +01:00
m2049r
c4e361a873 allow bech32 segwit btc addresses (#508) 2019-01-05 13:42:22 +01:00
m2049r
dba6cb057e add restore height for december (#502) 2018-12-23 08:29:21 +01:00
m2049r
9e1167c5b9 read unsigned types & read payload_data (top_version...) (#501) 2018-12-23 08:12:50 +01:00
Hans
12546a1ade [Estonian] Some additional translations (#497) 2018-12-23 07:48:20 +01:00
BlackLotus64
e9313bc235 ES Translation of strings and help v2 (#490) 2018-12-08 18:26:20 +01:00
m2049r
7e9bf84640 tweaks to barcode/tag reading (#494)
- deal with empty payment_id
- use btc message as description
- BarcodeData is final
2018-12-02 10:23:07 +01:00
129 changed files with 7247 additions and 1830 deletions

View File

@@ -3,11 +3,13 @@ jobs:
build: build:
working_directory: ~/code working_directory: ~/code
docker: docker:
- image: bitriseio/android-ndk - image: circleci/android:api-28-ndk
environment: environment:
JVM_OPTS: -Xmx3200m JVM_OPTS: -Xmx3200m
steps: steps:
- checkout - checkout
- run: yes | sdkmanager --licenses || exit 0
- run: yes | sdkmanager --update || exit 0
- restore_cache: - restore_cache:
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run: - run:

3
.idea/.gitignore generated vendored
View File

@@ -1,3 +0,0 @@
workspace.xml
markdown-*
misc.xml

1
.idea/.name generated
View File

@@ -1 +0,0 @@
xmrwallet

22
.idea/compiler.xml generated
View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@@ -1,3 +0,0 @@
<component name="CopyrightManager">
<settings default="" />
</component>

19
.idea/gradle.xml generated
View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="disableWrapperSourceDistributionNotification" value="true" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

9
.idea/modules.xml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/xmrwallet.iml" filepath="$PROJECT_DIR$/xmrwallet.iml" />
</modules>
</component>
</project>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 28 targetSdkVersion 28
versionCode 160 versionCode 176
versionName "1.10.10 'Node-O-matiC'" versionName "1.11.6 'Chernushka'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
@@ -113,10 +113,16 @@ dependencies {
implementation 'dnsjava:dnsjava:2.1.8' implementation 'dnsjava:dnsjava:2.1.8'
implementation 'org.jitsi:dnssecjava:1.1.3' implementation 'org.jitsi:dnssecjava:1.1.3'
implementation 'org.slf4j:slf4j-nop:1.7.25' implementation 'org.slf4j:slf4j-nop:1.7.25'
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
// https://mvnrepository.com/artifact/com.github.aelstad/keccakj
implementation 'com.github.aelstad:keccakj:1.1.0'
testImplementation "junit:junit:$rootProject.ext.junitVersion" testImplementation "junit:junit:$rootProject.ext.junitVersion"
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
testImplementation 'org.json:json:20140107' testImplementation 'org.json:json:20180813'
testImplementation 'net.jodah:concurrentunit:0.4.2' testImplementation 'net.jodah:concurrentunit:0.4.4'
} }

View File

@@ -26,14 +26,14 @@
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:label="@string/wallet_activity_name" android:label="@string/wallet_activity_name"
android:launchMode="singleTask" android:launchMode="singleTask"
android:screenOrientation="portrait" /> android:screenOrientation="behind" />
<activity <activity
android:name=".LoginActivity" android:name=".LoginActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait"> android:screenOrientation="locked">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@@ -42,6 +42,22 @@
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter> </intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<data android:scheme="monero" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<data android:scheme="bitcoin" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<meta-data <meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter" /> android:resource="@xml/usb_device_filter" />
@@ -52,5 +68,15 @@
android:description="@string/service_description" android:description="@string/service_description"
android:exported="false" android:exported="false"
android:label="Monero Wallet Service" /> android:label="Monero Wallet Service" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application> </application>
</manifest> </manifest>

View File

@@ -43,6 +43,9 @@ Copyright (c) 2014 Dushyanth Maguluru
<h3>AndroidLicensesPage (https://github.com/adamsp/AndroidLicensesPage)</h3> <h3>AndroidLicensesPage (https://github.com/adamsp/AndroidLicensesPage)</h3>
Copyright (c) 2013 Adam Speakman Copyright (c) 2013 Adam Speakman
<h3>SwipeableRecyclerView (https://github.com/brnunes/SwipeableRecyclerView)</h3>
Copyright (c) 2015 Bruno R. Nunes
<h3>Apache License, Version 2.0, January 2004</h3> <h3>Apache License, Version 2.0, January 2004</h3>
http://www.apache.org/licenses/<br/> http://www.apache.org/licenses/<br/>
<br/> <br/>

View File

@@ -442,12 +442,6 @@ Java_com_m2049r_xmrwallet_model_WalletManager_findWallets(JNIEnv *env, jobject i
return cpp2java(env, walletPaths); return cpp2java(env, walletPaths);
} }
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_getErrorString(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return env->NewStringUTF(wallet->errorString().c_str());
}
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; //TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
JNIEXPORT void JNICALL JNIEXPORT void JNICALL

View File

@@ -60,14 +60,14 @@ enum {
HASH_DATA_AREA = 136 HASH_DATA_AREA = 136
}; };
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed); void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height);
inline void slow_hash(const void *data, const size_t length, char *hash) { inline void slow_hash(const void *data, const size_t length, char *hash) {
cn_slow_hash(data, length, hash, 0 /* variant */, 0/*prehashed*/); cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/);
} }
inline void slow_hash_broken(const void *data, char *hash, int variant) { inline void slow_hash_broken(const void *data, char *hash, int variant) {
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/); cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, 0 /*height*/);
} }
#ifdef __cplusplus #ifdef __cplusplus

View File

@@ -18,7 +18,6 @@ package com.m2049r.levin.scanner;
import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.data.NodeInfo;
import java.net.InetSocketAddress;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@@ -176,9 +175,9 @@ public class Dispatcher implements PeerRetriever.OnGetPeers {
} }
private void retrievePeers(PeerRetriever peer) { private void retrievePeers(PeerRetriever peer) {
for (InetSocketAddress socketAddress : peer.getPeers()) { for (LevinPeer levinPeer : peer.getPeers()) {
if (getMorePeers()) if (getMorePeers())
retrievePeer(new NodeInfo(socketAddress)); retrievePeer(new NodeInfo(levinPeer));
else else
break; break;
} }

View 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;
}
}

View File

@@ -28,7 +28,6 @@ import java.io.DataInput;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
@@ -45,7 +44,7 @@ public class PeerRetriever implements Callable<PeerRetriever> {
static final private byte[] HANDSHAKE = handshakeRequest().asByteArray(); static final private byte[] HANDSHAKE = handshakeRequest().asByteArray();
static final private byte[] FLAGS_RESP = flagsResponse().asByteArray(); static final private byte[] FLAGS_RESP = flagsResponse().asByteArray();
final private List<InetSocketAddress> peers = new ArrayList<>(); final private List<LevinPeer> peers = new ArrayList<>();
private NodeInfo nodeInfo; private NodeInfo nodeInfo;
private OnGetPeers onGetPeersCallback; private OnGetPeers onGetPeersCallback;
@@ -67,7 +66,7 @@ public class PeerRetriever implements Callable<PeerRetriever> {
return !peers.isEmpty(); return !peers.isEmpty();
} }
public List<InetSocketAddress> getPeers() { public List<LevinPeer> getPeers() {
return peers; return peers;
} }
@@ -107,12 +106,18 @@ public class PeerRetriever implements Callable<PeerRetriever> {
} }
private void readAddressList(Section section) { 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") @SuppressWarnings("unchecked")
List<Section> peerList = (List<Section>) section.get("local_peerlist_new"); List<Section> peerList = (List<Section>) section.get("local_peerlist_new");
if (peerList != null) { if (peerList != null) {
for (Section peer : peerList) { for (Section peer : peerList) {
Section adr = (Section) peer.get("adr"); Section adr = (Section) peer.get("adr");
Byte type = (Byte) adr.get("type"); Integer type = (Integer) adr.get("type");
if ((type == null) || (type != 1)) if ((type == null) || (type != 1))
continue; continue;
Section addr = (Section) adr.get("addr"); Section addr = (Section) adr.get("addr");
@@ -121,7 +126,7 @@ public class PeerRetriever implements Callable<PeerRetriever> {
Integer ip = (Integer) addr.get("m_ip"); Integer ip = (Integer) addr.get("m_ip");
if (ip == null) if (ip == null)
continue; continue;
Short sport = (Short) addr.get("m_port"); Integer sport = (Integer) addr.get("m_port");
if (sport == null) if (sport == null)
continue; continue;
int port = sport; int port = sport;
@@ -133,7 +138,7 @@ public class PeerRetriever implements Callable<PeerRetriever> {
&& !inet.isLoopbackAddress() && !inet.isLoopbackAddress()
&& !inet.isMulticastAddress() && !inet.isMulticastAddress()
&& !inet.isLinkLocalAddress()) { && !inet.isLinkLocalAddress()) {
peers.add(new InetSocketAddress(inet, port)); peers.add(new LevinPeer(inet, port, topVersion, currentHeight, topId));
} }
} }
} }

View File

@@ -83,9 +83,11 @@ public class LevinReader {
case Section.SERIALIZE_TYPE_INT32: case Section.SERIALIZE_TYPE_INT32:
return in.readInt(); return in.readInt();
case Section.SERIALIZE_TYPE_UINT16: case Section.SERIALIZE_TYPE_UINT16:
return in.readUnsignedShort();
case Section.SERIALIZE_TYPE_INT16: case Section.SERIALIZE_TYPE_INT16:
return in.readShort(); return in.readShort();
case Section.SERIALIZE_TYPE_UINT8: case Section.SERIALIZE_TYPE_UINT8:
return in.readUnsignedByte();
case Section.SERIALIZE_TYPE_INT8: case Section.SERIALIZE_TYPE_INT8:
return in.readByte(); return in.readByte();
case Section.SERIALIZE_TYPE_OBJECT: case Section.SERIALIZE_TYPE_OBJECT:
@@ -171,10 +173,10 @@ public class LevinReader {
// this should be in LittleEndianDataInputStream because it has little // this should be in LittleEndianDataInputStream because it has little
// endian logic // endian logic
private long readRest(int firstByte, int bytes) throws IOException { private long readRest(final int firstByte, final int bytes) throws IOException {
long result = firstByte; long result = firstByte;
for (int i = 0; i < bytes; i++) { for (int i = 1; i < bytes + 1; i++) {
result = result + (in.readUnsignedByte() << 8); result = result + (((long) in.readUnsignedByte()) << (8 * i));
} }
return result; return result;
} }

View File

@@ -16,6 +16,7 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@@ -33,6 +34,7 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.Button; import android.widget.Button;
import android.widget.LinearLayout; import android.widget.LinearLayout;
@@ -45,6 +47,7 @@ import com.m2049r.xmrwallet.util.FingerprintHelper;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper; import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.RestoreHeight; import com.m2049r.xmrwallet.util.RestoreHeight;
import com.m2049r.xmrwallet.util.ledger.Monero;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import com.nulabinc.zxcvbn.Strength; import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn; import com.nulabinc.zxcvbn.Zxcvbn;
@@ -95,7 +98,6 @@ public class GenerateFragment extends Fragment {
etWalletRestoreHeight = view.findViewById(R.id.etWalletRestoreHeight); etWalletRestoreHeight = view.findViewById(R.id.etWalletRestoreHeight);
bGenerate = view.findViewById(R.id.bGenerate); bGenerate = view.findViewById(R.id.bGenerate);
etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etWalletViewKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etWalletViewKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etWalletSpendKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etWalletSpendKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
@@ -241,9 +243,7 @@ public class GenerateFragment extends Fragment {
} }
}); });
etWalletAddress.setVisibility(View.VISIBLE); etWalletAddress.setVisibility(View.VISIBLE);
etWalletAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() etWalletAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
{
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) { || (actionId == EditorInfo.IME_ACTION_NEXT)) {
@@ -275,9 +275,7 @@ public class GenerateFragment extends Fragment {
} }
if (type.equals(TYPE_KEY)) { if (type.equals(TYPE_KEY)) {
etWalletSpendKey.setVisibility(View.VISIBLE); etWalletSpendKey.setVisibility(View.VISIBLE);
etWalletSpendKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() etWalletSpendKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
{
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) { || (actionId == EditorInfo.IME_ACTION_NEXT)) {
@@ -304,9 +302,7 @@ public class GenerateFragment extends Fragment {
} }
}); });
} }
bGenerate.setOnClickListener(new View.OnClickListener() bGenerate.setOnClickListener(new View.OnClickListener() {
{
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
@@ -617,4 +613,80 @@ public class GenerateFragment extends Fragment {
} }
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
} }
AlertDialog ledgerDialog = null;
public void convertLedgerSeed() {
if (ledgerDialog != null) return;
final Activity activity = getActivity();
View promptsView = getLayoutInflater().inflate(R.layout.prompt_ledger_seed, null);
android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity);
alertDialogBuilder.setView(promptsView);
final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed);
final TextInputLayout etPassphrase = promptsView.findViewById(R.id.etPassphrase);
etSeed.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (etSeed.getError() != null) {
etSeed.setError(null);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
}
});
alertDialogBuilder
.setCancelable(false)
.setPositiveButton(getString(R.string.label_ok), null)
.setNegativeButton(getString(R.string.label_cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Helper.hideKeyboardAlways(activity);
etWalletMnemonic.getEditText().getText().clear();
dialog.cancel();
ledgerDialog = null;
}
});
ledgerDialog = alertDialogBuilder.create();
ledgerDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String ledgerSeed = etSeed.getEditText().getText().toString();
String ledgerPassphrase = etPassphrase.getEditText().getText().toString();
String moneroSeed = Monero.convert(ledgerSeed, ledgerPassphrase);
if (moneroSeed != null) {
etWalletMnemonic.getEditText().setText(moneroSeed);
ledgerDialog.dismiss();
ledgerDialog = null;
} else {
etSeed.setError(getString(R.string.bad_ledger_seed));
}
}
});
}
});
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
ledgerDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
ledgerDialog.show();
}
} }

View File

@@ -33,6 +33,7 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageButton; import android.widget.ImageButton;
@@ -620,6 +621,11 @@ public class GenerateReviewFragment extends Fragment {
return false; return false;
} }
}); });
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
openDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
return openDialog; return openDialog;
} }

View File

@@ -92,6 +92,11 @@ public class LoginActivity extends BaseActivity
Set<NodeInfo> favouriteNodes = new HashSet<>(); Set<NodeInfo> favouriteNodes = new HashSet<>();
@Override
public NodeInfo getNode() {
return node;
}
@Override @Override
public void setNode(NodeInfo node) { public void setNode(NodeInfo node) {
if ((node != null) && (node.getNetworkType() != WalletManager.getInstance().getNetworkType())) if ((node != null) && (node.getNetworkType() != WalletManager.getInstance().getNetworkType()))
@@ -264,7 +269,11 @@ public class LoginActivity extends BaseActivity
} else { } else {
Timber.i("Waiting for permissions"); Timber.i("Waiting for permissions");
} }
processUsbIntent(getIntent());
// try intents
Intent intent = getIntent();
if (!processUsbIntent(intent))
processUriIntent(intent);
} }
boolean checkServiceRunning() { boolean checkServiceRunning() {
@@ -711,6 +720,10 @@ public class LoginActivity extends BaseActivity
intent.putExtra(WalletActivity.REQUEST_PW, walletPassword); intent.putExtra(WalletActivity.REQUEST_PW, walletPassword);
intent.putExtra(WalletActivity.REQUEST_FINGERPRINT_USED, fingerprintUsed); intent.putExtra(WalletActivity.REQUEST_FINGERPRINT_USED, fingerprintUsed);
intent.putExtra(WalletActivity.REQUEST_STREETMODE, streetmode); intent.putExtra(WalletActivity.REQUEST_STREETMODE, streetmode);
if (uri != null) {
intent.putExtra(WalletActivity.REQUEST_URI, uri);
uri = null; // use only once
}
startActivity(intent); startActivity(intent);
} }
@@ -1216,6 +1229,12 @@ public class LoginActivity extends BaseActivity
case R.id.action_language: case R.id.action_language:
onChangeLocale(); onChangeLocale();
return true; return true;
case R.id.action_ledger_seed:
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (f instanceof GenerateFragment) {
((GenerateFragment) f).convertLedgerSeed();
}
return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@@ -1348,30 +1367,31 @@ public class LoginActivity extends BaseActivity
}; };
private void connectLedger(UsbManager usbManager, final UsbDevice usbDevice) { private void connectLedger(UsbManager usbManager, final UsbDevice usbDevice) {
try { if (Ledger.ENABLED)
Ledger.connect(usbManager, usbDevice); try {
registerDetachReceiver(); Ledger.connect(usbManager, usbDevice);
onLedgerAction(); registerDetachReceiver();
runOnUiThread(new Runnable() { onLedgerAction();
@Override runOnUiThread(new Runnable() {
public void run() { @Override
Toast.makeText(LoginActivity.this, public void run() {
getString(R.string.toast_ledger_attached, usbDevice.getProductName()), Toast.makeText(LoginActivity.this,
Toast.LENGTH_SHORT) getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
.show(); Toast.LENGTH_SHORT)
} .show();
}); }
} catch (IOException ex) { });
runOnUiThread(new Runnable() { } catch (IOException ex) {
@Override runOnUiThread(new Runnable() {
public void run() { @Override
Toast.makeText(LoginActivity.this, public void run() {
getString(R.string.open_wallet_ledger_missing), Toast.makeText(LoginActivity.this,
Toast.LENGTH_SHORT) getString(R.string.open_wallet_ledger_missing),
.show(); Toast.LENGTH_SHORT)
} .show();
}); }
} });
}
} }
@Override @Override
@@ -1380,7 +1400,7 @@ public class LoginActivity extends BaseActivity
processUsbIntent(intent); processUsbIntent(intent);
} }
private void processUsbIntent(Intent intent) { private boolean processUsbIntent(Intent intent) {
String action = intent.getAction(); String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) { if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
synchronized (this) { synchronized (this) {
@@ -1393,6 +1413,21 @@ public class LoginActivity extends BaseActivity
} }
} }
} }
return true;
}
return false;
}
private String uri = null;
private void processUriIntent(Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
synchronized (this) {
uri = intent.getDataString();
Timber.d("URI Intent %s", uri);
HelpFragment.display(getSupportFragmentManager(), R.string.help_uri);
}
} }
} }

View File

@@ -99,6 +99,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void setNode(NodeInfo node); void setNode(NodeInfo node);
NodeInfo getNode();
Set<NodeInfo> getFavouriteNodes(); Set<NodeInfo> getFavouriteNodes();
boolean hasLedger(); boolean hasLedger();
@@ -128,7 +130,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
activityCallback.setTitle(null); activityCallback.setTitle(null);
activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS); activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
activityCallback.showNet(); activityCallback.showNet();
findBestNode(); NodeInfo node = activityCallback.getNode();
if (node == null)
findBestNode();
else
showNode(node);
} }
@Override @Override
@@ -181,7 +187,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (activityCallback.getFavouriteNodes().isEmpty()) if (activityCallback.getFavouriteNodes().isEmpty())
activityCallback.onNodePrefs(); startNodePrefs();
else else
findBestNode(); findBestNode();
} }
@@ -191,8 +197,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
view.findViewById(R.id.ibOption).setOnClickListener(new View.OnClickListener() { view.findViewById(R.id.ibOption).setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (activityCallback != null) startNodePrefs();
activityCallback.onNodePrefs();
} }
}); });
@@ -476,4 +481,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
tvNodeAddress.setText(nodeInfo.getAddress()); tvNodeAddress.setText(nodeInfo.getAddress());
tvNodeAddress.setVisibility(View.VISIBLE); tvNodeAddress.setVisibility(View.VISIBLE);
} }
private void startNodePrefs() {
activityCallback.setNode(null);
activityCallback.onNodePrefs();
}
} }

View File

@@ -32,6 +32,7 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
@@ -505,6 +506,10 @@ public class NodeFragment extends Fragment
}); });
} }
}); });
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
editDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
etNodePass.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etNodePass.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {

View File

@@ -17,19 +17,28 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.nfc.NfcManager; import android.nfc.NfcManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.content.FileProvider;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.ShareActionProvider;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
@@ -56,6 +65,9 @@ import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.ExchangeView; import com.m2049r.xmrwallet.widget.ExchangeView;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -69,8 +81,8 @@ public class ReceiveFragment extends Fragment {
private TextInputLayout etNotes; private TextInputLayout etNotes;
private ExchangeView evAmount; private ExchangeView evAmount;
private TextView tvQrCode; private TextView tvQrCode;
private ImageView qrCode; private ImageView ivQrCode;
private ImageView qrCodeFull; private ImageView ivQrCodeFull;
private EditText etDummy; private EditText etDummy;
private ImageButton bCopyAddress; private ImageButton bCopyAddress;
private Button bSubaddress; private Button bSubaddress;
@@ -97,9 +109,9 @@ public class ReceiveFragment extends Fragment {
tvAddress = view.findViewById(R.id.tvAddress); tvAddress = view.findViewById(R.id.tvAddress);
etNotes = view.findViewById(R.id.etNotes); etNotes = view.findViewById(R.id.etNotes);
evAmount = view.findViewById(R.id.evAmount); evAmount = view.findViewById(R.id.evAmount);
qrCode = view.findViewById(R.id.qrCode); ivQrCode = view.findViewById(R.id.qrCode);
tvQrCode = view.findViewById(R.id.tvQrCode); tvQrCode = view.findViewById(R.id.tvQrCode);
qrCodeFull = view.findViewById(R.id.qrCodeFull); ivQrCodeFull = view.findViewById(R.id.qrCodeFull);
etDummy = view.findViewById(R.id.etDummy); etDummy = view.findViewById(R.id.etDummy);
bCopyAddress = view.findViewById(R.id.bCopyAddress); bCopyAddress = view.findViewById(R.id.bCopyAddress);
bSubaddress = view.findViewById(R.id.bSubaddress); bSubaddress = view.findViewById(R.id.bSubaddress);
@@ -178,25 +190,25 @@ public class ReceiveFragment extends Fragment {
} }
}); });
qrCode.setOnClickListener(new View.OnClickListener() { ivQrCode.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
etDummy.requestFocus(); etDummy.requestFocus();
if (qrValid) { if (qrValid) {
qrCodeFull.setImageBitmap(((BitmapDrawable) qrCode.getDrawable()).getBitmap()); ivQrCodeFull.setImageBitmap(((BitmapDrawable) ivQrCode.getDrawable()).getBitmap());
qrCodeFull.setVisibility(View.VISIBLE); ivQrCodeFull.setVisibility(View.VISIBLE);
} else { } else {
evAmount.doExchange(); evAmount.doExchange();
} }
} }
}); });
qrCodeFull.setOnClickListener(new View.OnClickListener() { ivQrCodeFull.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
qrCodeFull.setImageBitmap(null); ivQrCodeFull.setImageBitmap(null);
qrCodeFull.setVisibility(View.GONE); ivQrCodeFull.setVisibility(View.GONE);
} }
}); });
@@ -228,6 +240,81 @@ public class ReceiveFragment extends Fragment {
return view; return view;
} }
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
private ShareActionProvider shareActionProvider;
@Override
public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.receive_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
// Locate MenuItem with ShareActionProvider
MenuItem item = menu.findItem(R.id.menu_item_share);
// Fetch and store ShareActionProvider
shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
shareActionProvider.setOnShareTargetSelectedListener(new ShareActionProvider.OnShareTargetSelectedListener() {
@Override
public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
saveQrCode(); // save it only if we need it
return false;
}
});
}
private void setShareIntent() {
if (shareActionProvider != null) {
if (qrValid) {
shareActionProvider.setShareIntent(getShareIntent());
} else {
shareActionProvider.setShareIntent(null);
}
}
}
private void saveQrCode() {
if (!qrValid) throw new IllegalStateException("trying to save null qr code!");
File cachePath = new File(getActivity().getCacheDir(), "images");
if (!cachePath.exists())
if (!cachePath.mkdirs()) throw new IllegalStateException("cannot create images folder");
File png = new File(cachePath, "QR.png");
try {
FileOutputStream stream = new FileOutputStream(png);
Bitmap qrBitmap = ((BitmapDrawable) ivQrCode.getDrawable()).getBitmap();
qrBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.close();
} catch (IOException ex) {
Timber.e(ex);
// make sure we don't share an old qr code
if (!png.delete()) throw new IllegalStateException("cannot delete old qr code");
// if we manage to delete it, the URI points to nothing and the user gets a toast with the error
}
}
private Intent getShareIntent() {
File imagePath = new File(getActivity().getCacheDir(), "images");
File png = new File(imagePath, "QR.png");
Uri contentUri = FileProvider.getUriForFile(getActivity(),
BuildConfig.APPLICATION_ID + ".fileprovider", png);
if (contentUri != null) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
shareIntent.setDataAndType(contentUri, getActivity().getContentResolver().getType(contentUri));
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.putExtra(Intent.EXTRA_TEXT, bcData.getUriString());
return shareIntent;
}
return null;
}
void enableSubaddressButton(boolean enable) { void enableSubaddressButton(boolean enable) {
bSubaddress.setEnabled(enable); bSubaddress.setEnabled(enable);
if (enable) { if (enable) {
@@ -242,23 +329,23 @@ public class ReceiveFragment extends Fragment {
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
} }
boolean qrValid = true; private boolean qrValid = false;
void clearQR() { void clearQR() {
if (qrValid) { if (qrValid) {
qrCode.setImageBitmap(null); ivQrCode.setImageBitmap(null);
qrValid = false; qrValid = false;
setShareIntent();
if (isLoaded) if (isLoaded)
tvQrCode.setVisibility(View.VISIBLE); tvQrCode.setVisibility(View.VISIBLE);
} }
} }
void setQR(Bitmap qr) { void setQR(Bitmap qr) {
qrCode.setImageBitmap(qr); ivQrCode.setImageBitmap(qr);
qrValid = true; qrValid = true;
setShareIntent();
tvQrCode.setVisibility(View.GONE); tvQrCode.setVisibility(View.GONE);
Helper.hideKeyboard(getActivity());
etDummy.requestFocus();
} }
@Override @Override
@@ -392,7 +479,7 @@ public class ReceiveFragment extends Fragment {
return; return;
} }
bcData = new BarcodeData(BarcodeData.Asset.XMR, address, null, notes, xmrAmount); bcData = new BarcodeData(BarcodeData.Asset.XMR, address, null, notes, xmrAmount);
int size = Math.min(qrCode.getHeight(), qrCode.getWidth()); int size = Math.max(ivQrCode.getWidth(), ivQrCode.getHeight());
Bitmap qr = generate(bcData.getUriString(), size, size); Bitmap qr = generate(bcData.getUriString(), size, size);
if (qr != null) { if (qr != null) {
setQR(qr); setQR(qr);
@@ -422,8 +509,8 @@ public class ReceiveFragment extends Fragment {
Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.RGB_565); Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.RGB_565);
bitmap = addLogo(bitmap); bitmap = addLogo(bitmap);
return bitmap; return bitmap;
} catch (WriterException e) { } catch (WriterException ex) {
e.printStackTrace(); Timber.e(ex);
} }
return null; return null;
} }
@@ -487,6 +574,7 @@ public class ReceiveFragment extends Fragment {
@Override @Override
public void onPause() { public void onPause() {
Timber.d("onPause()"); Timber.d("onPause()");
Helper.hideKeyboard(getActivity());
super.onPause(); super.onPause();
} }

View File

@@ -31,7 +31,7 @@ public abstract class SecureActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// set FLAG_SECURE to prevent screenshots in Release Mode // set FLAG_SECURE to prevent screenshots in Release Mode
if (!BuildConfig.DEBUG && !BuildConfig.FLAVOR_type.equals("alpha")) { if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE); getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
} }
} }

View File

@@ -36,7 +36,7 @@ import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer; import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.UserNotes; import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -71,7 +71,6 @@ public class TxFragment extends Fragment {
private TextView tvTxFee; private TextView tvTxFee;
private TextView tvTxTransfers; private TextView tvTxTransfers;
private TextView etTxNotes; private TextView etTxNotes;
private Button bTxNotes;
// XMRTO stuff // XMRTO stuff
private View cvXmrTo; private View cvXmrTo;
@@ -102,21 +101,9 @@ public class TxFragment extends Fragment {
tvTxFee = view.findViewById(R.id.tvTxFee); tvTxFee = view.findViewById(R.id.tvTxFee);
tvTxTransfers = view.findViewById(R.id.tvTxTransfers); tvTxTransfers = view.findViewById(R.id.tvTxTransfers);
etTxNotes = view.findViewById(R.id.etTxNotes); etTxNotes = view.findViewById(R.id.etTxNotes);
bTxNotes = view.findViewById(R.id.bTxNotes);
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
bTxNotes.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
info.notes = null; // force reload on next view
bTxNotes.setEnabled(false);
etTxNotes.setEnabled(false);
userNotes.setNote(etTxNotes.getText().toString());
activityCallback.onSetNote(info.hash, userNotes.txNotes);
}
});
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() { tvTxXmrToKey.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@@ -131,14 +118,6 @@ public class TxFragment extends Fragment {
return view; return view;
} }
public void onNotesSet(boolean reload) {
bTxNotes.setEnabled(true);
etTxNotes.setEnabled(true);
if (reload) {
loadNotes(this.info);
}
}
void shareTxInfo() { void shareTxInfo() {
if (this.info == null) return; if (this.info == null) return;
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
@@ -315,7 +294,6 @@ public class TxFragment extends Fragment {
} }
} }
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -337,9 +315,9 @@ public class TxFragment extends Fragment {
String getTxNotes(String hash); String getTxNotes(String hash);
String getTxAddress(int major, int minor); boolean setTxNotes(String txId, String txNotes);
void onSetNote(String txId, String notes); String getTxAddress(int major, int minor);
void setToolbarButton(int type); void setToolbarButton(int type);
@@ -357,4 +335,16 @@ public class TxFragment extends Fragment {
+ " must implement Listener"); + " must implement Listener");
} }
} }
}
@Override
public void onPause() {
if (!etTxNotes.getText().toString().equals(userNotes.note)) { // notes have changed
// save them
userNotes.setNote(etTxNotes.getText().toString());
info.notes = userNotes.txNotes;
activityCallback.setTxNotes(info.hash, info.notes);
}
Helper.hideKeyboard(getActivity());
super.onPause();
}
}

View File

@@ -47,6 +47,7 @@ import android.widget.Toast;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.TxData; import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.dialog.CreditsFragment; import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment; import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
@@ -59,7 +60,6 @@ import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import java.util.ArrayList; import java.util.ArrayList;
@@ -81,6 +81,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
public static final String REQUEST_PW = "pw"; public static final String REQUEST_PW = "pw";
public static final String REQUEST_FINGERPRINT_USED = "fingerprint"; public static final String REQUEST_FINGERPRINT_USED = "fingerprint";
public static final String REQUEST_STREETMODE = "streetmode"; public static final String REQUEST_STREETMODE = "streetmode";
public static final String REQUEST_URI = "uri";
private NavigationView accountsView; private NavigationView accountsView;
private DrawerLayout drawer; private DrawerLayout drawer;
@@ -92,6 +93,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
private String password; private String password;
private String uri = null;
private long streetMode = 0; private long streetMode = 0;
@Override @Override
@@ -144,8 +147,16 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} else { } else {
streetMode = 0; streetMode = 0;
} }
updateAccountsBalance(); final WalletFragment walletFragment = (WalletFragment)
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
if (walletFragment != null) walletFragment.resetDismissedTransactions();
forceUpdate(); forceUpdate();
runOnUiThread(new Runnable() {
@Override
public void run() {
updateAccountsBalance();
}
});
} }
@Override @Override
@@ -168,6 +179,11 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
return getWallet().getUserNote(txId); return getWallet().getUserNote(txId);
} }
@Override
public boolean setTxNotes(String txId, String txNotes) {
return getWallet().setUserNote(txId, txNotes);
}
@Override @Override
public String getTxAddress(int major, int minor) { public String getTxAddress(int major, int minor) {
return getWallet().getSubaddress(major, minor); return getWallet().getSubaddress(major, minor);
@@ -188,6 +204,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
// we can set the streetmode height AFTER opening the wallet // we can set the streetmode height AFTER opening the wallet
requestStreetMode = extras.getBoolean(REQUEST_STREETMODE); requestStreetMode = extras.getBoolean(REQUEST_STREETMODE);
password = extras.getString(REQUEST_PW); password = extras.getString(REQUEST_PW);
uri = extras.getString(REQUEST_URI);
connectWalletService(walletId, password); connectWalletService(walletId, password);
} else { } else {
finish(); finish();
@@ -286,7 +303,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
showNet(); showNet();
} }
invalidateOptionsMenu(); invalidateOptionsMenu();
} }
private void onEnableStreetMode() { private void onEnableStreetMode() {
@@ -510,7 +526,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override @Override
public void onSendRequest() { public void onSendRequest() {
replaceFragment(new SendFragment(), null, null); replaceFragment(SendFragment.newInstance(uri), null, null);
uri = null; // only use uri once
} }
@Override @Override
@@ -706,26 +723,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
} }
@Override
public void onSetNotes(final boolean success) {
try {
final TxFragment txFragment = (TxFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
runOnUiThread(new Runnable() {
public void run() {
if (!success) {
Toast.makeText(WalletActivity.this, getString(R.string.tx_notes_set_failed), Toast.LENGTH_LONG).show();
}
txFragment.onNotesSet(success);
}
});
} catch (ClassCastException ex) {
// not in tx fragment
Timber.d(ex.getLocalizedMessage());
// never mind
}
}
@Override @Override
public void onProgress(final String text) { public void onProgress(final String text) {
try { try {
@@ -787,21 +784,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
@Override
public void onSetNote(String txId, String notes) {
if (mIsBound) { // no point in talking to unbound service
Intent intent = new Intent(getApplicationContext(), WalletService.class);
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SETNOTE);
intent.putExtra(WalletService.REQUEST_CMD_SETNOTE_TX, txId);
intent.putExtra(WalletService.REQUEST_CMD_SETNOTE_NOTES, notes);
startService(intent);
Timber.d("SET NOTE request sent");
} else {
Timber.e("Service not bound");
}
}
@Override @Override
public void onPrepareSend(final String tag, final TxData txData) { public void onPrepareSend(final String tag, final TxData txData) {
if (mIsBound) { // no point in talking to unbound service if (mIsBound) { // no point in talking to unbound service
@@ -956,8 +938,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
if (!processed || (onUriScannedListener == null)) { if (!processed || (onUriScannedListener == null)) {
Toast.makeText(this, getString(R.string.nfc_tag_read_what), Toast.LENGTH_LONG).show(); Toast.makeText(this, getString(R.string.nfc_tag_read_what), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, getString(R.string.nfc_tag_read_success), Toast.LENGTH_SHORT).show();
} }
} }
@@ -1057,6 +1037,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} else { } else {
tvBalance.setText(null); tvBalance.setText(null);
} }
updateAccountsList();
} }
void updateAccountsHeader() { void updateAccountsHeader() {
@@ -1070,8 +1051,11 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
Menu menu = accountsView.getMenu(); Menu menu = accountsView.getMenu();
menu.removeGroup(R.id.accounts_list); menu.removeGroup(R.id.accounts_list);
final int n = wallet.getNumAccounts(); final int n = wallet.getNumAccounts();
final boolean showBalances = (n > 1) && !isStreetMode();
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
final String label = wallet.getAccountLabel(i); final String label = (showBalances ?
getString(R.string.label_account, wallet.getAccountLabel(i), Helper.getDisplayAmount(wallet.getBalance(i), 2))
: wallet.getAccountLabel(i));
final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label); final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label);
item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp); item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp);
if (i == wallet.getAccountIndex()) if (i == wallet.getAccountIndex())

View File

@@ -38,6 +38,7 @@ import android.widget.ProgressBar;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import com.github.brnunes.swipeablerecyclerview.SwipeableRecyclerViewTouchListener;
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
@@ -73,6 +74,12 @@ public class WalletFragment extends Fragment
private Spinner sCurrency; private Spinner sCurrency;
private List<String> dismissedTransactions = new ArrayList<>();
public void resetDismissedTransactions() {
dismissedTransactions.clear();
}
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@@ -116,9 +123,44 @@ public class WalletFragment extends Fragment
RecyclerView recyclerView = view.findViewById(R.id.list); RecyclerView recyclerView = view.findViewById(R.id.list);
this.adapter = new TransactionInfoAdapter(getActivity(), this); adapter = new TransactionInfoAdapter(getActivity(), this);
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
SwipeableRecyclerViewTouchListener swipeTouchListener =
new SwipeableRecyclerViewTouchListener(recyclerView,
new SwipeableRecyclerViewTouchListener.SwipeListener() {
@Override
public boolean canSwipeLeft(int position) {
return activityCallback.isStreetMode();
}
@Override
public boolean canSwipeRight(int position) {
return activityCallback.isStreetMode();
}
@Override
public void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
dismissedTransactions.add(adapter.getItem(position).hash);
adapter.removeItem(position);
}
adapter.notifyDataSetChanged();
}
@Override
public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
dismissedTransactions.add(adapter.getItem(position).hash);
adapter.removeItem(position);
}
adapter.notifyDataSetChanged();
}
});
recyclerView.addOnItemTouchListener(swipeTouchListener);
bSend.setOnClickListener(new View.OnClickListener() { bSend.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@@ -177,7 +219,7 @@ public class WalletFragment extends Fragment
if (isExchanging) return; // wait for exchange to finish - it will fire this itself then. if (isExchanging) return; // wait for exchange to finish - it will fire this itself then.
// at this point selection is XMR in case of error // at this point selection is XMR in case of error
String displayB; String displayB;
double amountA = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // crash if this fails! double amountA = Helper.getDecimalAmount(unlockedBalance).doubleValue();
if (!Helper.CRYPTO.equals(balanceCurrency)) { // not XMR if (!Helper.CRYPTO.equals(balanceCurrency)) { // not XMR
double amountB = amountA * balanceRate; double amountB = amountA * balanceRate;
displayB = Helper.getFormattedAmount(amountB, false); displayB = Helper.getFormattedAmount(amountB, false);
@@ -193,10 +235,10 @@ public class WalletFragment extends Fragment
private final ExchangeApi exchangeApi = Helper.getExchangeApi(); private final ExchangeApi exchangeApi = Helper.getExchangeApi();
void refreshBalance() { void refreshBalance() {
double unconfirmedXmr = Double.parseDouble(Helper.getDisplayAmount(balance - unlockedBalance)); double unconfirmedXmr = Helper.getDecimalAmount(balance - unlockedBalance).doubleValue();
showUnconfirmed(unconfirmedXmr); showUnconfirmed(unconfirmedXmr);
if (sCurrency.getSelectedItemPosition() == 0) { // XMR if (sCurrency.getSelectedItemPosition() == 0) { // XMR
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail! double amountXmr = Helper.getDecimalAmount(unlockedBalance).doubleValue();
showBalance(Helper.getFormattedAmount(amountXmr, true)); showBalance(Helper.getFormattedAmount(amountXmr, true));
} else { // not XMR } else { // not XMR
String currency = (String) sCurrency.getSelectedItem(); String currency = (String) sCurrency.getSelectedItem();
@@ -252,7 +294,7 @@ public class WalletFragment extends Fragment
public void exchangeFailed() { public void exchangeFailed() {
sCurrency.setSelection(0, true); // default to XMR sCurrency.setSelection(0, true); // default to XMR
double amountXmr = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // assume this cannot fail! double amountXmr = Helper.getDecimalAmount(unlockedBalance).doubleValue();
showBalance(Helper.getFormattedAmount(amountXmr, true)); showBalance(Helper.getFormattedAmount(amountXmr, true));
hideExchanging(); hideExchanging();
} }
@@ -294,7 +336,9 @@ public class WalletFragment extends Fragment
Timber.d("StreetHeight=%d", streetHeight); Timber.d("StreetHeight=%d", streetHeight);
for (TransactionInfo info : wallet.getHistory().getAll()) { for (TransactionInfo info : wallet.getHistory().getAll()) {
Timber.d("TxHeight=%d", info.blockheight); Timber.d("TxHeight=%d", info.blockheight);
if (info.isPending || (info.blockheight >= streetHeight)) list.add(info); if ((info.isPending || (info.blockheight >= streetHeight))
&& !dismissedTransactions.contains(info.hash))
list.add(info);
} }
adapter.setInfos(list); adapter.setInfos(list);
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();

View File

@@ -21,7 +21,10 @@ import android.net.Uri;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator; import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.OpenAliasHelper; import com.m2049r.xmrwallet.util.OpenAliasHelper;
import com.m2049r.xmrwallet.util.PaymentProtocolHelper;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -37,8 +40,10 @@ public class BarcodeData {
public static final String OA_XMR_ASSET = "xmr"; public static final String OA_XMR_ASSET = "xmr";
public static final String OA_BTC_ASSET = "btc"; public static final String OA_BTC_ASSET = "btc";
static final String BTC_SCHEME = "bitcoin:"; static final String BTC_SCHEME = "bitcoin";
static final String BTC_DESCRIPTION = "message";
static final String BTC_AMOUNT = "amount"; static final String BTC_AMOUNT = "amount";
static final String BTC_BIP70_PARM = "r";
public enum Asset { public enum Asset {
XMR, BTC XMR, BTC
@@ -47,56 +52,55 @@ public class BarcodeData {
public enum Security { public enum Security {
NORMAL, NORMAL,
OA_NO_DNSSEC, OA_NO_DNSSEC,
OA_DNSSEC OA_DNSSEC,
BIP70
} }
public Asset asset = null; final public Asset asset;
public String addressName = null; final public String address;
public String address = null; final public String addressName;
public String paymentId = null; final public String paymentId;
public String amount = null; final public String amount;
public String description = null; final public String description;
public Security security = Security.NORMAL; final public Security security;
final public String bip70;
public BarcodeData(String uri) {
this.asset = asset;
this.address = address;
}
public BarcodeData(Asset asset, String address) { public BarcodeData(Asset asset, String address) {
this.asset = asset; this(asset, address, null, null, null, null, Security.NORMAL);
this.address = address;
} }
public BarcodeData(Asset asset, String address, String amount) { public BarcodeData(Asset asset, String address, String amount) {
this.asset = asset; this(asset, address, null, null, null, amount, Security.NORMAL);
this.address = address; }
this.amount = amount;
public BarcodeData(Asset asset, String address, String amount, String description, Security security) {
this(asset, address, null, null, description, amount, security);
} }
public BarcodeData(Asset asset, String address, String paymentId, String amount) { public BarcodeData(Asset asset, String address, String paymentId, String amount) {
this.asset = asset; this(asset, address, null, paymentId, null, amount, Security.NORMAL);
this.address = address;
this.paymentId = paymentId;
this.amount = amount;
} }
public BarcodeData(Asset asset, String address, String paymentId, String description, String amount) { public BarcodeData(Asset asset, String address, String paymentId, String description, String amount) {
this(asset, address, null, paymentId, description, amount, Security.NORMAL);
}
public BarcodeData(Asset asset, String address, String addressName, String paymentId, String description, String amount, Security security) {
this(asset, address, addressName, null, paymentId, description, amount, security);
}
public BarcodeData(Asset asset, String address, String addressName, String bip70, String paymentId, String description, String amount, Security security) {
this.asset = asset; this.asset = asset;
this.address = address; this.address = address;
this.bip70 = bip70;
this.addressName = addressName;
this.paymentId = paymentId; this.paymentId = paymentId;
this.description = description; this.description = description;
this.amount = amount; this.amount = amount;
}
public void setAddressName(String name) {
addressName = name;
}
public void setSecurity(Security security) {
this.security = security; this.security = security;
} }
public Uri getUri() { public Uri getUri() {
return Uri.parse(getUriString()); return Uri.parse(getUriString());
} }
@@ -134,13 +138,17 @@ public class BarcodeData {
if (bcData == null) { if (bcData == null) {
bcData = parseBitcoinUri(qrCode); bcData = parseBitcoinUri(qrCode);
} }
// check for btc payment uri (like bitpay)
if (bcData == null) {
bcData = parseBitcoinPaymentUrl(qrCode);
}
// check for naked btc address // check for naked btc address
if (bcData == null) { if (bcData == null) {
bcData = parseBitcoinNaked(qrCode); bcData = parseBitcoinNaked(qrCode);
} }
// check for OpenAlias // check for OpenAlias
if (bcData == null) { if (bcData == null) {
bcData = parseOpenAlias(qrCode); bcData = parseOpenAlias(qrCode, false);
} }
return bcData; return bcData;
} }
@@ -175,7 +183,11 @@ public class BarcodeData {
} }
} }
String address = monero.getPath(); String address = monero.getPath();
String paymentId = parms.get(XMR_PAYMENTID); 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 description = parms.get(XMR_DESCRIPTION);
String amount = parms.get(XMR_AMOUNT); String amount = parms.get(XMR_AMOUNT);
if (amount != null) { if (amount != null) {
@@ -212,19 +224,28 @@ public class BarcodeData {
} }
// bitcoin:mpQ84J43EURZHkCnXbyQ4PpNDLLBqdsMW2?amount=0.01 // bitcoin:mpQ84J43EURZHkCnXbyQ4PpNDLLBqdsMW2?amount=0.01
static public BarcodeData parseBitcoinUri(String uri) { // bitcoin:?r=https://bitpay.com/i/xxx
Timber.d("parseBitcoinUri=%s", uri); static public BarcodeData parseBitcoinUri(String uriString) {
Timber.d("parseBitcoinUri=%s", uriString);
if (uri == null) return null; if (uriString == null) return null;
URI uri;
try {
uri = new URI(uriString);
} catch (URISyntaxException ex) {
return null;
}
if (!uri.isOpaque() ||
!uri.getScheme().equals(BTC_SCHEME)) return null;
if (!uri.startsWith(BTC_SCHEME)) return null; String[] parts = uri.getRawSchemeSpecificPart().split("[?]");
if ((parts.length <= 0) || (parts.length > 2)) {
String noScheme = uri.substring(BTC_SCHEME.length()); Timber.d("invalid number of parts %d", parts.length);
Uri bitcoin = Uri.parse(noScheme); return null;
}
Map<String, String> parms = new HashMap<>(); Map<String, String> parms = new HashMap<>();
String query = bitcoin.getQuery(); if (parts.length == 2) {
if (query != null) { String[] args = parts[1].split("&");
String[] args = query.split("&");
for (String arg : args) { for (String arg : args) {
String[] namevalue = arg.split("="); String[] namevalue = arg.split("=");
if (namevalue.length == 0) { if (namevalue.length == 0) {
@@ -234,9 +255,26 @@ public class BarcodeData {
namevalue.length > 1 ? Uri.decode(namevalue[1]) : ""); namevalue.length > 1 ? Uri.decode(namevalue[1]) : "");
} }
} }
String address = bitcoin.getPath(); String description = parms.get(BTC_DESCRIPTION);
String address = parts[0]; // no need to decode as there can bo no special characters
if (address.isEmpty()) { // possibly a BIP72 uri
String bip70 = parms.get(BTC_BIP70_PARM);
if (bip70 == null) {
Timber.d("no address and can't find pp url");
return null;
}
if (!PaymentProtocolHelper.isHttp(bip70)) {
Timber.d("[%s] is not http url", bip70);
return null;
}
return new BarcodeData(BarcodeData.Asset.BTC, null, null, bip70, null, description, null, Security.NORMAL);
}
if (!BitcoinAddressValidator.validate(address)) {
Timber.d("BTC address (%s) invalid", address);
return null;
}
String amount = parms.get(BTC_AMOUNT); String amount = parms.get(BTC_AMOUNT);
if (amount != null) { if ((amount != null) && (!amount.isEmpty())) {
try { try {
Double.parseDouble(amount); Double.parseDouble(amount);
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
@@ -244,11 +282,22 @@ public class BarcodeData {
return null; // we have an amount but its not a number! return null; // we have an amount but its not a number!
} }
} }
if (!BitcoinAddressValidator.validate(address)) { return new BarcodeData(BarcodeData.Asset.BTC, address, null, description, amount);
Timber.d("address invalid"); }
// https://bitpay.com/invoice?id=xxx
// https://bitpay.com/i/KbMdd4EhnLXSbpWGKsaeo6
static public BarcodeData parseBitcoinPaymentUrl(String url) {
Timber.d("parseBitcoinUri=%s", url);
if (url == null) return null;
if (!PaymentProtocolHelper.isHttp(url)) {
Timber.d("[%s] is not http url", url);
return null; return null;
} }
return new BarcodeData(BarcodeData.Asset.BTC, address, amount);
return new BarcodeData(Asset.BTC, url);
} }
static public BarcodeData parseBitcoinNaked(String address) { static public BarcodeData parseBitcoinNaked(String address) {
@@ -264,7 +313,7 @@ public class BarcodeData {
return new BarcodeData(BarcodeData.Asset.BTC, address); 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); Timber.d("parseOpenAlias=%s", oaString);
if (oaString == null) return null; if (oaString == null) return null;
@@ -316,8 +365,8 @@ public class BarcodeData {
return null; return null;
} }
BarcodeData bc = new BarcodeData(asset, address, paymentId, description, amount); Security sec = dnssec ? BarcodeData.Security.OA_DNSSEC : BarcodeData.Security.OA_NO_DNSSEC;
bc.setAddressName(addressName);
return bc; return new BarcodeData(asset, address, addressName, paymentId, description, amount, sec);
} }
} }

View File

@@ -22,6 +22,7 @@ import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.Credentials; import com.burgstaller.okhttp.digest.Credentials;
import com.burgstaller.okhttp.digest.DigestAuthenticator; import com.burgstaller.okhttp.digest.DigestAuthenticator;
import com.m2049r.levin.scanner.Dispatcher; import com.m2049r.levin.scanner.Dispatcher;
import com.m2049r.levin.scanner.LevinPeer;
import com.m2049r.xmrwallet.util.OkHttpHelper; import com.m2049r.xmrwallet.util.OkHttpHelper;
import org.json.JSONException; import org.json.JSONException;
@@ -45,7 +46,8 @@ import okhttp3.ResponseBody;
import timber.log.Timber; import timber.log.Timber;
public class NodeInfo extends Node { public class NodeInfo extends Node {
final static public int MIN_MAJOR_VERSION = 9; final static public int MIN_MAJOR_VERSION = 11;
final static public String RPC_VERSION = "2.0";
private long height = 0; private long height = 0;
private long timestamp = 0; private long timestamp = 0;
@@ -99,21 +101,18 @@ public class NodeInfo extends Node {
super(nodeString); super(nodeString);
} }
public NodeInfo(InetSocketAddress socketAddress) { public NodeInfo(LevinPeer levinPeer) {
super(socketAddress); super(levinPeer.getSocketAddress());
}
public NodeInfo(InetSocketAddress address) {
super(address);
} }
public NodeInfo() { public NodeInfo() {
super(); super();
} }
public NodeInfo(InetSocketAddress peerAddress, long height, int majorVersion, double respTime) {
super(peerAddress);
this.height = height;
this.majorVersion = majorVersion;
this.responseTime = respTime;
}
public long getHeight() { public long getHeight() {
return height; return height;
} }
@@ -230,9 +229,12 @@ public class NodeInfo extends Node {
responseCode = response.code(); responseCode = response.code();
if (response.isSuccessful()) { if (response.isSuccessful()) {
ResponseBody respBody = response.body(); // closed through Response object ResponseBody respBody = response.body(); // closed through Response object
if ((respBody != null) && (respBody.contentLength() < 1000)) { // sanity check if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check
final JSONObject json = new JSONObject( final JSONObject json = new JSONObject(
respBody.string()); respBody.string());
String rpcVersion = json.getString("jsonrpc");
if (!RPC_VERSION.equals(rpcVersion))
return false;
final JSONObject header = json.getJSONObject( final JSONObject header = json.getJSONObject(
"result").getJSONObject("block_header"); "result").getJSONObject("block_header");
height = header.getLong("height"); height = header.getLong("height");

View File

@@ -20,9 +20,6 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.util.UserNotes;
import timber.log.Timber;
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents // https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
public class TxData implements Parcelable { public class TxData implements Parcelable {

View File

@@ -18,12 +18,11 @@ package com.m2049r.xmrwallet.data;
import android.os.Parcel; import android.os.Parcel;
import com.m2049r.xmrwallet.model.PendingTransaction;
public class TxDataBtc extends TxData { public class TxDataBtc extends TxData {
private String xmrtoUuid; private String xmrtoUuid;
private String btcAddress; private String btcAddress;
private String bip70;
private double btcAmount; private double btcAmount;
public TxDataBtc() { public TxDataBtc() {
@@ -50,6 +49,14 @@ public class TxDataBtc extends TxData {
this.btcAddress = btcAddress; this.btcAddress = btcAddress;
} }
public String getBip70() {
return bip70;
}
public void setBip70(String bip70) {
this.bip70 = bip70;
}
public double getBtcAmount() { public double getBtcAmount() {
return btcAmount; return btcAmount;
} }
@@ -63,6 +70,7 @@ public class TxDataBtc extends TxData {
super.writeToParcel(out, flags); super.writeToParcel(out, flags);
out.writeString(xmrtoUuid); out.writeString(xmrtoUuid);
out.writeString(btcAddress); out.writeString(btcAddress);
out.writeString(bip70);
out.writeDouble(btcAmount); out.writeDouble(btcAmount);
} }
@@ -81,16 +89,19 @@ public class TxDataBtc extends TxData {
super(in); super(in);
xmrtoUuid = in.readString(); xmrtoUuid = in.readString();
btcAddress = in.readString(); btcAddress = in.readString();
bip70 = in.readString();
btcAmount = in.readDouble(); btcAmount = in.readDouble();
} }
@Override @Override
public String toString() { public String toString() {
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
sb.append(",xmrtoUuid:"); sb.append(",xmrtoUuid:");
sb.append(xmrtoUuid); sb.append(xmrtoUuid);
sb.append(",btcAddress:"); sb.append(",btcAddress:");
sb.append(btcAddress); sb.append(btcAddress);
sb.append(",bip70:");
sb.append(bip70);
sb.append(",btcAmount:"); sb.append(",btcAmount:");
sb.append(btcAmount); sb.append(btcAmount);
return sb.toString(); return sb.toString();

Some files were not shown because too many files have changed in this diff Show More