1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-03 08:23:04 +02:00

Compare commits

...

25 Commits

Author SHA1 Message Date
m2049r
e9c74d4d9c Random fixes (#228)
* adapt for android studio 3.1 and remove witness

* set networktype from node

* add monero logging for DEBUG builds

* do not reset timestamps in apk

* no witness

* new version & apk naming
2018-04-15 16:33:20 +02:00
m2049r
b37adb4546 Update README.md 2018-04-11 18:20:07 +02:00
erciccione
a4e99209be change suggested ringsize from 5 to 7 in help.xml (#223) 2018-04-07 16:44:18 +02:00
m2049r
45aa8f30e5 fix openssl links 2018-04-05 19:09:30 +02:00
m2049r
60a7b15700 new version for fixed malloc (83dcd65) 2018-04-03 11:51:14 +02:00
m2049r
df2ff8b3b7 correct toolbar colour for testnet (#218) 2018-04-02 13:02:00 +02:00
m2049r
da8c8f80f1 new version for m2049r/monero 7e97e11 (#217) 2018-04-02 12:14:55 +02:00
m2049r
eda3de11fe added zxcvbn4j license (#216) 2018-04-01 13:23:03 +02:00
m2049r
7d7de14827 new version id for monero PR#3526 (#215) 2018-03-30 14:12:17 +02:00
m2049r
1c709da92c Update FAQ.md 2018-03-29 23:00:39 +02:00
m2049r
a9092497b2 changes for monero v0.12 (#214)
* new version id & name
* witness checksums
* build docs updated for v0.12
* remove binaries
* setenv HOME for ringdb to 'monero' in shared storage
* min ringsize 7
* remove boost_locale and zmq from build - don't need them for wallet_api
* splits for all archs
* throw IndexOutOfBounds in case the TX is empty
* donate, you ungrateful bastards! (removed donations to make google happy)
2018-03-29 22:35:31 +02:00
m2049r
b1f530e64a new version number (#208) 2018-03-13 22:31:32 +01:00
m2049r
8c086b77d3 Minor Bugfixes (#207)
* corrected spanish

* removed testcode

* fix issue #206
2018-03-06 18:15:23 +01:00
m2049r
c0fdfe87be build for x86 & arm in both 32 & 64 bits (#204) 2018-02-27 07:22:11 +01:00
m2049r
6cfd840283 fixed some bugs & reverted to boost 1.58 2018-02-26 09:46:20 +01:00
Miguel Botón
d80cde1136 Added build instructions for 64-bit (aarch64). (#172) 2018-02-26 08:42:22 +01:00
m2049r
bea4b06675 new version 2018-02-23 19:47:20 +01:00
m2049r
88bc33b5a4 added zxcvbn password score (#199)
* added zxcvbn password score

* changes messages & thresholds

change password label to passphrase
2018-02-23 19:32:21 +01:00
m2049r
2db36bb824 Enable Restore with Date instead of Height Only (#198)
* restore height class

* exact heights

* tweaks with DST

* some more spanish
2018-02-23 19:31:55 +01:00
m2049r
dd689b1883 fix for 2N9fzq66uZYQXp7uqrPBH6jKBhjrgTzpGCy (#190) 2018-01-24 19:59:13 +01:00
m2049r
641abd13f5 split apk (#191) 2018-01-23 21:35:26 +01:00
Stephan Hagios
a0b3a7fe5d used background borderless for nicer ripple effect (#189) 2018-01-22 21:36:13 +01:00
Stephan Hagios
a0486f581f Added circleci yml (#188)
* Adjusted layout files with correct sizes.

* Added password visibility toggle

* PR suggestions

* circle ci yml added

* different container with ndk
2018-01-21 18:53:45 +01:00
m2049r
36161137ec correct spanish (#187) 2018-01-18 17:45:46 +01:00
m2049r
c9927edbd1 save on exit only with connected wallet (#186) 2018-01-18 17:23:35 +01:00
94 changed files with 1589 additions and 652 deletions

27
.circleci/config.yml Normal file
View File

@@ -0,0 +1,27 @@
version: 2
jobs:
build:
working_directory: ~/code
docker:
- image: bitriseio/android-ndk
environment:
JVM_OPTS: -Xmx3200m
steps:
- checkout
- restore_cache:
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run:
name: Download Dependencies
command: ./gradlew androidDependencies
- save_cache:
paths:
- ~/.gradle
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run:
name: Run Tests
command: ./gradlew test
- store_artifacts:
path: app/build/reports
destination: reports
- store_test_results:
path: app/build/test-results

2
.gitignore vendored
View File

@@ -3,6 +3,8 @@
*.iml
/.idea/libraries
/.idea/workspace.xml
/.idea/caches
/.idea/codeStyles
/local.properties
/captures
.externalNativeBuild

View File

@@ -3,6 +3,7 @@ Another Android Monero Wallet
### QUICKSTART
- Download the APK for the most current release [here](https://github.com/m2049r/xmrwallet/releases) and install it
- Alternatively add our F-Droid repo https://f-droid.monerujo.io/fdroid/repo with fingerpint ```A8 2C 68 E1 4A F0 AA 6A 2E C2 0E 6B 27 2E FF 25 E5 A0 38 F3 F6 58 84 31 6E 0F 5E 0D 91 E7 B7 13``` to your F-Droid client
- Run the App and select "Generate Wallet" to create a new wallet or recover a wallet
- Advanced users can copy over synced wallet files (all files) onto sdcard in directory Monerujo (created first time App is started)
- See the [FAQ](doc/FAQ.md)

View File

@@ -60,9 +60,13 @@ set_target_properties(boost_wserialization PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_wserialization.a)
#############
# Monero set(libs_to_merge wallet cryptonote_core cryptonote_basic mnemonics common cncrypto ringct)
# Monero
#############
add_library(wallet_api STATIC IMPORTED)
set_target_properties(wallet_api PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet_api.a)
add_library(wallet STATIC IMPORTED)
set_target_properties(wallet PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet.a)
@@ -91,11 +95,9 @@ add_library(ringct STATIC IMPORTED)
set_target_properties(ringct PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct.a)
#####
add_library(p2p STATIC IMPORTED)
set_target_properties(p2p PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libp2p.a)
add_library(ringct_basic STATIC IMPORTED)
set_target_properties(ringct_basic PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct_basic.a)
add_library(blockchain_db STATIC IMPORTED)
set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION
@@ -113,7 +115,6 @@ add_library(unbound STATIC IMPORTED)
set_target_properties(unbound PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libunbound.a)
####
add_library(epee STATIC IMPORTED)
set_target_properties(epee PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libepee.a)
@@ -122,9 +123,21 @@ add_library(blocks STATIC IMPORTED)
set_target_properties(blocks PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblocks.a)
add_library(miniupnpc STATIC IMPORTED)
set_target_properties(miniupnpc PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libminiupnpc.a)
add_library(checkpoints STATIC IMPORTED)
set_target_properties(checkpoints PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcheckpoints.a)
add_library(device STATIC IMPORTED)
set_target_properties(device PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libdevice.a)
add_library(multisig STATIC IMPORTED)
set_target_properties(multisig PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libmultisig.a)
add_library(version STATIC IMPORTED)
set_target_properties(version PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libversion.a)
#############
# System
@@ -137,23 +150,26 @@ include_directories( ${EXTERNAL_LIBS_DIR}/monero/include )
message(STATUS EXTERNAL_LIBS_DIR : ${EXTERNAL_LIBS_DIR})
target_link_libraries( monerujo
wallet_api
wallet
cryptonote_core
cryptonote_basic
mnemonics
ringct
ringct_basic
common
cncrypto
blockchain_db
lmdb
easylogging
unbound
p2p
epee
blocks
miniupnpc
checkpoints
device
multisig
version
boost_chrono
boost_date_time

View File

@@ -1,15 +1,14 @@
apply plugin: 'com.android.application'
apply plugin: 'witness'
android {
compileSdkVersion 25
buildToolsVersion '26.0.2'
buildToolsVersion '27.0.3'
defaultConfig {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 25
versionCode 68
versionName "1.3.8 'Satoshis Dream'"
versionCode 87
versionName "1.4.7 'Monero Spedner'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -17,9 +16,6 @@ android {
arguments '-DANDROID_STL=c++_shared'
}
}
ndk {
abiFilters 'armeabi-v7a'
}
}
buildTypes {
@@ -36,49 +32,51 @@ android {
path "CMakeLists.txt"
}
}
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}
// Map for the version code that gives each ABI a value.
def abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
// APKs for the same app that all have the same version information.
android.applicationVariants.all { variant ->
// Assigns a different version code for each output APK.
variant.outputs.all {
output ->
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode
if (abiName == null) abiName = "universal"
def v = "${variant.versionName}".replaceFirst(" .*\$", "").replace(".", "x")
outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk"
}
}
}
dependencies {
compile 'com.android.support:appcompat-v7:25.4.0'
compile 'com.android.support:design:25.4.0'
compile 'com.android.support:support-v4:25.4.0'
compile 'com.android.support:recyclerview-v7:25.4.0'
compile 'com.android.support:cardview-v7:25.4.0'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'com.android.support:appcompat-v7:25.4.0'
implementation 'com.android.support:design:25.4.0'
implementation 'com.android.support:support-v4:25.4.0'
implementation 'com.android.support:recyclerview-v7:25.4.0'
implementation 'com.android.support:cardview-v7:25.4.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
compile "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
compile "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
testCompile "junit:junit:$rootProject.ext.junitVersion"
testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testCompile "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
testCompile 'org.json:json:20140107'
testCompile 'net.jodah:concurrentunit:0.4.2'
}
dependencyVerification {
verify = [
'com.android.support:design:3f409bf2019967ffc344cfaf11e52131fac982468a1707aaeb25bf3c52838966',
'com.android.support:appcompat-v7:70551e62660db15b790c5275f56b9de4dd9407d1494d07c8f3dd5698f3638677',
'com.android.support:transition:848270144fb180efd2bf928a00ed176dbbc5290badfd638390ffba90088df8b3',
'me.dm7.barcodescanner:zxing:d43973c9527c23fa8e6d338c6a2c458e373ce1ac6bcaa3bc41d11ae49116000d',
'me.dm7.barcodescanner:core:a5c8a704089b58029db166172ed8e55d756877d010a85a0b1c94fdc96ffb8f9a',
'com.android.support:support-v4:ee44c481a1f4d6978568e223e8125379b52b2ececdd53450e09ebae144bd377d',
'com.android.support:recyclerview-v7:a2fe121f9d01ed8980e97095b4a3fe9700a0aa0a7d4b0f8c594f765ad8455a0d',
'com.android.support:cardview-v7:f3fbbe1fcfdbec7333c6a2c516c5fd511a909d1975271818e268d6fe297d8c70',
'com.android.support.constraint:constraint-layout:b0c688cc2b7172608f8153a689d746da40f71e52d7e2fe2bfd9df2f92db77085',
'com.android.support:animated-vector-drawable:628ab1d56a6ee4cbedf32617af8b2a1fe02964ed0628e8f898cc09ddba6e1835',
'com.android.support:support-vector-drawable:077009d13882ee96f061e4bc2dbe7cce7ae1762d8297592a787ff741afbfb1f2',
'com.android.support:support-fragment:316d35d4d2d2902057efad104a73e4bdb50bee260a7075678185b8cd71170945',
'com.android.support:support-core-ui:e72ae29b823889686cff6fcb948d6745c2baf6d4c2af4fdffa1ec1e42e3833a3',
'com.android.support:support-media-compat:566a161d9cb0083ef62a53e46b71ce5b3d455b8635b1a0a4ae28d96d4b583de8',
'com.android.support:support-core-utils:34b8437dfa95ff28d29cf57ffa3b1354a9fa9bfe4059f0fd5ce2f5e4326a1748',
'com.android.support:support-compat:54019c63614ce08b02d7b9605490cd2b29ba5b2505f394a9517450b5f72b30ca',
'com.android.support:support-annotations:a774272036941b4e912eb426d70c848bde7f06a3bf5fb491f75a427dc6595270',
'com.android.support.constraint:constraint-layout-solver:8c62525a9bc5cff5633a96cb9b32fffeccaf41b8841aa87fc22607070dea9b8d',
'com.google.zxing:core:bba7724e02a997cec38213af77133ee8e24b0d5cf5fa7ecbc16a4fa93f11ee0d',
'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850',
'com.squareup.okhttp3:okhttp:7265adbd6f028aade307f58569d814835cd02bc9beffb70c25f72c9de50d61c4',
]
implementation 'com.nulab-inc:zxcvbn:1.2.3'
testImplementation "junit:junit:$rootProject.ext.junitVersion"
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
testImplementation 'org.json:json:20140107'
testImplementation 'net.jodah:concurrentunit:0.4.2'
}

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,9 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -32,12 +34,18 @@ import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.TextView;
import com.m2049r.xmrwallet.util.RestoreHeight;
import com.m2049r.xmrwallet.widget.Toolbar;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import timber.log.Timber;
@@ -125,7 +133,7 @@ public class GenerateFragment extends Fragment {
});
Helper.showKeyboard(getActivity());
//##############
etWalletName.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
@@ -231,9 +239,7 @@ public class GenerateFragment extends Fragment {
}
if (!type.equals(TYPE_NEW)) {
etWalletRestoreHeight.setVisibility(View.VISIBLE);
etWalletRestoreHeight.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener()
{
etWalletRestoreHeight.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboard(getActivity());
@@ -254,11 +260,61 @@ public class GenerateFragment extends Fragment {
}
});
etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
checkPassword();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
etWalletName.requestFocus();
initZxcvbn();
return view;
}
Zxcvbn zxcvbn = new Zxcvbn();
// initialize zxcvbn engine in background thread
private void initZxcvbn() {
new Thread(new Runnable() {
@Override
public void run() {
zxcvbn.measure("");
}
}).start();
}
private void checkPassword() {
String password = etWalletPassword.getEditText().getText().toString();
if (!password.isEmpty()) {
Strength strength = zxcvbn.measure(password);
int msg;
double guessesLog10 = strength.getGuessesLog10();
if (guessesLog10 < 10)
msg = R.string.password_weak;
else if (guessesLog10 < 11)
msg = R.string.password_fair;
else if (guessesLog10 < 12)
msg = R.string.password_good;
else if (guessesLog10 < 13)
msg = R.string.password_strong;
else
msg = R.string.password_very_strong;
etWalletPassword.setError(getResources().getString(msg));
} else {
etWalletPassword.setError(null);
}
}
private boolean checkName() {
String name = etWalletName.getEditText().getText().toString();
boolean ok = true;
@@ -281,6 +337,42 @@ public class GenerateFragment extends Fragment {
return ok;
}
private boolean checkHeight() {
long height = !type.equals(TYPE_NEW) ? getHeight() : 0;
boolean ok = true;
if (height < 0) {
etWalletRestoreHeight.setError(getString(R.string.generate_restoreheight_error));
ok = false;
}
if (ok) {
etWalletRestoreHeight.setError(null);
}
return ok;
}
private long getHeight() {
long height = 0;
String restoreHeight = etWalletRestoreHeight.getEditText().getText().toString().trim();
if (restoreHeight.isEmpty()) return -1;
try {
// is it a date?
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
parser.setLenient(false);
parser.parse(restoreHeight);
height = RestoreHeight.getInstance().getHeight(restoreHeight);
} catch (ParseException exPE) {
try {
// or is it a height?
height = Long.parseLong(restoreHeight);
} catch (NumberFormatException exNFE) {
return -1;
}
}
Timber.d("Using Restore Height = %d", height);
return height;
}
private boolean checkMnemonic() {
String seed = etWalletMnemonic.getEditText().getText().toString();
boolean ok = (seed.split("\\s").length == 25); // 25 words
@@ -294,7 +386,7 @@ public class GenerateFragment extends Fragment {
private boolean checkAddress() {
String address = etWalletAddress.getEditText().getText().toString();
boolean ok = Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet());
boolean ok = Wallet.isAddressValid(address);
if (!ok) {
etWalletAddress.setError(getString(R.string.generate_check_address));
} else {
@@ -327,15 +419,13 @@ public class GenerateFragment extends Fragment {
private void generateWallet() {
if (!checkName()) return;
if (!checkHeight()) return;
String name = etWalletName.getEditText().getText().toString();
String password = etWalletPassword.getEditText().getText().toString();
long height;
try {
height = Long.parseLong(etWalletRestoreHeight.getEditText().getText().toString());
} catch (NumberFormatException ex) {
height = 0; // Keep calm and carry on!
}
long height = getHeight();
if (height < 0) height = 0;
if (type.equals(TYPE_NEW)) {
bGenerate.setEnabled(false);

View File

@@ -34,6 +34,7 @@ import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.widget.Toolbar;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
@@ -81,7 +82,7 @@ public class GenerateReviewFragment extends Fragment {
bAccept = (Button) view.findViewById(R.id.bAccept);
boolean testnet = WalletManager.getInstance().isTestNet();
boolean testnet = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
tvWalletMnemonic.setTextIsSelectable(testnet);
tvWalletSpendKey.setTextIsSelectable(testnet);
@@ -185,6 +186,7 @@ public class GenerateReviewFragment extends Fragment {
name = wallet.getName();
status = wallet.getStatus();
if (status != Wallet.Status.Status_Ok) {
Timber.e(wallet.getErrorString());
if (closeWallet) wallet.close();
return false;
}

File diff suppressed because it is too large Load Diff

View File

@@ -45,6 +45,7 @@ import android.widget.Toast;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.NodeList;
@@ -81,11 +82,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
File getStorageRoot();
boolean onWalletSelected(String wallet, String daemon, boolean testnet);
boolean onWalletSelected(String wallet, String daemon);
void onWalletDetails(String wallet, boolean testnet);
void onWalletDetails(String wallet);
void onWalletReceive(String wallet, boolean testnet);
void onWalletReceive(String wallet);
void onWalletRename(String name);
@@ -93,14 +94,16 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void onWalletArchive(String walletName);
void onAddWallet(boolean testnet, String type);
void onAddWallet(String type);
void showNet(boolean testnet);
void showNet();
void setToolbarButton(int type);
void setTitle(String title);
void setNetworkType(NetworkType networkType);
}
@Override
@@ -126,8 +129,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
super.onResume();
Timber.d("onResume()");
activityCallback.setTitle(null);
activityCallback.setToolbarButton(Toolbar.BUTTON_DONATE);
activityCallback.showNet(isTestnet());
activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
activityCallback.showNet();
}
@Override
@@ -244,13 +247,13 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
// Callbacks from WalletInfoAdapter
@Override
public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) {
String x = isTestnet() ? "9A-" : "4-";
if (x.indexOf(infoItem.address.charAt(0)) < 0) {
String addressPrefix = addressPrefix();
if (addressPrefix.indexOf(infoItem.address.charAt(0)) < 0) {
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
return;
}
if (activityCallback.onWalletSelected(infoItem.name, getDaemon(), isTestnet())) {
if (activityCallback.onWalletSelected(infoItem.name, getDaemon())) {
savePrefs();
}
}
@@ -279,11 +282,24 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
return true;
}
private String addressPrefix() {
switch (WalletManager.getInstance().getNetworkType()) {
case NetworkType_Testnet:
return "9A-";
case NetworkType_Mainnet:
return "4-";
case NetworkType_Stagenet:
return "5-";
default:
throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType());
}
}
private void filterList() {
displayedList.clear();
String x = isTestnet() ? "9A" : "4";
String addressPrefix = addressPrefix();
for (WalletManager.WalletInfo s : walletList) {
if (x.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s);
if (addressPrefix.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s);
}
}
@@ -313,11 +329,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
private void showInfo(@NonNull String name) {
activityCallback.onWalletDetails(name, isTestnet());
activityCallback.onWalletDetails(name);
}
private void showReceive(@NonNull String name) {
activityCallback.onWalletReceive(name, isTestnet());
activityCallback.onWalletReceive(name);
}
@Override
@@ -329,29 +345,31 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.list_menu, menu);
menu.findItem(R.id.action_testnet).setChecked(isTestnet());
menu.findItem(R.id.action_testnet).setChecked(testnetCheckMenu);
super.onCreateOptionsMenu(menu, inflater);
}
private boolean testnet = BuildConfig.DEBUG;
private boolean testnetCheckMenu = BuildConfig.DEBUG;
boolean isTestnet() {
return testnet;
}
//boolean isTestnet() {
// return testnet;
//}
public boolean onTestnetMenuItem() {
boolean lastState = testnet;
boolean lastState = testnetCheckMenu;
setNet(!lastState, true); // set and save
return !lastState;
}
public void setNet(boolean testnet, boolean save) {
this.testnet = testnet;
activityCallback.showNet(testnet);
public void setNet(boolean testnetChecked, boolean save) {
this.testnetCheckMenu = testnetChecked;
NetworkType net = testnetChecked ? NetworkType.NetworkType_Testnet : NetworkType.NetworkType_Mainnet;
activityCallback.setNetworkType(net);
activityCallback.showNet();
if (save) {
savePrefs(true); // use previous state as we just clicked it
}
if (testnet) {
if (testnetChecked) {
setDaemon(daemonTestNet);
} else {
setDaemon(daemonMainNet);
@@ -379,7 +397,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
daemonMainNet = new NodeList(sharedPref.getString(PREF_DAEMON_MAINNET, PREF_DAEMONLIST_MAINNET));
daemonTestNet = new NodeList(sharedPref.getString(PREF_DAEMON_TESTNET, PREF_DAEMONLIST_TESTNET));
setNet(isTestnet(), false);
setNet(testnetCheckMenu, false);
showXmrtoEnabled = sharedPref.getBoolean(PREF_SHOW_XMRTO_ENABLED, true);
}
@@ -398,7 +416,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void savePrefs(boolean usePreviousTestnetState) {
Timber.d("SAVE / %s", usePreviousTestnetState);
// save the daemon address for the net
boolean testnet = isTestnet() ^ usePreviousTestnetState;
boolean testnet = testnetCheckMenu ^ usePreviousTestnetState;
String daemon = getDaemon();
if (testnet) {
daemonTestNet.setRecent(daemon);
@@ -484,19 +502,19 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
case R.id.fabNew:
fabScreen.setVisibility(View.INVISIBLE);
isFabOpen = false;
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_NEW);
activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
break;
case R.id.fabView:
animateFAB();
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_VIEWONLY);
activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY);
break;
case R.id.fabKey:
animateFAB();
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_KEY);
activityCallback.onAddWallet(GenerateFragment.TYPE_KEY);
break;
case R.id.fabSeed:
animateFAB();
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_SEED);
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
break;
case R.id.fabScreen:
animateFAB();

View File

@@ -300,7 +300,7 @@ public class ReceiveFragment extends Fragment {
String paymentId = etPaymentId.getEditText().getText().toString();
String xmrAmount = evAmount.getAmount();
Timber.d("%s/%s/%s", xmrAmount, paymentId, address);
if ((xmrAmount == null) || !Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet())) {
if ((xmrAmount == null) || !Wallet.isAddressValid(address)) {
clearQR();
Timber.d("CLEARQR");
return;

View File

@@ -36,7 +36,7 @@ import android.widget.Toast;
import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.dialog.DonationFragment;
import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
import com.m2049r.xmrwallet.fragment.send.SendFragment;
@@ -141,7 +141,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
@Override
protected void onDestroy() {
Timber.d("onDestroy()");
if (!isSynced()) {
if ((mBoundService != null) && !isSynced() && (getWallet() != null)) {
saveWallet();
}
stopWalletService();
@@ -159,8 +159,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
case R.id.action_info:
onWalletDetails();
return true;
case R.id.action_donate:
DonationFragment.display(getSupportFragmentManager());
case R.id.action_credits:
CreditsFragment.display(getSupportFragmentManager());
return true;
case R.id.action_share:
onShareTxInfo();
@@ -213,8 +213,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
case Toolbar.BUTTON_CLOSE:
finish();
break;
case Toolbar.BUTTON_DONATE:
Toast.makeText(WalletActivity.this, getString(R.string.label_donate), Toast.LENGTH_SHORT).show();
case Toolbar.BUTTON_CREDITS:
Toast.makeText(WalletActivity.this, getString(R.string.label_credits), Toast.LENGTH_SHORT).show();
case Toolbar.BUTTON_NONE:
default:
Timber.e("Button " + type + "pressed - how can this be?");
@@ -222,12 +222,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
}
});
boolean testnet = WalletManager.getInstance().isTestNet();
if (testnet) {
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
} else {
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet);
}
showNet();
Fragment walletFragment = new WalletFragment();
getSupportFragmentManager().beginTransaction()
@@ -238,6 +233,23 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
Timber.d("onCreate() done.");
}
public void showNet() {
switch (WalletManager.getInstance().getNetworkType()) {
case NetworkType_Mainnet:
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet);
break;
case NetworkType_Testnet:
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
break;
case NetworkType_Stagenet:
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
break;
default:
throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType());
}
}
public Wallet getWallet() {
if (mBoundService == null) throw new IllegalStateException("WalletService not bound.");
return mBoundService.getWallet();
@@ -798,7 +810,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
@Override
public boolean verifyWalletPassword(String password) {
String walletPath = new File(Helper.getStorageRoot(this),
String walletPath = new File(Helper.getWalletRoot(this),
getWalletName() + ".keys").getAbsolutePath();
return WalletManager.getInstance().verifyWalletPassword(walletPath, password, true);
}

View File

@@ -19,13 +19,15 @@ package com.m2049r.xmrwallet;
import android.app.Application;
import com.m2049r.xmrwallet.util.Helper;
import timber.log.Timber;
public class XmrWalletApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}

View File

@@ -18,6 +18,7 @@ package com.m2049r.xmrwallet.data;
import android.net.Uri;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
@@ -178,7 +179,7 @@ public class BarcodeData {
return null; // we have an amount but its not a number!
}
}
if (!BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet())) {
if (!BitcoinAddressValidator.validate(address)) {
Timber.d("address invalid");
return null;
}
@@ -190,7 +191,7 @@ public class BarcodeData {
if (address == null) return null;
if (!BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet())) {
if (!BitcoinAddressValidator.validate(address)) {
Timber.d("address invalid");
return null;
}

View File

@@ -0,0 +1,112 @@
/*
* 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();
}
}

View File

@@ -28,16 +28,14 @@ import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.Helper;
public class DonationFragment extends DialogFragment {
public class CreditsFragment extends DialogFragment {
static final String TAG = "DonationFragment";
public static DonationFragment newInstance() {
return new DonationFragment();
public static CreditsFragment newInstance() {
return new CreditsFragment();
}
public static void display(FragmentManager fm) {
@@ -47,24 +45,14 @@ public class DonationFragment extends DialogFragment {
ft.remove(prev);
}
DonationFragment.newInstance().show(ft, TAG);
CreditsFragment.newInstance().show(ft, TAG);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_donation, null);
final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_credits, null);
((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.donation_credits)));
(view.findViewById(R.id.bCopyAddress)).
setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_address),
((TextView) view.findViewById(R.id.tvWalletAddress)).getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
}
});
((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.credits_text)));
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(view);

View File

@@ -212,7 +212,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private boolean checkAddressNoError() {
String address = etAddress.getEditText().getText().toString();
return Wallet.isAddressValid(address)
|| BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet());
|| BitcoinAddressValidator.validate(address);
}
private boolean checkAddress() {
@@ -228,13 +228,13 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private boolean isIntegratedAddress() {
String address = etAddress.getEditText().getText().toString();
return (address.length() == INTEGRATED_ADDRESS_LENGTH)
&& Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet());
&& Wallet.isAddressValid(address);
}
private boolean isBitcoinAddress() {
String address = etAddress.getEditText().getText().toString();
if ((address.length() >= 27) && (address.length() <= 34))
return BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet());
return BitcoinAddressValidator.validate(address);
else
return false;
}

View File

@@ -54,7 +54,8 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
TxData getTxData();
}
final static int Mixins[] = {4, 7, 12, 25}; // must match the layout XML
// Mixin = Ringsize - 1
final static int Mixins[] = {6, 9, 12, 25}; // must match the layout XML / "@array/mixin"
final static PendingTransaction.Priority Priorities[] =
{PendingTransaction.Priority.Priority_Default,
PendingTransaction.Priority.Priority_Low,

View File

@@ -77,7 +77,7 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
@Override
public void onClick(View v) {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_send_txid), tvTxId.getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
Toast.makeText(getActivity(), getString(R.string.message_copy_txid), Toast.LENGTH_SHORT).show();
}
});

View File

@@ -137,12 +137,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
ivTxType.setVisibility(View.GONE); // gives us more space for the amount
}
long realAmount = infoItem.amount;
if (infoItem.isPending) {
realAmount = realAmount - infoItem.fee;
}
String displayAmount = Helper.getDisplayAmount(realAmount, Helper.DISPLAY_DIGITS_INFO);
String displayAmount = Helper.getDisplayAmount(infoItem.amount, Helper.DISPLAY_DIGITS_INFO);
if (infoItem.direction == TransactionInfo.Direction.Direction_Out) {
tvAmount.setText(context.getString(R.string.tx_list_amount_negative, displayAmount));
} else {

View File

@@ -0,0 +1,45 @@
/*
* 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.model;
public enum NetworkType {
NetworkType_Mainnet(0),
NetworkType_Testnet(1),
NetworkType_Stagenet(2);
public static NetworkType fromInteger(int n) {
switch (n) {
case 0:
return NetworkType_Mainnet;
case 1:
return NetworkType_Testnet;
case 2:
return NetworkType_Stagenet;
}
return null;
}
public int getValue() {
return value;
}
private int value;
NetworkType(int value) {
this.value = value;
}
}

View File

@@ -84,7 +84,14 @@ public class PendingTransaction {
public native long getFee();
public native String getFirstTxId();
public String getFirstTxId() {
String id = getFirstTxIdJ();
if (id == null)
throw new IndexOutOfBoundsException();
return id;
}
public native String getFirstTxIdJ();
public native long getTxCount();

View File

@@ -79,7 +79,11 @@ public class Wallet {
public native String getPath();
public native boolean isTestNet();
public NetworkType getNetworkType() {
return NetworkType.fromInteger(nettype());
}
public native int nettype();
//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
@@ -155,10 +159,10 @@ public class Wallet {
public static native boolean isPaymentIdValid(String payment_id);
public static boolean isAddressValid(String address) {
return isAddressValid(address, WalletManager.getInstance().isTestNet());
return isAddressValid(address, WalletManager.getInstance().getNetworkType().getValue());
}
public static native boolean isAddressValid(String address, boolean isTestNet);
public static native boolean isAddressValid(String address, int networkType);
//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);

View File

@@ -16,6 +16,8 @@
package com.m2049r.xmrwallet.model;
import com.m2049r.xmrwallet.data.WalletNode;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
@@ -39,6 +41,7 @@ public class WalletManager {
if (WalletManager.Instance == null) {
WalletManager.Instance = new WalletManager();
}
return WalletManager.Instance;
}
@@ -69,22 +72,22 @@ public class WalletManager {
}
public Wallet createWallet(File aFile, String password, String language) {
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, isTestNet());
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, getNetworkType().getValue());
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet);
return wallet;
}
private native long createWalletJ(String path, String password, String language, boolean isTestNet);
private native long createWalletJ(String path, String password, String language, int networkType);
public Wallet openWallet(String path, String password) {
long walletHandle = openWalletJ(path, password, isTestNet());
long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet);
return wallet;
}
private native long openWalletJ(String path, String password, boolean isTestNet);
private native long openWalletJ(String path, String password, int networkType);
public Wallet recoveryWallet(File aFile, String password, String mnemonic) {
return recoveryWallet(aFile, password, mnemonic, 0);
@@ -92,28 +95,28 @@ public class WalletManager {
public Wallet recoveryWallet(File aFile, String password, String mnemonic, long restoreHeight) {
long walletHandle = recoveryWalletJ(aFile.getAbsolutePath(), password, mnemonic,
isTestNet(), restoreHeight);
getNetworkType().getValue(), restoreHeight);
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet);
return wallet;
}
private native long recoveryWalletJ(String path, String password, String mnemonic,
boolean isTestNet, long restoreHeight);
int networkType, long restoreHeight);
public Wallet createWalletWithKeys(File aFile, String password, String language, long restoreHeight,
String addressString, String viewKeyString, String spendKeyString) {
long walletHandle = createWalletWithKeysJ(aFile.getAbsolutePath(), password,
language, isTestNet(), restoreHeight,
long walletHandle = createWalletFromKeysJ(aFile.getAbsolutePath(), password,
language, getNetworkType().getValue(), restoreHeight,
addressString, viewKeyString, spendKeyString);
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet);
return wallet;
}
private native long createWalletWithKeysJ(String path, String password,
private native long createWalletFromKeysJ(String path, String password,
String language,
boolean isTestNet,
int networkType,
long restoreHeight,
String addressString,
String viewKeyString,
@@ -204,24 +207,23 @@ public class WalletManager {
//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;
private String daemonAddress = null;
private boolean testnet = true;
private NetworkType networkType = null;
public boolean isTestNet() {
if (daemonAddress == null) {
return true;
// assume testnet not explicitly initialised
//throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
}
return testnet;
public NetworkType getNetworkType() {
return networkType;
}
public void setDaemon(String address, boolean testnet, String username, String password) {
//Timber.d("SETDAEMON " + username + "/" + password + "/" + address);
this.daemonAddress = address;
this.testnet = testnet;
this.daemonUsername = username;
this.daemonPassword = password;
setDaemonAddressJ(address);
//public void setDaemon(String address, NetworkType networkType, String username, String password) {
public void setDaemon(WalletNode walletNode) {
this.daemonAddress = walletNode.getAddress();
this.networkType = walletNode.getNetworkType();
this.daemonUsername = walletNode.getUsername();
this.daemonPassword = walletNode.getPassword();
setDaemonAddressJ(daemonAddress);
}
public void setNetworkType(NetworkType networkType) {
this.networkType = networkType;
}
public String getDaemonAddress() {
@@ -268,5 +270,17 @@ public class WalletManager {
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
static public native void initLogger(String argv0, String defaultLogBaseName);
//TODO: maybe put these in an enum like in monero core - but why?
static public int LOGLEVEL_SILENT = -1;
static public int LOGLEVEL_WARN = 0;
static public int LOGLEVEL_INFO = 1;
static public int LOGLEVEL_DEBUG = 2;
static public int LOGLEVEL_TRACE = 3;
static public int LOGLEVEL_MAX = 4;
static public native void setLogLevel(int level);
static public native void logDebug(String category, String message);
static public native void logInfo(String category, String message);
static public native void logWarning(String category, String message);
static public native void logError(String category, String message);
}

View File

@@ -524,7 +524,7 @@ public class WalletService extends Service {
showProgress(20);
Wallet wallet = null;
WalletManager walletMgr = WalletManager.getInstance();
Timber.d("WalletManager testnet=%s", walletMgr.isTestNet());
Timber.d("WalletManager network=%s", walletMgr.getNetworkType().name());
showProgress(30);
if (walletMgr.walletExists(path)) {
Timber.d("open wallet %s", path);

View File

@@ -18,6 +18,9 @@ package com.m2049r.xmrwallet.util;
// based on https://rosettacode.org/wiki/Bitcoin/address_validation#Java
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.WalletManager;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -27,6 +30,11 @@ public class BitcoinAddressValidator {
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
public static boolean validate(String addrress) {
return validate(addrress,
WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet);
}
public static boolean validate(String addrress, boolean testnet) {
if (addrress.length() < 26 || addrress.length() > 35)
return false;
@@ -34,10 +42,11 @@ public class BitcoinAddressValidator {
if (decoded == null)
return false;
int v = decoded[0] & 0xFF;
if (!testnet) {
if ((decoded[0] != 0x00) && (decoded[0] != 0x05)) return false;
if ((v != 0x00) && (v != 0x05)) return false;
} else {
if ((decoded[0] != 0x6f) && (decoded[0] != 0xc4)) return false;
if ((v != 0x6f) && (v != 0xc4)) return false;
}
byte[] hash1 = sha256(Arrays.copyOfRange(decoded, 0, 21));
@@ -57,7 +66,11 @@ public class BitcoinAddressValidator {
byte[] result = new byte[25];
byte[] numBytes = num.toByteArray();
System.arraycopy(numBytes, 0, result, result.length - numBytes.length, numBytes.length);
if (num.bitLength() == 200) {
System.arraycopy(numBytes, 1, result, 0, 25);
} else {
System.arraycopy(numBytes, 0, result, result.length - numBytes.length, numBytes.length);
}
return result;
}

View File

@@ -31,12 +31,16 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Environment;
import android.support.v4.content.ContextCompat;
import android.system.ErrnoException;
import android.system.Os;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
@@ -55,16 +59,21 @@ import timber.log.Timber;
public class Helper {
static private final String WALLET_DIR = "monerujo";
static private final String HOME_DIR = "monero";
static public int DISPLAY_DIGITS_INFO = 5;
static public File getStorageRoot(Context context) {
static public File getWalletRoot(Context context) {
return getStorage(context, WALLET_DIR);
}
static public File getStorage(Context context, String folderName) {
if (!isExternalStorageWritable()) {
String msg = context.getString(R.string.message_strorage_not_writable);
Timber.e(msg);
throw new IllegalStateException(msg);
}
File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR);
File dir = new File(Environment.getExternalStorageDirectory(), folderName);
if (!dir.exists()) {
Timber.i("Creating %s", dir.getAbsolutePath());
dir.mkdirs(); // try to make it
@@ -114,9 +123,9 @@ public class Helper {
}
static public File getWalletFile(Context context, String aWalletName) {
File walletDir = getStorageRoot(context);
File walletDir = getWalletRoot(context);
File f = new File(walletDir, aWalletName);
Timber.d("wallet= %s size= %d", f.getAbsolutePath(), f.length());
Timber.d("wallet=%s size= %d", f.getAbsolutePath(), f.length());
return f;
}
@@ -263,10 +272,35 @@ public class Helper {
}
static public HttpUrl getXmrToBaseUrl() {
if ((WalletManager.getInstance() == null) || WalletManager.getInstance().isTestNet()) {
if ((WalletManager.getInstance() == null)
|| (WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet)) {
return HttpUrl.parse("https://test.xmr.to/api/v2/xmr2btc/");
} else {
return HttpUrl.parse("https://xmr.to/api/v2/xmr2btc/");
}
}
static public void setMoneroHome(Context context) {
try {
String home = getStorage(context, HOME_DIR).getAbsolutePath();
Os.setenv("HOME", home, true);
} catch (ErrnoException ex) {
throw new IllegalStateException(ex);
}
}
static public void initLogger(Context context) {
if (BuildConfig.DEBUG) {
initLogger(context, WalletManager.LOGLEVEL_DEBUG);
}
// no logger if not debug
}
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
static public void initLogger(Context context, int level) {
String home = getStorage(context, HOME_DIR).getAbsolutePath();
WalletManager.initLogger(home + "/monerujo", "monerujo.log");
if (level >= WalletManager.LOGLEVEL_SILENT)
WalletManager.setLogLevel(level);
}
}

View File

@@ -0,0 +1,152 @@
/*
* 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.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
public class RestoreHeight {
static private RestoreHeight Singleton = null;
static public RestoreHeight getInstance() {
if (Singleton == null) {
synchronized (RestoreHeight.class) {
if (Singleton == null) {
Singleton = new RestoreHeight();
}
}
}
return Singleton;
}
private Map<String, Long> blockheight = new HashMap<>();
RestoreHeight() {
blockheight.put("2014-05-01", 18844L);
blockheight.put("2014-06-01", 65406L);
blockheight.put("2014-07-01", 108882L);
blockheight.put("2014-08-01", 153594L);
blockheight.put("2014-09-01", 198072L);
blockheight.put("2014-10-01", 241088L);
blockheight.put("2014-11-01", 285305L);
blockheight.put("2014-12-01", 328069L);
blockheight.put("2015-01-01", 372369L);
blockheight.put("2015-02-01", 416505L);
blockheight.put("2015-03-01", 456631L);
blockheight.put("2015-04-01", 501084L);
blockheight.put("2015-05-01", 543973L);
blockheight.put("2015-06-01", 588326L);
blockheight.put("2015-07-01", 631187L);
blockheight.put("2015-08-01", 675484L);
blockheight.put("2015-09-01", 719725L);
blockheight.put("2015-10-01", 762463L);
blockheight.put("2015-11-01", 806528L);
blockheight.put("2015-12-01", 849041L);
blockheight.put("2016-01-01", 892866L);
blockheight.put("2016-02-01", 936736L);
blockheight.put("2016-03-01", 977691L);
blockheight.put("2016-04-01", 1015848L);
blockheight.put("2016-05-01", 1037417L);
blockheight.put("2016-06-01", 1059651L);
blockheight.put("2016-07-01", 1081269L);
blockheight.put("2016-08-01", 1103630L);
blockheight.put("2016-09-01", 1125983L);
blockheight.put("2016-10-01", 1147617L);
blockheight.put("2016-11-01", 1169779L);
blockheight.put("2016-12-01", 1191402L);
blockheight.put("2017-01-01", 1213861L);
blockheight.put("2017-02-01", 1236197L);
blockheight.put("2017-03-01", 1256358L);
blockheight.put("2017-04-01", 1278622L);
blockheight.put("2017-05-01", 1300239L);
blockheight.put("2017-06-01", 1322564L);
blockheight.put("2017-07-01", 1344225L);
blockheight.put("2017-08-01", 1366664L);
blockheight.put("2017-09-01", 1389113L);
blockheight.put("2017-10-01", 1410738L);
blockheight.put("2017-11-01", 1433039L);
blockheight.put("2017-12-01", 1454639L);
blockheight.put("2018-01-01", 1477201L);
blockheight.put("2018-02-01", 1499599L);
}
public long getHeight(String date) {
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
parser.setTimeZone(TimeZone.getTimeZone("UTC"));
parser.setLenient(false);
try {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.set(Calendar.DST_OFFSET, 0);
cal.setTime(parser.parse(date));
cal.add(Calendar.DAY_OF_MONTH, -4); // give it some leeway
if (cal.get(Calendar.YEAR) < 2014)
return 1;
if ((cal.get(Calendar.YEAR) == 2014) && (cal.get(Calendar.MONTH) <= 3))
// before May 2014
return 1;
Calendar query = (Calendar) cal.clone();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
cal.set(Calendar.DAY_OF_MONTH, 1);
long prevTime = cal.getTimeInMillis();
String prevDate = formatter.format(prevTime);
// lookup blockheight at first of the month
Long prevBc = blockheight.get(prevDate);
if (prevBc == null) {
// if too recent, go back in time and find latest one we have
while (prevBc == null) {
cal.add(Calendar.MONTH, -1);
if (cal.get(Calendar.YEAR) < 2014) {
throw new IllegalStateException("endless loop looking for blockheight");
}
prevTime = cal.getTimeInMillis();
prevDate = formatter.format(prevTime);
prevBc = blockheight.get(prevDate);
}
}
long height = prevBc;
// now we have a blockheight & a date ON or BEFORE the restore date requested
if (date.equals(prevDate)) return height;
// see if we have a blockheight after this date
cal.add(Calendar.MONTH, 1);
long nextTime = cal.getTimeInMillis();
String nextDate = formatter.format(nextTime);
Long nextBc = blockheight.get(nextDate);
if (nextBc != null) { // we have a range - interpolate the blockheight we are looking for
long diff = nextBc - prevBc;
long diffDays = TimeUnit.DAYS.convert(nextTime - prevTime, TimeUnit.MILLISECONDS);
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime,
TimeUnit.MILLISECONDS);
height = Math.round(prevBc + diff * (1.0 * days / diffDays));
} else {
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime,
TimeUnit.MILLISECONDS);
height = Math.round(prevBc + 1.0 * days * (24 * 60 / 2));
}
return height;
} catch (ParseException ex) {
throw new IllegalArgumentException(ex);
}
}
}

View File

@@ -45,7 +45,7 @@ public class Toolbar extends android.support.v7.widget.Toolbar {
ImageView toolbarImage;
TextView toolbarTitle;
TextView toolbarSubtitle;
Button bDonate;
Button bCredits;
public Toolbar(Context context) {
super(context);
@@ -87,8 +87,8 @@ public class Toolbar extends android.support.v7.widget.Toolbar {
toolbarTitle = (TextView) findViewById(R.id.toolbarTitle);
toolbarSubtitle = (TextView) findViewById(R.id.toolbarSubtitle);
bDonate = (Button) findViewById(R.id.bDonate);
bDonate.setOnClickListener(new View.OnClickListener() {
bCredits = (Button) findViewById(R.id.bCredits);
bCredits.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (onButtonListener != null) {
onButtonListener.onButton(buttonType);
@@ -116,43 +116,43 @@ public class Toolbar extends android.support.v7.widget.Toolbar {
public final static int BUTTON_NONE = 0;
public final static int BUTTON_BACK = 1;
public final static int BUTTON_CLOSE = 2;
public final static int BUTTON_DONATE = 3;
public final static int BUTTON_CREDITS = 3;
public final static int BUTTON_CANCEL = 4;
int buttonType = BUTTON_DONATE;
int buttonType = BUTTON_CREDITS;
public void setButton(int type) {
switch (type) {
case BUTTON_BACK:
Timber.d("BUTTON_BACK");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_arrow_back_white_24dp, 0, 0, 0);
bDonate.setText(null);
bDonate.setVisibility(View.VISIBLE);
bCredits.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_arrow_back_white_24dp, 0, 0, 0);
bCredits.setText(null);
bCredits.setVisibility(View.VISIBLE);
break;
case BUTTON_CLOSE:
Timber.d("BUTTON_CLOSE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0);
bDonate.setText(R.string.label_close);
bDonate.setVisibility(View.VISIBLE);
bCredits.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0);
bCredits.setText(R.string.label_close);
bCredits.setVisibility(View.VISIBLE);
break;
case BUTTON_DONATE:
Timber.d("BUTTON_DONATE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_favorite_white_24dp, 0, 0, 0);
bDonate.setText(R.string.label_donate);
bDonate.setVisibility(View.VISIBLE);
case BUTTON_CREDITS:
Timber.d("BUTTON_CREDITS");
bCredits.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_favorite_white_24dp, 0, 0, 0);
bCredits.setText(R.string.label_credits);
bCredits.setVisibility(View.VISIBLE);
break;
case BUTTON_CANCEL:
Timber.d("BUTTON_CANCEL");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0);
bDonate.setText(R.string.label_cancel);
bDonate.setVisibility(View.VISIBLE);
bCredits.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0);
bCredits.setText(R.string.label_cancel);
bCredits.setVisibility(View.VISIBLE);
break;
case BUTTON_NONE:
default:
Timber.d("BUTTON_NONE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
bDonate.setText(null);
bDonate.setVisibility(View.INVISIBLE);
bCredits.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
bCredits.setText(null);
bCredits.setVisibility(View.INVISIBLE);
}
buttonType = type;
}

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
android:src="@drawable/gunther_coder" />
<TextView
android:id="@+id/tvCredits"
style="@style/MoneroText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/header_top"
android:autoLink="web"
android:gravity="center"
android:text="@string/credits_text" />
</LinearLayout>
</ScrollView>

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
android:src="@drawable/gunther_donate" />
<TextView
style="@style/MoneroText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/donation_text"
android:textSize="14sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/header_top"
android:background="@drawable/backgound_spinner"
android:orientation="vertical"
android:padding="@dimen/header_top">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
style="@style/MoneroLabel.Heading.Donation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:text="@string/donation_address_label" />
<ImageButton
android:id="@+id/bCopyAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginEnd="8dp"
android:background="?android:selectableItemBackground"
android:src="@drawable/ic_content_copy_black_24dp" />
</FrameLayout>
<TextView
android:id="@+id/tvWalletAddress"
style="@style/MoneroText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/data_top"
android:text="@string/donation_address"
android:textAlignment="center"
android:textColor="@color/moneroBlack"
android:textSize="17sp" />
</LinearLayout>
<TextView
android:id="@+id/tvCredits"
style="@style/MoneroText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/header_top"
android:autoLink="web"
android:gravity="center"
android:text="@string/donation_credits" />
</LinearLayout>
</ScrollView>

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