diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..f8b92c3a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.gradle
+build
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..a7c382ed
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1 @@
+workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 00000000..373175e3
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+xmrwallet
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 00000000..96cc43ef
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 00000000..e7bedf33
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 00000000..7ac24c77
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/animated_vector_drawable_25_3_1.xml b/.idea/libraries/animated_vector_drawable_25_3_1.xml
new file mode 100644
index 00000000..78eeffce
--- /dev/null
+++ b/.idea/libraries/animated_vector_drawable_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/appcompat_v7_25_3_1.xml b/.idea/libraries/appcompat_v7_25_3_1.xml
new file mode 100644
index 00000000..f1d4de56
--- /dev/null
+++ b/.idea/libraries/appcompat_v7_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/cardview_v7_25_3_1.xml b/.idea/libraries/cardview_v7_25_3_1.xml
new file mode 100644
index 00000000..beadd4a8
--- /dev/null
+++ b/.idea/libraries/cardview_v7_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/constraint_layout_1_0_2.xml b/.idea/libraries/constraint_layout_1_0_2.xml
new file mode 100644
index 00000000..ae7ec8d9
--- /dev/null
+++ b/.idea/libraries/constraint_layout_1_0_2.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/constraint_layout_solver_1_0_2.xml b/.idea/libraries/constraint_layout_solver_1_0_2.xml
new file mode 100644
index 00000000..75255581
--- /dev/null
+++ b/.idea/libraries/constraint_layout_solver_1_0_2.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/design_25_3_1.xml b/.idea/libraries/design_25_3_1.xml
new file mode 100644
index 00000000..90e013af
--- /dev/null
+++ b/.idea/libraries/design_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/espresso_core_2_2_2.xml b/.idea/libraries/espresso_core_2_2_2.xml
new file mode 100644
index 00000000..1fe68a26
--- /dev/null
+++ b/.idea/libraries/espresso_core_2_2_2.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/espresso_idling_resource_2_2_2.xml b/.idea/libraries/espresso_idling_resource_2_2_2.xml
new file mode 100644
index 00000000..df91204f
--- /dev/null
+++ b/.idea/libraries/espresso_idling_resource_2_2_2.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/exposed_instrumentation_api_publish_0_5.xml b/.idea/libraries/exposed_instrumentation_api_publish_0_5.xml
new file mode 100644
index 00000000..16e1b54c
--- /dev/null
+++ b/.idea/libraries/exposed_instrumentation_api_publish_0_5.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/hamcrest_core_1_3.xml b/.idea/libraries/hamcrest_core_1_3.xml
new file mode 100644
index 00000000..157e3f34
--- /dev/null
+++ b/.idea/libraries/hamcrest_core_1_3.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/hamcrest_integration_1_3.xml b/.idea/libraries/hamcrest_integration_1_3.xml
new file mode 100644
index 00000000..58b2c4b3
--- /dev/null
+++ b/.idea/libraries/hamcrest_integration_1_3.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/hamcrest_library_1_3.xml b/.idea/libraries/hamcrest_library_1_3.xml
new file mode 100644
index 00000000..676cc632
--- /dev/null
+++ b/.idea/libraries/hamcrest_library_1_3.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/javawriter_2_1_1.xml b/.idea/libraries/javawriter_2_1_1.xml
new file mode 100644
index 00000000..a66fefb7
--- /dev/null
+++ b/.idea/libraries/javawriter_2_1_1.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/javax_annotation_api_1_2.xml b/.idea/libraries/javax_annotation_api_1_2.xml
new file mode 100644
index 00000000..811e73fe
--- /dev/null
+++ b/.idea/libraries/javax_annotation_api_1_2.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/javax_inject_1.xml b/.idea/libraries/javax_inject_1.xml
new file mode 100644
index 00000000..0d1d5fc9
--- /dev/null
+++ b/.idea/libraries/javax_inject_1.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/jsr305_2_0_1.xml b/.idea/libraries/jsr305_2_0_1.xml
new file mode 100644
index 00000000..cdf98782
--- /dev/null
+++ b/.idea/libraries/jsr305_2_0_1.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/junit_4_12.xml b/.idea/libraries/junit_4_12.xml
new file mode 100644
index 00000000..305df301
--- /dev/null
+++ b/.idea/libraries/junit_4_12.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/recyclerview_v7_25_3_1.xml b/.idea/libraries/recyclerview_v7_25_3_1.xml
new file mode 100644
index 00000000..6b856b38
--- /dev/null
+++ b/.idea/libraries/recyclerview_v7_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/rules_0_5.xml b/.idea/libraries/rules_0_5.xml
new file mode 100644
index 00000000..0be4378d
--- /dev/null
+++ b/.idea/libraries/rules_0_5.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/runner_0_5.xml b/.idea/libraries/runner_0_5.xml
new file mode 100644
index 00000000..0d922490
--- /dev/null
+++ b/.idea/libraries/runner_0_5.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/support_annotations_25_3_1.xml b/.idea/libraries/support_annotations_25_3_1.xml
new file mode 100644
index 00000000..beb3941f
--- /dev/null
+++ b/.idea/libraries/support_annotations_25_3_1.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/support_compat_25_3_1.xml b/.idea/libraries/support_compat_25_3_1.xml
new file mode 100644
index 00000000..92ae6747
--- /dev/null
+++ b/.idea/libraries/support_compat_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/support_core_ui_25_3_1.xml b/.idea/libraries/support_core_ui_25_3_1.xml
new file mode 100644
index 00000000..46bcc7fe
--- /dev/null
+++ b/.idea/libraries/support_core_ui_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/support_core_utils_25_3_1.xml b/.idea/libraries/support_core_utils_25_3_1.xml
new file mode 100644
index 00000000..a54f87f6
--- /dev/null
+++ b/.idea/libraries/support_core_utils_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/support_fragment_25_3_1.xml b/.idea/libraries/support_fragment_25_3_1.xml
new file mode 100644
index 00000000..1c87b7a4
--- /dev/null
+++ b/.idea/libraries/support_fragment_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/support_media_compat_25_3_1.xml b/.idea/libraries/support_media_compat_25_3_1.xml
new file mode 100644
index 00000000..03ab57b7
--- /dev/null
+++ b/.idea/libraries/support_media_compat_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/support_v4_25_3_1.xml b/.idea/libraries/support_v4_25_3_1.xml
new file mode 100644
index 00000000..194e0c44
--- /dev/null
+++ b/.idea/libraries/support_v4_25_3_1.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/support_vector_drawable_25_3_1.xml b/.idea/libraries/support_vector_drawable_25_3_1.xml
new file mode 100644
index 00000000..c4a86f49
--- /dev/null
+++ b/.idea/libraries/support_vector_drawable_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/transition_25_3_1.xml b/.idea/libraries/transition_25_3_1.xml
new file mode 100644
index 00000000..0cf2b5ac
--- /dev/null
+++ b/.idea/libraries/transition_25_3_1.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..5d199810
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..dce0d43c
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 00000000..7f68460d
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..94a25f7f
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..bd0557e8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,49 @@
+# Monerujo
+Another Android Monero Wallet
+
+### QUICKSTART
+- Download APK (Release) and install it
+- Copy over synced wallet (all three files) onto sdcard in directory Monerujo (created first time app is started)
+- Start app (again)
+- see the [FAQ](doc/FAQ.md)
+
+### Disclaimer
+This is my first serious Android App.
+
+### Notes
+- Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/
+- Special thanks to /u/gkruja for inspiration to pick this project up again
+- Based off monero v0.10.3.1 with pull requests #2238 & #2239 applied => so can be used in mainnet!
+- currently only android32
+- sorry for my messups in github
+- currently only use is checking incoming/outgoing transactions
+- works in testnet & mainnet (please use your own daemons)
+- takes forever to sync on mainnet (even with own daemon)
+- use your own daemon - it's easy
+- screen stays on until first sync is complete
+
+### TODO
+- make it pretty
+- adjust layout so we can use bigger font sizes
+- provide detailed build instructions for third party binaries
+- visibility of methods/classes
+- sensible error dialogs (e.g. when no write permissions granted) instead of just crashing on purpose
+- ~~sensible loading/saving progress bars instead of just freezing up~~
+- spend monero - not so difficult with wallet api
+- ~~figure out how to make it all flow better (loading/saving takes forever and does not run in background)~~
+- ~~currently loading in background thread produces segfaults in JNI~~
+- check licenses of included libraries
+- License Dialog
+
+### Issues
+- screen rotation crashes the app
+- turning the display off/on during sync stops sync
+
+### HOW TO BUILD
+No need to build. Binaries are included:
+
+- openssl-1.0.2l
+- monero-v0.10.3.1 + pull requests #2238 & #2239
+- boost_1_64_0
+
+If you want to build - fire up Android Studio and build. Also you can rebuild all of the above.
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 00000000..e7838274
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1,2 @@
+.externalNativeBuild
+build
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
new file mode 100644
index 00000000..d971b423
--- /dev/null
+++ b/app/CMakeLists.txt
@@ -0,0 +1,172 @@
+cmake_minimum_required(VERSION 3.4.1)
+message(STATUS ABI_INFO = ${ANDROID_ABI})
+
+add_library( monerujo
+ SHARED
+ src/main/cpp/monerujo.cpp )
+
+set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../external-libs)
+
+############
+# OpenSSL
+############
+
+add_library(crypto STATIC IMPORTED)
+set_target_properties(crypto PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a)
+
+add_library(ssl STATIC IMPORTED)
+set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libssl.a)
+
+############
+# Boost
+############
+
+add_library(boost_chrono STATIC IMPORTED)
+set_target_properties(boost_chrono PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_chrono.a)
+
+add_library(boost_date_time STATIC IMPORTED)
+set_target_properties(boost_date_time PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_date_time.a)
+
+add_library(boost_filesystem STATIC IMPORTED)
+set_target_properties(boost_filesystem PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_filesystem.a)
+
+add_library(boost_program_options STATIC IMPORTED)
+set_target_properties(boost_program_options PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_program_options.a)
+
+add_library(boost_regex STATIC IMPORTED)
+set_target_properties(boost_regex PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_regex.a)
+
+add_library(boost_serialization STATIC IMPORTED)
+set_target_properties(boost_serialization PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_serialization.a)
+
+add_library(boost_system STATIC IMPORTED)
+set_target_properties(boost_system PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_system.a)
+
+add_library(boost_thread STATIC IMPORTED)
+set_target_properties(boost_thread PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_thread.a)
+
+add_library(boost_wserialization STATIC IMPORTED)
+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)
+#############
+
+add_library(wallet STATIC IMPORTED)
+set_target_properties(wallet PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet.a)
+
+add_library(cryptonote_core STATIC IMPORTED)
+set_target_properties(cryptonote_core PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcryptonote_core.a)
+
+add_library(cryptonote_basic STATIC IMPORTED)
+set_target_properties(cryptonote_basic PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcryptonote_basic.a)
+
+add_library(mnemonics STATIC IMPORTED)
+set_target_properties(mnemonics PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libmnemonics.a)
+
+add_library(common STATIC IMPORTED)
+set_target_properties(common PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcommon.a)
+
+add_library(cncrypto STATIC IMPORTED)
+set_target_properties(cncrypto PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcncrypto.a)
+
+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(blockchain_db STATIC IMPORTED)
+set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblockchain_db.a)
+
+add_library(lmdb STATIC IMPORTED)
+set_target_properties(lmdb PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/liblmdb.a)
+
+add_library(easylogging STATIC IMPORTED)
+set_target_properties(easylogging PROPERTIES IMPORTED_LOCATION
+ ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libeasylogging.a)
+
+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)
+
+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)
+
+#############
+# System
+#############
+
+find_library( log-lib log )
+
+include_directories( ${EXTERNAL_LIBS_DIR}/monero/include )
+
+message(STATUS EXTERNAL_LIBS_DIR : ${EXTERNAL_LIBS_DIR})
+
+target_link_libraries( monerujo
+ wallet
+ cryptonote_core
+ cryptonote_basic
+ mnemonics
+ ringct
+ common
+ cncrypto
+
+ blockchain_db
+ lmdb
+ #easylogging # not for 0.10.3.1
+ unbound
+ p2p
+
+ epee
+ blocks
+ miniupnpc
+
+ boost_chrono
+ boost_date_time
+ boost_filesystem
+ boost_program_options
+ boost_regex
+ boost_serialization
+ boost_system
+ boost_thread
+ boost_wserialization
+
+ ssl
+ crypto
+
+ ${log-lib}
+ )
diff --git a/app/app.iml b/app/app.iml
new file mode 100644
index 00000000..b6bdf792
--- /dev/null
+++ b/app/app.iml
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 00000000..096647ec
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,50 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 25
+ buildToolsVersion "25.0.2"
+ defaultConfig {
+ applicationId "com.m2049r.xmrwallet"
+ minSdkVersion 21
+ targetSdkVersion 25
+ versionCode 2
+ versionName "0.2.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ externalNativeBuild {
+ cmake {
+ cppFlags "-std=c++11"
+ arguments '-DANDROID_STL=c++_shared'
+ }
+ }
+ ndk {
+ abiFilters 'armeabi-v7a'
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ path "CMakeLists.txt"
+ }
+ }
+}
+
+dependencies {
+ compile fileTree(include: ['*.jar'], dir: 'libs')
+ androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+ exclude group: 'com.android.support', module: 'support-annotations'
+ })
+
+ compile 'com.android.support:appcompat-v7:25.3.1'
+ compile 'com.android.support:design:25.3.1'
+ compile 'com.android.support.constraint:constraint-layout:1.0.2'
+ compile 'com.android.support:support-v4:25.3.1'
+ compile 'com.android.support:recyclerview-v7:25.3.1'
+ compile 'com.android.support:cardview-v7:25.3.1'
+ testCompile 'junit:junit:4.12'
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 00000000..cb4574ad
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\Users\Test\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..92f895a7
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp
new file mode 100644
index 00000000..a5eda8a0
--- /dev/null
+++ b/app/src/main/cpp/monerujo.cpp
@@ -0,0 +1,1004 @@
+/**
+ * Copyright (c) 2017 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
+ *
+ * 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
+ *
+ * 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.
+ */
+
+#ifndef XMRWALLET_WALLET_LIB_H
+#define XMRWALLET_WALLET_LIB_H
+
+#include
+/*
+#include
+
+#define LOG_TAG "[NDK]"
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
+#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
+*/
+
+jfieldID getHandleField(JNIEnv *env, jobject obj, const char* fieldName = "handle") {
+ jclass c = env->GetObjectClass(obj);
+ return env->GetFieldID(c, fieldName, "J"); // of type long
+}
+
+template
+T *getHandle(JNIEnv *env, jobject obj, const char* fieldName = "handle") {
+ jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName));
+ return reinterpret_cast(handle);
+}
+
+void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) {
+ env->SetLongField(obj, getHandleField(env, obj), handle);
+}
+
+template
+void setHandle(JNIEnv *env, jobject obj, T *t) {
+ jlong handle = reinterpret_cast(t);
+ setHandleFromLong(env, obj, handle);
+}
+
+#endif //XMRWALLET_WALLET_LIB_H
diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
new file mode 100644
index 00000000..e9149c4c
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
@@ -0,0 +1,353 @@
+/**
+ * Copyright (c) 2017 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
+ *
+ * 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
+ *
+ * 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;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
+import com.m2049r.xmrwallet.model.TransactionInfo;
+import com.m2049r.xmrwallet.model.Wallet;
+import com.m2049r.xmrwallet.service.WalletService;
+
+import java.util.List;
+
+public class WalletActivity extends AppCompatActivity
+ implements TransactionInfoAdapter.OnInteractionListener, WalletService.Observer {
+ private static final String TAG = "WalletActivity";
+
+ public static final String REQUEST_ID = "id";
+ public static final String REQUEST_PW = "pw";
+
+ TransactionInfoAdapter adapter;
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Log.d(TAG, "onStart()");
+ acquireWakeLock();
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ String walletId = extras.getString(REQUEST_ID);
+ String walletPassword = extras.getString(REQUEST_PW);
+ connectWalletService(walletId, walletPassword);
+ } else {
+ throw new IllegalStateException("No extras passed! Panic!");
+ }
+ showProgress();
+ final Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ onProgress(10); // look like we are working!
+ }
+ }, 250);
+ //Log.d(TAG, "onStart() done.");
+ }
+
+ private String title = null;
+
+ void setActivityTitle(Wallet wallet) {
+ if ((wallet == null) || (title != null)) return;
+ String shortName = wallet.getName();
+ if (shortName.length() > 16) {
+ shortName = shortName.substring(0, 14) + "...";
+ }
+ this.title = "[" + wallet.getAddress().substring(0, 6) + "] " + shortName;
+ setTitle(this.title);
+ onProgress(100);
+ Log.d(TAG, "wallet title is " + this.title);
+ }
+
+ @Override
+ protected void onStop() {
+ Log.d(TAG, "onStop()");
+ releaseWakeLock();
+ disconnectWalletService();
+ super.onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ Log.d(TAG, "onDestroy()");
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ Log.d(TAG, "onCreate()");
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.wallet_activity);
+
+ // TODO do stuff with savedInstanceState
+ if (savedInstanceState != null) {
+ return;
+ }
+ //Log.d(TAG, "no savedInstanceState");
+
+ RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
+
+ RecyclerView.ItemDecoration itemDecoration = new
+ DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
+ recyclerView.addItemDecoration(itemDecoration);
+
+ this.adapter = new TransactionInfoAdapter(this);
+ recyclerView.setAdapter(adapter);
+
+ setTitle(getString(R.string.status_wallet_loading));
+ //Log.d(TAG, "onCreate() done.");
+ }
+
+ private long firstBlock = 0;
+ private boolean synced = false;
+
+ private void updateStatus(Wallet wallet) {
+ setActivityTitle(wallet);
+ final TextView balanceView = (TextView) findViewById(R.id.tvBalance);
+ final TextView unlockedView = (TextView) findViewById(R.id.tvUnlockedBalance);
+ final TextView syncProgressView = (TextView) findViewById(R.id.tvBlockHeightProgress);
+ final TextView connectionStatusView = (TextView) findViewById(R.id.tvConnectionStatus);
+
+ //Wallet wallet = getWallet();
+ balanceView.setText(Wallet.getDisplayAmount(wallet.getBalance()));
+ unlockedView.setText(Wallet.getDisplayAmount(wallet.getUnlockedBalance()));
+ String sync = "";
+ if (wallet.getConnectionStatus() == Wallet.ConnectionStatus.ConnectionStatus_Connected) {
+ if (!wallet.isSynchronized()) {
+ long n = wallet.getDaemonBlockChainHeight() - wallet.getBlockChainHeight();
+ sync = n + " " + getString(R.string.status_remaining);
+ if (firstBlock == 0) {
+ firstBlock = wallet.getBlockChainHeight();
+ }
+ int x = 100 - Math.round(100f * n / (1f * wallet.getDaemonBlockChainHeight() - firstBlock));
+ //Log.d(TAG, n + "/" + (wallet.getDaemonBlockChainHeight() - firstBlock));
+ onProgress(getString(R.string.status_syncing) + " " + sync);
+ onProgress(x);
+ } else {
+ sync = getString(R.string.status_synced) + ": " + wallet.getBlockChainHeight();
+ if (!synced) {
+ hideProgress();
+ synced = true;
+ }
+ }
+ }
+ String t = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
+ syncProgressView.setText(sync);
+ connectionStatusView.setText(t + " " + wallet.getConnectionStatus().toString().substring(17));
+ }
+
+ @Override
+ public void onRefreshed(final Wallet wallet, final boolean full) {
+ Log.d(TAG, "onRefreshed()");
+ if (wallet.isSynchronized()) {
+ releaseWakeLock(); // the idea is to stay awake until synced
+ }
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (full) {
+ List list = wallet.getHistory().getAll();
+ adapter.setInfos(list);
+ adapter.notifyDataSetChanged();
+ }
+ updateStatus(wallet);
+ }
+ });
+ }
+
+ Wallet getWallet() {
+ if (mBoundService == null) throw new IllegalStateException("WalletService not bound.");
+ return mBoundService.getWallet();
+ }
+
+ private WalletService mBoundService = null;
+ private boolean mIsBound = false;
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ // This is called when the connection with the service has been
+ // established, giving us the service object we can use to
+ // interact with the service. Because we have bound to a explicit
+ // service that we know is running in our own process, we can
+ // cast its IBinder to a concrete class and directly access it.
+ mBoundService = ((WalletService.WalletServiceBinder) service).getService();
+ //Log.d(TAG, "setting observer of " + mBoundService);
+ mBoundService.setObserver(WalletActivity.this);
+ //TODO show current progress (eg. if the service is already busy saving last wallet)
+ Log.d(TAG, "CONNECTED");
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ // This is called when the connection with the service has been
+ // unexpectedly disconnected -- that is, its process crashed.
+ // Because it is running in our same process, we should never
+ // see this happen.
+ mBoundService = null;
+ setTitle(getString(R.string.wallet_activity_name));
+ Log.d(TAG, "DISCONNECTED");
+ }
+ };
+
+ void connectWalletService(String walletName, String walletPassword) {
+ // Establish a connection with the service. We use an explicit
+ // class name because we want a specific service implementation that
+ // we know will be running in our own process (and thus won't be
+ // supporting component replacement by other applications).
+ Intent intent = new Intent(getApplicationContext(), WalletService.class);
+ intent.putExtra(WalletService.REQUEST_WALLET, walletName);
+ intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_LOAD);
+ intent.putExtra(WalletService.REQUEST_CMD_LOAD_PW, walletPassword);
+ startService(intent);
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ mIsBound = true;
+ Log.d(TAG, "BOUND");
+ }
+
+ void disconnectWalletService() {
+ if (mIsBound) {
+ // Detach our existing connection.
+ mBoundService.setObserver(null);
+ unbindService(mConnection);
+ mIsBound = false;
+ Toast.makeText(getApplicationContext(), getString(R.string.status_wallet_unloading), Toast.LENGTH_LONG).show();
+ Log.d(TAG, "UNBOUND");
+ }
+ }
+
+ // Callbacks from TransactionInfoAdapter
+ @Override
+ public void onInteraction(final View view, final TransactionInfo infoItem) {
+ final Context ctx = view.getContext();
+ AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
+ builder.setTitle("Transaction details");
+
+ builder.setNegativeButton("Copy TX ID", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ClipboardManager clipboardManager = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("TX", infoItem.getHash());
+ clipboardManager.setPrimaryClip(clip);
+ }
+ });
+
+ builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ });
+ builder.setMessage("TX ID: " + infoItem.getHash() +
+ "\nPayment ID: " + infoItem.getPaymentId() +
+ "\nBlockHeight: " + infoItem.getBlockHeight() +
+ "\nAmount: " + Wallet.getDisplayAmount(infoItem.getAmount()) +
+ "\nFee: " + Wallet.getDisplayAmount(infoItem.getFee()));
+ AlertDialog alert1 = builder.create();
+ alert1.show();
+ }
+
+ @Override
+ protected void onPause() {
+ Log.d(TAG, "onPause()");
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.d(TAG, "onResume()");
+ }
+
+ private PowerManager.WakeLock wl = null;
+
+ void acquireWakeLock() {
+ if ((wl != null) && wl.isHeld()) return;
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ this.wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name));
+ try {
+ wl.acquire();
+ Log.d(TAG, "WakeLock acquired");
+ } catch (SecurityException ex) {
+ Log.d(TAG, "WakeLock NOT acquired");
+ Log.d(TAG, ex.getLocalizedMessage());
+ wl = null;
+ }
+ }
+
+ void releaseWakeLock() {
+ if ((wl == null) || !wl.isHeld()) return;
+ wl.release();
+ wl = null;
+ Log.d(TAG, "WakeLock released");
+ }
+
+ private void showProgress() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ LinearLayout llProgress = (LinearLayout) findViewById(R.id.llProgress);
+ llProgress.setVisibility(View.VISIBLE);
+ }
+ });
+ }
+
+ private void hideProgress() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ LinearLayout llProgress = (LinearLayout) findViewById(R.id.llProgress);
+ llProgress.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ @Override
+ public void onProgress(final String text) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ TextView progressText = (TextView) findViewById(R.id.tvProgress);
+ progressText.setText(text);
+ }
+ });
+ }
+
+ @Override
+ public void onProgress(final int n) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ ProgressBar progress = (ProgressBar) findViewById(R.id.pbProgress);
+ progress.setProgress(n);
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java
new file mode 100644
index 00000000..e2a730b6
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java
@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2017 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
+ *
+ * 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.layout;
+
+import android.graphics.Color;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.m2049r.xmrwallet.R;
+import com.m2049r.xmrwallet.model.TransactionInfo;
+import com.m2049r.xmrwallet.model.Wallet;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+public class TransactionInfoAdapter extends RecyclerView.Adapter {
+ static final String TAG = "TransactionInfoAdapter";
+
+ static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
+ static final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss");
+
+ static final int TX_RED = Color.rgb(255, 79, 65);
+ static final int TX_GREEN = Color.rgb(54, 176, 91);
+
+ public interface OnInteractionListener {
+ void onInteraction(View view, TransactionInfo item);
+ }
+
+ private final List infoItems;
+ private final OnInteractionListener listener;
+
+ public TransactionInfoAdapter(OnInteractionListener listener) {
+ this.infoItems = new ArrayList<>();
+ this.listener = listener;
+ Calendar cal = Calendar.getInstance();
+ TimeZone tz = cal.getTimeZone(); //get the local time zone.
+ DATE_FORMATTER.setTimeZone(tz);
+ TIME_FORMATTER.setTimeZone(tz);
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.transaction_item, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ holder.bind(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return infoItems.size();
+ }
+
+ public void setInfos(List data) {
+ // TODO do stuff with data so we can really recycle elements (i.e. add only new tx)
+ this.infoItems.clear();
+ if (data != null) {
+ Log.d(TAG, "setInfos " + data.size());
+ // sort by block height
+ Collections.sort(data, new Comparator() {
+ @Override
+ public int compare(TransactionInfo o1, TransactionInfo o2) {
+ long b1 = o1.getBlockHeight();
+ long b2 = o2.getBlockHeight();
+ return (b1 > b2) ? -1 : (b1 < b2) ? 1 : 0;
+ }
+ });
+ this.infoItems.addAll(data);
+ } else {
+ Log.d(TAG, "setInfos null");
+ }
+ notifyDataSetChanged();
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+ public final TextView tvAmount;
+ public final TextView tvAmountPoint;
+ public final TextView tvAmountDecimal;
+ public final TextView tvDate;
+ public final TextView tvTime;
+ public TransactionInfo infoItem;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ this.tvAmount = (TextView) itemView.findViewById(R.id.tx_amount);
+ // I know this is stupid but can't be bothered to align decimals otherwise
+ this.tvAmountPoint = (TextView) itemView.findViewById(R.id.tx_amount_point);
+ this.tvAmountDecimal = (TextView) itemView.findViewById(R.id.tx_amount_decimal);
+ this.tvDate = (TextView) itemView.findViewById(R.id.tx_date);
+ this.tvTime = (TextView) itemView.findViewById(R.id.tx_time);
+ }
+
+ private String getDate(long time) {
+ return DATE_FORMATTER.format(new Date(time * 1000));
+ }
+
+ private String getTime(long time) {
+ return TIME_FORMATTER.format(new Date(time * 1000));
+ }
+
+ private void setTxColour(int clr) {
+ tvAmount.setTextColor(clr);
+ tvAmountDecimal.setTextColor(clr);
+ tvAmountPoint.setTextColor(clr);
+ }
+
+ void bind(int position) {
+ this.infoItem = infoItems.get(position);
+ String displayAmount = Wallet.getDisplayAmount(infoItem.getAmount());
+ // TODO fix this with i8n code
+ String amountParts[] = displayAmount.split("\\.");
+ // TODO what if there is no decimal point?
+
+ this.tvAmount.setText(amountParts[0]);
+ this.tvAmountDecimal.setText(amountParts[1]);
+ if (infoItem.getDirection() == TransactionInfo.Direction.Direction_In) {
+ setTxColour(TX_GREEN);
+ } else {
+ setTxColour(TX_RED);
+ }
+ this.tvDate.setText(getDate(infoItem.getTimestamp()));
+ this.tvTime.setText(getTime(infoItem.getTimestamp()));
+
+ itemView.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (listener != null) {
+ int position = getAdapterPosition(); // gets item position
+ if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
+ listener.onInteraction(view, infoItems.get(position));
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java
new file mode 100644
index 00000000..120f6bb3
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2017 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
+ *
+ * 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;
+
+import java.util.List;
+
+public class TransactionHistory {
+ static {
+ System.loadLibrary("monerujo");
+ }
+
+ private long handle;
+
+ public TransactionHistory(long handle) {
+ this.handle = handle;
+ }
+
+ public TransactionInfo getTransaction(int i) {
+ long infoHandle = getTransactionByIndexJ(i);
+ return new TransactionInfo(infoHandle);
+ }
+
+ public TransactionInfo getTransaction(String id) {
+ long infoHandle = getTransactionByIdJ(id);
+ return new TransactionInfo(infoHandle);
+ }
+
+ /*
+ public List getAll() {
+ List handles = getAllJ();
+ List infoList = new ArrayList();
+ for (Long handle : handles) {
+ infoList.add(new TransactionInfo(handle.longValue()));
+ }
+ return infoList;
+ }
+ */
+ public native int getCount();
+
+ private native long getTransactionByIndexJ(int i);
+
+ private native long getTransactionByIdJ(String id);
+
+ public native List getAll();
+
+ public native void refresh();
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java
new file mode 100644
index 00000000..48634d72
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java
@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2017 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
+ *
+ * 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 class TransactionInfo {
+ static {
+ System.loadLibrary("monerujo");
+ }
+
+ public long handle;
+
+ public TransactionInfo(long handle) {
+ this.handle = handle;
+ }
+
+ public enum Direction {
+ Direction_In,
+ Direction_Out
+ }
+
+ public class Transfer {
+ long amount;
+ String address;
+
+ public Transfer(long amount, String address) {
+ this.amount = amount;
+ this.address = address;
+ }
+
+ public long getAmount() {
+ return amount;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+ }
+
+ public String toString() {
+ return getDirection() + "@" + getBlockHeight() + " " + getAmount();
+ }
+
+ public Direction getDirection() {
+ return TransactionInfo.Direction.values()[getDirectionJ()];
+ }
+
+ public native int getDirectionJ();
+
+ public native boolean isPending();
+
+ public native boolean isFailed();
+
+ public native long getAmount();
+
+ public native long getFee();
+
+ public native long getBlockHeight();
+
+ public native long getConfirmations();
+
+ public native String getHash();
+
+ public native long getTimestamp();
+
+ public native String getPaymentId();
+
+/*
+ private List transfers;
+
+ public List getTransfers() { // not threadsafe
+ if (this.transfers == null) {
+ this.transfers = getTransfersJ();
+ }
+ return this.transfers;
+ }
+
+ private native List getTransfersJ();
+*/
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java b/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java
new file mode 100644
index 00000000..2e2dfaa5
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java
@@ -0,0 +1,215 @@
+/**
+ * Copyright (c) 2017 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
+ *
+ * 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;
+
+import android.util.Log;
+
+import java.io.File;
+
+public class Wallet {
+ static {
+ System.loadLibrary("monerujo");
+ }
+
+ static final String TAG = "Wallet";
+
+ public String getName() {
+ String p = getPath();
+ return new File(p).getName();
+ }
+
+ private long handle = 0;
+ private long listenerHandle = 0;
+
+ public Wallet(long handle) {
+ this.handle = handle;
+ }
+
+ public enum Status {
+ Status_Ok,
+ Status_Error,
+ Status_Critical
+ }
+
+ public enum ConnectionStatus {
+ ConnectionStatus_Disconnected,
+ ConnectionStatus_Connected,
+ ConnectionStatus_WrongVersion
+ }
+
+ //public native long createWalletListenerJ();
+
+ public native String getSeed();
+
+ public native String getSeedLanguage();
+
+ public native void setSeedLanguage(String language);
+
+ public Status getStatus() {
+ return Wallet.Status.values()[getStatusJ()];
+ }
+
+ private native int getStatusJ();
+
+ public native String getErrorString();
+
+ public native boolean setPassword(String password);
+
+ public native String getAddress();
+
+ public native String getPath();
+
+ public native boolean isTestNet();
+
+//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;
+
+ public native String getIntegratedAddress(String payment_id);
+
+ public native String getSecretViewKey();
+
+ public boolean store() {
+ return store(this.getPath());
+ }
+
+ public native boolean store(String path);
+
+ public boolean close() {
+ return WalletManager.getInstance().close(this);
+ }
+
+ public native String getFilename();
+
+ // virtual std::string keysFilename() const = 0;
+ public boolean init(long upper_transaction_size_limit) {
+ return initJ(WalletManager.getInstance().getDaemonAddress(), upper_transaction_size_limit);
+ }
+
+ private native boolean initJ(String daemon_address, long upper_transaction_size_limit);
+
+// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
+// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
+// virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0;
+// virtual bool connectToDaemon() = 0;
+
+ public ConnectionStatus getConnectionStatus() {
+ int s = getConnectionStatusJ();
+ return Wallet.ConnectionStatus.values()[s];
+ }
+
+ private native int getConnectionStatusJ();
+
+//TODO virtual void setTrustedDaemon(bool arg) = 0;
+//TODO virtual bool trustedDaemon() const = 0;
+
+ public native long getBalance();
+
+ public native long getUnlockedBalance();
+
+ public native boolean isWatchOnly();
+
+ public native long getBlockChainHeight();
+
+ public native long getApproximateBlockChainHeight();
+
+ public native long getDaemonBlockChainHeight();
+
+ public native long getDaemonBlockChainTargetHeight();
+
+ public native boolean isSynchronized();
+
+ public static native String getDisplayAmount(long amount);
+
+ public static native long getAmountFromString(String amount);
+
+ public static native long getAmountFromDouble(double amount);
+
+ public static native String generatePaymentId();
+
+ public static native boolean isPaymentIdValid(String payment_id);
+
+ public static native boolean isAddressValid(String address, boolean isTestNet);
+
+//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);
+
+ public static native String getPaymentIdFromAddress(String address, boolean isTestNet);
+
+ public static native long getMaximumAllowedAmount();
+
+ public native void startRefresh();
+
+ public native void pauseRefresh();
+
+ public native boolean refresh();
+
+ public native void refreshAsync();
+
+//TODO virtual void setAutoRefreshInterval(int millis) = 0;
+//TODO virtual int autoRefreshInterval() const = 0;
+
+
+//virtual PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id,
+// optional tvAmount, uint32_t mixin_count,
+// PendingTransaction::Priority = PendingTransaction::Priority_Low) = 0;
+
+//virtual PendingTransaction * createSweepUnmixableTransaction() = 0;
+
+//virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0;
+//virtual bool submitTransaction(const std::string &fileName) = 0;
+//virtual void disposeTransaction(PendingTransaction * t) = 0;
+//virtual bool exportKeyImages(const std::string &filename) = 0;
+//virtual bool importKeyImages(const std::string &filename) = 0;
+
+
+//virtual TransactionHistory * history() const = 0;
+
+ private TransactionHistory history = null;
+
+ public TransactionHistory getHistory() {
+ if (history == null) {
+ history = new TransactionHistory(getHistoryJ());
+ }
+ return history;
+ }
+
+ private native long getHistoryJ();
+
+//virtual AddressBook * addressBook() const = 0;
+//virtual void setListener(WalletListener *) = 0;
+
+ private native long setListenerJ(WalletListener listener);
+
+ public void setListener(WalletListener listener) {
+ this.listenerHandle = setListenerJ(listener);
+ }
+
+ public native int getDefaultMixin();
+
+ public native void setDefaultMixin(int mixin);
+
+//virtual bool setUserNote(const std::string &txid, const std::string ¬e) = 0;
+//virtual std::string getUserNote(const std::string &txid) const = 0;
+//virtual std::string getTxKey(const std::string &txid) const = 0;
+
+//virtual std::string signMessage(const std::string &message) = 0;
+//virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0;
+
+//virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector &unknown_parameters, std::string &error) = 0;
+//virtual bool rescanSpent() = 0;
+
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/WalletListener.java b/app/src/main/java/com/m2049r/xmrwallet/model/WalletListener.java
new file mode 100644
index 00000000..ea7f1f45
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletListener.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2017 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
+ *
+ * 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 interface WalletListener {
+ /**
+ * moneySpent - called when money spent
+ * @param txId - transaction id
+ * @param amount - tvAmount
+ */
+ void moneySpent(String txId, long amount);
+
+ /**
+ * moneyReceived - called when money received
+ * @param txId - transaction id
+ * @param amount - tvAmount
+ */
+ void moneyReceived(String txId, long amount);
+
+ /**
+ * unconfirmedMoneyReceived - called when payment arrived in tx pool
+ * @param txId - transaction id
+ * @param amount - tvAmount
+ */
+ void unconfirmedMoneyReceived(String txId, long amount);
+
+ /**
+ * newBlock - called when new block received
+ * @param height - block height
+ */
+ void newBlock(long height);
+
+ /**
+ * updated - generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet;
+ */
+ void updated();
+
+ /**
+ * refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously
+ */
+ void refreshed();
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java
new file mode 100644
index 00000000..1c9e46da
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java
@@ -0,0 +1,230 @@
+/**
+ * Copyright (c) 2017 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
+ *
+ * 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;
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class WalletManager {
+ final static String TAG = "WalletManager";
+
+ static {
+ System.loadLibrary("monerujo");
+ }
+
+ // no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it)
+ private static WalletManager Instance = null;
+
+ public static WalletManager getInstance() { // TODO not threadsafe
+ if (WalletManager.Instance == null) {
+ WalletManager.Instance = new WalletManager();
+ }
+ return WalletManager.Instance;
+ }
+
+ private WalletManager() {
+ this.managedWallets = new HashMap<>();
+ }
+
+ private Map managedWallets;
+
+ public Wallet getWallet(String walletId) {
+ return managedWallets.get(walletId);
+ }
+
+ private void manageWallet(String walletId, Wallet wallet) {
+ if (getWallet(walletId) != null) {
+ throw new IllegalStateException("Wallet already under management!");
+ }
+ Log.d(TAG, "Managing " + walletId);
+ managedWallets.put(walletId, wallet);
+ }
+
+ private void unmanageWallet(String walletId) {
+ if (getWallet(walletId) == null) {
+ throw new IllegalStateException("Wallet not under management!");
+ }
+ Log.d(TAG, "Unmanaging " + walletId);
+ managedWallets.remove(walletId);
+ }
+
+ public Wallet createWallet(String path, String password, String language) {
+ long walletHandle = createWalletJ(path, password, language, isTestNet());
+ Wallet wallet = new Wallet(walletHandle);
+ manageWallet(wallet.getName(), wallet);
+ return wallet;
+ }
+
+ public Wallet openWallet(String path, String password) {
+ long walletHandle = openWalletJ(path, password, isTestNet());
+ Wallet wallet = new Wallet(walletHandle);
+ manageWallet(wallet.getName(), wallet);
+ return wallet;
+ }
+
+ public Wallet recoveryWallet(String path, String mnemonic) {
+ Wallet wallet = recoveryWallet(path, mnemonic, 0);
+ manageWallet(wallet.getName(), wallet);
+ return wallet;
+ }
+
+ public Wallet recoveryWallet(String path, String mnemonic, long restoreHeight) {
+ long walletHandle = recoveryWalletJ(path, mnemonic, isTestNet(), restoreHeight);
+ Wallet wallet = new Wallet(walletHandle);
+ manageWallet(wallet.getName(), wallet);
+ return wallet;
+ }
+
+ private native long createWalletJ(String path, String password, String language, boolean isTestNet);
+
+ private native long openWalletJ(String path, String password, boolean isTestNet);
+
+ private native long recoveryWalletJ(String path, String mnemonic, boolean isTestNet, long restoreHeight);
+
+ private native long createWalletFromKeysJ(String path, String language,
+ boolean isTestNet,
+ long restoreHeight,
+ String addressString,
+ String viewKeyString,
+ String spendKeyString);
+
+ public native boolean closeJ(Wallet wallet);
+
+ public boolean close(Wallet wallet) {
+ String walletId = new File(wallet.getFilename()).getName();
+ unmanageWallet(walletId);
+ boolean closed = closeJ(wallet);
+ if (!closed) {
+ // in case we could not close it
+ // we unmanage it
+ manageWallet(walletId, wallet);
+ }
+ return closed;
+ }
+
+ public native boolean walletExists(String path);
+
+ public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
+
+ //public native List findWallets(String path); // this does not work - some error in boost
+
+ public class WalletInfo {
+ public File path;
+ public String name;
+ public String address;
+ }
+
+ public List findWallets(File path) {
+ List wallets = new ArrayList<>();
+ Log.d(TAG, "Scanning: " + path.getAbsolutePath());
+ File[] found = path.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String filename) {
+ return filename.endsWith(".keys");
+ }
+ });
+ for (int i = 0; i < found.length; i++) {
+ WalletInfo info = new WalletInfo();
+ info.path = path;
+ String filename = found[i].getName();
+ info.name = filename.substring(0, filename.length() - 5); // 5 is length of ".keys"+1
+ File addressFile = new File(path, info.name + ".address.txt");
+ //Log.d(TAG, addressFile.getAbsolutePath());
+ info.address = "??????";
+ BufferedReader addressReader = null;
+ try {
+ addressReader = new BufferedReader(new FileReader(addressFile));
+ info.address = addressReader.readLine();
+ } catch (IOException ex) {
+ Log.d(TAG, ex.getLocalizedMessage());
+ } finally {
+ if (addressReader != null) {
+ try {
+ addressReader.close();
+ } catch (IOException ex) {
+ // that's just too bad
+ }
+ }
+ }
+ wallets.add(info);
+ }
+ return wallets;
+ }
+
+ public native String getErrorString();
+
+//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;
+
+ public boolean isTestNet() {
+ if (daemonAddress == null) {
+ // assume testnet not explicitly initialised
+ throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
+ }
+ return testnet;
+ }
+
+ public void setDaemon(String address, boolean testnet) {
+ this.daemonAddress = address;
+ this.testnet = testnet;
+ setDaemonAddressJ(address);
+ }
+
+ public String getDaemonAddress() {
+ if (daemonAddress == null) {
+ // assume testnet not explicitly initialised
+ throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
+ }
+ return this.daemonAddress;
+ }
+
+ private native void setDaemonAddressJ(String address);
+
+ public native int getConnectedDaemonVersion();
+
+ public native long getBlockchainHeight();
+
+ public native long getBlockchainTargetHeight();
+
+ public native long getNetworkDifficulty();
+
+ public native double getMiningHashRate();
+
+ public native long getBlockTarget();
+
+ public native boolean isMining();
+
+ public native boolean startMining(String address, boolean background_mining, boolean ignore_battery);
+
+ public native boolean stopMining();
+
+ public native String resolveOpenAlias(String address, boolean dnssec_valid);
+
+//TODO static std::tuple checkUpdates(const std::string &software, const std::string &subdir);
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java
new file mode 100644
index 00000000..5517b0e6
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java
@@ -0,0 +1,352 @@
+/**
+ * Copyright (c) 2017 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
+ *
+ * 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.service;
+
+import android.app.ProgressDialog;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.m2049r.xmrwallet.R;
+import com.m2049r.xmrwallet.model.Wallet;
+import com.m2049r.xmrwallet.model.WalletListener;
+import com.m2049r.xmrwallet.model.WalletManager;
+import com.m2049r.xmrwallet.util.Helper;
+
+// Bind / Unbind
+// Activity onCreate() / onDestroy()
+// or
+// Activity onStart() / onStop()
+
+
+public class WalletService extends Service {
+ final static String TAG = "WalletService";
+
+ public static final String REQUEST = "request";
+ public static final String REQUEST_WALLET = "wallet";
+ public static final String REQUEST_CMD_LOAD = "load";
+ public static final String REQUEST_CMD_LOAD_PW = "walletPassword";
+
+ public static final int START_SERVICE = 1;
+ public static final int STOP_SERVICE = 2;
+
+ private MyWalletListener listener = null;
+
+ private class MyWalletListener implements WalletListener {
+ private Wallet wallet;
+ boolean updated = true;
+
+ Wallet getWallet() {
+ return wallet;
+ }
+
+ MyWalletListener(Wallet aWallet) {
+ if (aWallet == null) throw new IllegalArgumentException("Cannot open wallet!");
+ this.wallet = aWallet;
+ }
+
+ public void start() {
+ Log.d(TAG, "MyWalletListener.start()");
+ if (wallet == null) throw new IllegalStateException("No wallet!");
+ //acquireWakeLock();
+ wallet.setListener(this);
+ wallet.startRefresh();
+ }
+
+ public void stop() {
+ Log.d(TAG, "MyWalletListener.stop()");
+ if (wallet == null) throw new IllegalStateException("No wallet!");
+ wallet.pauseRefresh();
+ wallet.setListener(null);
+ //releaseWakeLock();
+ }
+
+ // WalletListener callbacks
+ public void moneySpent(String txId, long amount) {
+ Log.d(TAG, "moneySpent() " + amount + " @ " + txId);
+ }
+
+ public void moneyReceived(String txId, long amount) {
+ Log.d(TAG, "moneyReceived() " + amount + " @ " + txId);
+ }
+
+ public void unconfirmedMoneyReceived(String txId, long amount) {
+ Log.d(TAG, "unconfirmedMoneyReceived() " + amount + " @ " + txId);
+ }
+
+ long lastBlockTime = 0;
+
+ public void newBlock(long height) {
+ if (wallet == null) throw new IllegalStateException("No wallet!");
+ // don't flood with an update for every block ...
+ if (lastBlockTime < System.currentTimeMillis() - 2000) {
+ Log.d(TAG, "newBlock() @" + height + "with observer " + observer);
+ lastBlockTime = System.currentTimeMillis();
+ if (observer != null) {
+ observer.onRefreshed(wallet, false);
+ }
+ }
+ }
+
+ public void updated() {
+ Log.d(TAG, "updated() " + wallet.getBalance());
+ if (wallet == null) throw new IllegalStateException("No wallet!");
+ updated = true;
+ }
+
+ public void refreshed() {
+ if (wallet == null) throw new IllegalStateException("No wallet!");
+ Log.d(TAG, "refreshed() " + wallet.getBalance() + " sync=" + wallet.isSynchronized() + "with observer " + observer);
+ if (updated) {
+ if (observer != null) {
+ wallet.getHistory().refresh();
+ observer.onRefreshed(wallet, true);
+ updated = false;
+ }
+ }
+ }
+ }
+
+ /////////////////////////////////////////////
+ // communication back to client (activity) //
+ /////////////////////////////////////////////
+ // NB: This allows for only one observer, i.e. only a single activity bound here
+
+ private Observer observer = null;
+
+ public void setObserver(Observer anObserver) {
+ observer = anObserver;
+ Log.d(TAG, "setObserver " + observer);
+ }
+
+ public interface Observer {
+ void onRefreshed(Wallet wallet, boolean full);
+
+ void onProgress(String text);
+
+ void onProgress(int n);
+ }
+
+ private void showProgress(String text) {
+ if (observer != null) {
+ observer.onProgress(text);
+ }
+ }
+
+ private void showProgress(int n) {
+ if (observer != null) {
+ observer.onProgress(n);
+ }
+ }
+
+ //
+ public Wallet getWallet() {
+ if (listener == null) throw new IllegalStateException("no listener");
+ return listener.getWallet();
+ }
+
+ /////////////////////////////////////////////
+ /////////////////////////////////////////////
+
+ private Looper mServiceLooper;
+ private WalletService.ServiceHandler mServiceHandler;
+
+ // Handler that receives messages from the thread
+ private final class ServiceHandler extends Handler {
+ public ServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ Log.d(TAG, "Handling " + msg.arg2);
+ switch (msg.arg2) {
+ case START_SERVICE: {
+ Bundle extras = msg.getData();
+ String walletId = extras.getString(REQUEST_WALLET, null);
+ String walletPw = extras.getString(REQUEST_CMD_LOAD_PW, null);
+ Log.d(TAG, "LOAD wallet " + walletId);// + ":" + walletPw);
+ if (walletId != null) {
+ start(walletId, walletPw); // TODO What if this fails?
+ }
+ }
+ break;
+ case STOP_SERVICE:
+ stop();
+ break;
+ default:
+ Log.e(TAG, "UNKNOWN " + msg.arg2);
+ }
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ //mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ //showNotification();
+
+ // We are using a HandlerThread and a Looper to avoid loading and closing
+ // concurrency
+ HandlerThread thread = new HandlerThread("WalletService",
+ Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+
+ // Get the HandlerThread's Looper and use it for our Handler
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new WalletService.ServiceHandler(mServiceLooper);
+
+ Log.d(TAG, "Service created");
+ }
+
+ @Override
+ public void onDestroy() {
+ Log.d(TAG, "onDestroy()");
+ // Cancel the persistent notification.
+ //mNM.cancel(NOTIFICATION);
+ if (this.listener != null) {
+ Log.w(TAG, "onDestroy() with active listener");
+ // no need to stop() here because the wallet closing should have been triggered
+ // through onUnbind() already
+ }
+ }
+
+ public class WalletServiceBinder extends Binder {
+ public WalletService getService() {
+ return WalletService.this;
+ }
+ }
+
+ private final IBinder mBinder = new WalletServiceBinder();
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // when the activity satrts the service, it expects to start it for a new wallet
+ // the service is possibly still occupied with saving the last opened wallet
+ // so we queue the open request
+ // this should not matter since the old activity is not getting updates
+ // and the new one is not listening yet (although it will be bound)
+ Log.d(TAG, "onStartCommand()");
+ //acquireWakeLock(); // we want to be awake for the fun stuff
+ // For each start request, send a message to start a job and deliver the
+ // start ID so we know which request we're stopping when we finish the job
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg2 = START_SERVICE;
+ msg.setData(intent.getExtras());
+ mServiceHandler.sendMessage(msg);
+ //Log.d(TAG, "onStartCommand() message sent");
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // Very first client binds
+ Log.d(TAG, "onBind()");
+ return mBinder;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ Log.d(TAG, "onUnbind()");
+ // All clients have unbound with unbindService()
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg2 = STOP_SERVICE;
+ mServiceHandler.sendMessage(msg);
+ Log.d(TAG, "onUnbind() message sent");
+ return true; // true is important so that onUnbind is also called next time
+ }
+
+ private void start(String walletName, String walletPassword) {
+ // if there is an listener it is always started / syncing
+ Log.d(TAG, "start()");
+ showProgress(getString(R.string.status_wallet_loading));
+ showProgress(10);
+ if (listener == null) {
+ Log.d(TAG, "start() loadWallet");
+ Wallet aWallet = loadWallet(walletName, walletPassword);
+ listener = new MyWalletListener(aWallet);
+ listener.start();
+ showProgress(95);
+ }
+ Log.d(TAG, "start() done");
+ }
+
+ public void stop() {
+ Log.d(TAG, "stop()");
+ setObserver(null); // in case it was not reset already
+ if (listener != null) {
+ listener.stop();
+ Log.d(TAG, "stop() closing");
+ listener.getWallet().close();
+ Log.d(TAG, "stop() closed");
+ listener = null;
+ }
+ stopSelf();
+ // TODO ensure the Looper & thread actually stop and go away?
+ }
+
+ private Wallet loadWallet(String walletName, String walletPassword) {
+ String path = Helper.getWalletPath(getApplicationContext(), walletName);
+ //Log.d(TAG, "open wallet " + path);
+ Wallet wallet = openWallet(walletName, walletPassword);
+ //Log.d(TAG, "wallet opened: " + wallet);
+ if (wallet != null) {
+ //Log.d(TAG, wallet.getStatus().toString());
+ Log.d(TAG, "Using daemon " + WalletManager.getInstance().getDaemonAddress());
+ showProgress(55);
+ wallet.init(0);
+ showProgress(90);
+ Log.d(TAG, wallet.getConnectionStatus().toString());
+ }
+ return wallet;
+ }
+
+ private Wallet openWallet(String walletName, String walletPassword) {
+ String path = Helper.getWalletPath(getApplicationContext(), walletName);
+ showProgress(20);
+ Wallet wallet = null;
+ WalletManager walletMgr = WalletManager.getInstance();
+ Log.d(TAG, "WalletManager testnet=" + walletMgr.isTestNet());
+ showProgress(30);
+ if (walletMgr.walletExists(path)) {
+ Log.d(TAG, "open wallet " + path);
+ wallet = walletMgr.openWallet(path, walletPassword);
+ showProgress(60);
+ Log.d(TAG, "wallet opened");
+ Wallet.Status status = wallet.getStatus();
+ Log.d(TAG, "wallet status is " + status);
+ if (status != Wallet.Status.Status_Ok) {
+ Log.d(TAG, "wallet status is " + status);
+ WalletManager.getInstance().close(wallet); // TODO close() failed?
+ wallet = null;
+ // TODO what do we do with the progress??
+ }
+ }
+ return wallet;
+ }
+}
+
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
new file mode 100644
index 00000000..118056da
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2017 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
+ *