fixup readme

save prefs
merge-requests/3/head
m2049r 7 years ago
parent e576dedb9b
commit 34294932d1

@ -0,0 +1 @@
xmrwallet

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

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

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

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="animated-vector-drawable-25.3.1">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/c5daa70b6947fa9fd8a9960e661cc14fc4797397/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/c5daa70b6947fa9fd8a9960e661cc14fc4797397/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/animated-vector-drawable/25.3.1/animated-vector-drawable-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="appcompat-v7-25.3.1">
<CLASSES>
<root url="file://$USER_HOME$/.android/build-cache/7347280c7c5efbce562b3f7493acce3ce62120aa/output/res" />
<root url="jar://$USER_HOME$/.android/build-cache/7347280c7c5efbce562b3f7493acce3ce62120aa/output/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/appcompat-v7/25.3.1/appcompat-v7-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="cardview-v7-25.3.1">
<CLASSES>
<root url="file://$USER_HOME$/.android/build-cache/d3101339ab3be4a16068a03e764036d173229f64/output/res" />
<root url="jar://$USER_HOME$/.android/build-cache/d3101339ab3be4a16068a03e764036d173229f64/output/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/cardview-v7/25.3.1/cardview-v7-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="constraint-layout-1.0.2">
<CLASSES>
<root url="file://$USER_HOME$/.android/build-cache/983cd9976ef510b2538361561f2ee91f1200f245/output/res" />
<root url="jar://$USER_HOME$/.android/build-cache/983cd9976ef510b2538361561f2ee91f1200f245/output/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="constraint-layout-solver-1.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/m2repository/com/android/support/constraint/constraint-layout-solver/1.0.2/constraint-layout-solver-1.0.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="design-25.3.1">
<CLASSES>
<root url="file://$USER_HOME$/.android/build-cache/222602355d1fc232e06a3e23c06dc49c03836f7a/output/res" />
<root url="jar://$USER_HOME$/.android/build-cache/222602355d1fc232e06a3e23c06dc49c03836f7a/output/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/design/25.3.1/design-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="espresso-core-2.2.2">
<CLASSES>
<root url="file://$USER_HOME$/.android/build-cache/d505abdc89cc7d1b938ec3f95e87d4323360fa14/output/res" />
<root url="jar://$USER_HOME$/.android/build-cache/d505abdc89cc7d1b938ec3f95e87d4323360fa14/output/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/test/espresso/espresso-core/2.2.2/espresso-core-2.2.2-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="espresso-idling-resource-2.2.2">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/0dcbbeb3c862ffabe5d9f3c1f70b25bfdf6c739e/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/0dcbbeb3c862ffabe5d9f3c1f70b25bfdf6c739e/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/test/espresso/espresso-idling-resource/2.2.2/espresso-idling-resource-2.2.2-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="exposed-instrumentation-api-publish-0.5">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/c11e9f250159df473b4fbff8b80f455f0be2e5b5/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/c11e9f250159df473b4fbff8b80f455f0be2e5b5/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/test/exposed-instrumentation-api-publish/0.5/exposed-instrumentation-api-publish-0.5-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="hamcrest-core-1.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/42a25dc3219429f0e5d060061f71acb49bf010a0/hamcrest-core-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/1dc37250fbc78e23a65a67fbbaf71d2e9cbc3c0b/hamcrest-core-1.3-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="hamcrest-integration-1.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-integration/1.3/5de0c73fef18917cd85d0ab70bb23818685e4dfd/hamcrest-integration-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="hamcrest-library-1.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-library/1.3/4785a3c21320980282f9f33d0d1264a69040538f/hamcrest-library-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="javawriter-2.1.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.squareup/javawriter/2.1.1/67ff45d9ae02e583d0f9b3432a5ebbe05c30c966/javawriter-2.1.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="javax.annotation-api-1.2">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/javax.annotation/javax.annotation-api/1.2/479c1e06db31c432330183f5cae684163f186146/javax.annotation-api-1.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="javax.inject-1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/javax.inject/javax.inject/1/6975da39a7040257bd51d21a231b76c915872d38/javax.inject-1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="jsr305-2.0.1">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/2.0.1/516c03b21d50a644d538de0f0369c620989cd8f0/jsr305-2.0.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="junit-4.12">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/2973d150c0dc1fefe998f834810d68f278ea58ec/junit-4.12.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/a6c32b40bf3d76eca54e3c601e5d1470c86fcdfa/junit-4.12-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="recyclerview-v7-25.3.1">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/99e5fdc3d331d7083d1b3759b72d024c2f736dcd/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/99e5fdc3d331d7083d1b3759b72d024c2f736dcd/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/recyclerview-v7/25.3.1/recyclerview-v7-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="rules-0.5">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/8f8d46a3318b85241b55e25415619e9017c0aef1/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/8f8d46a3318b85241b55e25415619e9017c0aef1/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/test/rules/0.5/rules-0.5-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="runner-0.5">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/a3a29c4e90c979d7576a0a7d8450261e0065a0a8/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/a3a29c4e90c979d7576a0a7d8450261e0065a0a8/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/test/runner/0.5/runner-0.5-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="support-annotations-25.3.1">
<CLASSES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/support-annotations/25.3.1/support-annotations-25.3.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/support-annotations/25.3.1/support-annotations-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="support-compat-25.3.1">
<CLASSES>
<root url="file://$USER_HOME$/.android/build-cache/bfee7899a02ec429f8d6fbb22abb30af9d75dd54/output/res" />
<root url="jar://$USER_HOME$/.android/build-cache/bfee7899a02ec429f8d6fbb22abb30af9d75dd54/output/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/support-compat/25.3.1/support-compat-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="support-core-ui-25.3.1">
<CLASSES>
<root url="file://$USER_HOME$/.android/build-cache/f1e0938f7953f4d9d6e09e6205aea1c4ea1bf919/output/res" />
<root url="jar://$USER_HOME$/.android/build-cache/f1e0938f7953f4d9d6e09e6205aea1c4ea1bf919/output/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/support-core-ui/25.3.1/support-core-ui-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="support-core-utils-25.3.1">
<CLASSES>
<root url="file://$USER_HOME$/.android/build-cache/3399641f4104cc67bc990816452765709c509ff9/output/res" />
<root url="jar://$USER_HOME$/.android/build-cache/3399641f4104cc67bc990816452765709c509ff9/output/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/support-core-utils/25.3.1/support-core-utils-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="support-fragment-25.3.1">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/3f5c9811e6cfbd571b84d0dbb634b48dfe989015/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/3f5c9811e6cfbd571b84d0dbb634b48dfe989015/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/support-fragment/25.3.1/support-fragment-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="support-media-compat-25.3.1">
<CLASSES>
<root url="file://$USER_HOME$/.android/build-cache/7ed270352f6e04a617f3ff349bf4590f91afd032/output/res" />
<root url="jar://$USER_HOME$/.android/build-cache/7ed270352f6e04a617f3ff349bf4590f91afd032/output/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/support-media-compat/25.3.1/support-media-compat-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="support-v4-25.3.1">
<CLASSES>
<root url="file://$USER_HOME$/.android/build-cache/00ab1c7e820f681dc2b7ac4321cc1d73f2876d96/output/res" />
<root url="jar://$USER_HOME$/.android/build-cache/00ab1c7e820f681dc2b7ac4321cc1d73f2876d96/output/jars/classes.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="support-vector-drawable-25.3.1">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/e6ca1fb96a4aeca737119703e3fea72846da3ca1/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/e6ca1fb96a4aeca737119703e3fea72846da3ca1/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/support-vector-drawable/25.3.1/support-vector-drawable-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="transition-25.3.1">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/1ff9f43f47f4f75d31b57959475cee7001de24ac/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/1ff9f43f47f4f75d31b57959475cee7001de24ac/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/android/m2repository/com/android/support/transition/25.3.1/transition-25.3.1-sources.jar!/" />
</SOURCES>
</library>
</component>

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

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

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

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

File diff suppressed because it is too large Load Diff

@ -0,0 +1,51 @@
# 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
- this is more of a proof of concept
- 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
- don't have the screen on for first sync - use IntentService with WakeLock instead?
- make it pretty
- show current block height - is that relevant?
- License Dialog
- support for right-to-left layouts
- visibility of methods/classes
- adjust layout so we can use bigger font sizes
- 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
### Issues
- occasional crashes ...
### 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.

@ -0,0 +1,171 @@
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 SHARED IMPORTED)
set_target_properties(crypto PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.so)
add_library(ssl SHARED IMPORTED)
set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libssl.so)
############
# 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 SHARED IMPORTED)
set_target_properties(wallet PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet.so)
add_library(cryptonote_core SHARED IMPORTED)
set_target_properties(cryptonote_core PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcryptonote_core.so)
add_library(cryptonote_basic SHARED IMPORTED)
set_target_properties(cryptonote_basic PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcryptonote_basic.so)
add_library(mnemonics SHARED IMPORTED)
set_target_properties(mnemonics PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libmnemonics.so)
add_library(common SHARED IMPORTED)
set_target_properties(common PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcommon.so)
add_library(cncrypto SHARED IMPORTED)
set_target_properties(cncrypto PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcncrypto.so)
add_library(ringct SHARED IMPORTED)
set_target_properties(ringct PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct.so)
#####
add_library(p2p SHARED IMPORTED)
set_target_properties(p2p PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libp2p.so)
add_library(blockchain_db SHARED IMPORTED)
set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblockchain_db.so)
add_library(lmdb SHARED IMPORTED)
set_target_properties(lmdb PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/liblmdb.so)
add_library(easylogging SHARED IMPORTED)
set_target_properties(easylogging PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libeasylogging.so)
add_library(unbound SHARED IMPORTED)
set_target_properties(unbound PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libunbound.so)
####
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
cncrypto
ringct
common
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}
)

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
</configuration>
</facet>
<facet type="native-android-gradle" name="Native-Android-Gradle">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/cpp" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/cmake" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 25 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" scope="TEST" name="runner-0.5" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="espresso-idling-resource-2.2.2" level="project" />
<orderEntry type="library" exported="" name="constraint-layout-1.0.2" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-library-1.3" level="project" />
<orderEntry type="library" exported="" name="transition-25.3.1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-integration-1.3" level="project" />
<orderEntry type="library" exported="" name="design-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-core-ui-25.3.1" level="project" />
<orderEntry type="library" exported="" name="cardview-v7-25.3.1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="jsr305-2.0.1" level="project" />
<orderEntry type="library" exported="" name="support-core-utils-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-fragment-25.3.1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="espresso-core-2.2.2" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="exposed-instrumentation-api-publish-0.5" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="rules-0.5" level="project" />
<orderEntry type="library" exported="" name="constraint-layout-solver-1.0.2" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="javax.annotation-api-1.2" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="javax.inject-1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="javawriter-2.1.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-media-compat-25.3.1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-core-1.3" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-25.3.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-vector-drawable-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-compat-25.3.1" level="project" />
<orderEntry type="library" exported="" name="animated-vector-drawable-25.3.1" level="project" />
</component>
</module>

@ -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 1
versionName "0.1"
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'
}

@ -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

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.m2049r.xmrwallet">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".WalletActivity"
android:label="@string/wallet_activity_name"
android:launchMode="singleTop"></activity>
<activity
android:name=".LoginActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

@ -0,0 +1,988 @@
#include <inttypes.h>
#include "monerujo.h"
#include "wallet2_api.h"
//TODO explicit casting jlong, jint, jboolean to avoid warnings
#ifdef __cplusplus
extern "C"
{
#endif
#include <android/log.h>
#define LOG_TAG "WalletNDK"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG,__VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , LOG_TAG,__VA_ARGS__)
#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__)
static JavaVM *cachedJVM;
static jclass class_ArrayList;
static jclass class_WalletListener;
static jclass class_TransactionInfo;
static jclass class_TransactionInfo$Transfer;
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
cachedJVM = jvm;
LOGI("JNI_OnLoad");
JNIEnv *jenv;
if (jvm->GetEnv(reinterpret_cast<void **>(&jenv), JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
//LOGI("JNI_OnLoad ok");
class_ArrayList = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("java/util/ArrayList")));
class_TransactionInfo = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/model/TransactionInfo")));
class_TransactionInfo$Transfer = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/model/TransactionInfo$Transfer")));
class_WalletListener = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/model/WalletListener")));
return JNI_VERSION_1_6;
}
#ifdef __cplusplus
}
#endif
int attachJVM(JNIEnv **jenv) {
int envStat = cachedJVM->GetEnv((void **) jenv, JNI_VERSION_1_6);
if (envStat == JNI_EDETACHED) {
if (cachedJVM->AttachCurrentThread(jenv, nullptr) != 0) {
LOGE("Failed to attach");
return JNI_ERR;
}
} else if (envStat == JNI_EVERSION) {
LOGE("GetEnv: version not supported");
return JNI_ERR;
}
//LOGI("envStat=%i", envStat);
return envStat;
}
void detachJVM(JNIEnv *jenv, int envStat) {
//LOGI("envStat=%i", envStat);
if (jenv->ExceptionCheck()) {
jenv->ExceptionDescribe();
}
if (envStat == JNI_EDETACHED) {
cachedJVM->DetachCurrentThread();
}
}
struct MyWalletListener : Bitmonero::WalletListener {
jobject jlistener;
MyWalletListener(JNIEnv *env, jobject aListener) {
LOGD("Created MyListener");
jlistener = env->NewGlobalRef(aListener);;
}
~MyWalletListener() {
LOGD("Destroyed MyListener");
};
void deleteGlobalJavaRef(JNIEnv *env) {
env->DeleteGlobalRef(jlistener);
jlistener = nullptr;
}
/**
* @brief updated - generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet;
*/
void updated() {
if (jlistener == nullptr) return;
LOGD("updated");
JNIEnv *jenv;
int envStat = attachJVM(&jenv);
if (envStat == JNI_ERR) return;
jmethodID listenerClass_updated = jenv->GetMethodID(class_WalletListener, "updated", "()V");
jenv->CallVoidMethod(jlistener, listenerClass_updated);
detachJVM(jenv, envStat);
}
/**
* @brief moneySpent - called when money spent
* @param txId - transaction id
* @param amount - amount
*/
void moneySpent(const std::string &txId, uint64_t amount) {
if (jlistener == nullptr) return;
LOGD("moneySpent %"
PRIu64, amount);
}
/**
* @brief moneyReceived - called when money received
* @param txId - transaction id
* @param amount - amount
*/
void moneyReceived(const std::string &txId, uint64_t amount) {
if (jlistener == nullptr) return;
LOGD("moneyReceived %"
PRIu64, amount);
}
/**
* @brief unconfirmedMoneyReceived - called when payment arrived in tx pool
* @param txId - transaction id
* @param amount - amount
*/
void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) {
if (jlistener == nullptr) return;
LOGD("unconfirmedMoneyReceived %"
PRIu64, amount);
}
/**
* @brief newBlock - called when new block received
* @param height - block height
*/
void newBlock(uint64_t height) {
if (jlistener == nullptr) return;
//LOGD("newBlock");
JNIEnv *jenv;
int envStat = attachJVM(&jenv);
if (envStat == JNI_ERR) return;
jlong h = static_cast<jlong>(height);
jmethodID listenerClass_newBlock = jenv->GetMethodID(class_WalletListener, "newBlock",
"(J)V");
jenv->CallVoidMethod(jlistener, listenerClass_newBlock, h);
detachJVM(jenv, envStat);
}
/**
* @brief refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously
*/
void refreshed() {
if (jlistener == nullptr) return;
LOGD("refreshed");
JNIEnv *jenv;
int envStat = attachJVM(&jenv);
if (envStat == JNI_ERR) return;
jmethodID listenerClass_refreshed = jenv->GetMethodID(class_WalletListener, "refreshed",
"()V");
jenv->CallVoidMethod(jlistener, listenerClass_refreshed);
detachJVM(jenv, envStat);
}
};
//// helper methods
std::vector<std::string> java2cpp(JNIEnv *env, jobject arrayList) {
jmethodID java_util_ArrayList_size = env->GetMethodID(class_ArrayList, "size", "()I");
jmethodID java_util_ArrayList_get = env->GetMethodID(class_ArrayList, "get",
"(I)Ljava/lang/Object;");
jint len = env->CallIntMethod(arrayList, java_util_ArrayList_size);
std::vector<std::string> result;
result.reserve(len);
for (jint i = 0; i < len; i++) {
jstring element = static_cast<jstring>(env->CallObjectMethod(arrayList,
java_util_ArrayList_get, i));
const char *pchars = env->GetStringUTFChars(element, JNI_FALSE);
result.emplace_back(pchars);
env->ReleaseStringUTFChars(element, pchars);
env->DeleteLocalRef(element);
}
return result;
}
jobject cpp2java(JNIEnv *env, std::vector<std::string> vector) {
jmethodID java_util_ArrayList_ = env->GetMethodID(class_ArrayList, "<init>", "(I)V");
jmethodID java_util_ArrayList_add = env->GetMethodID(class_ArrayList, "add",
"(Ljava/lang/Object;)Z");
jobject result = env->NewObject(class_ArrayList, java_util_ArrayList_, vector.size());
for (std::string &s: vector) {
jstring element = env->NewStringUTF(s.c_str());
env->CallBooleanMethod(result, java_util_ArrayList_add, element);
env->DeleteLocalRef(element);
}
return result;
}
/// end helpers
#ifdef __cplusplus
extern "C"
{
#endif
/**********************************/
/********** WalletManager *********/
/**********************************/
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_createWalletJ(JNIEnv *env, jobject instance,
jstring path, jstring password,
jstring language,
jboolean isTestNet) {
const char *_path = env->GetStringUTFChars(path, JNI_FALSE);
const char *_password = env->GetStringUTFChars(password, JNI_FALSE);
const char *_language = env->GetStringUTFChars(language, JNI_FALSE);
Bitmonero::Wallet *wallet =
Bitmonero::WalletManagerFactory::getWalletManager()->createWallet(
std::string(_path),
std::string(_password),
std::string(_language),
isTestNet);
env->ReleaseStringUTFChars(path, _path);
env->ReleaseStringUTFChars(password, _password);
env->ReleaseStringUTFChars(language, _language);
return reinterpret_cast<jlong>(wallet);
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_openWalletJ(JNIEnv *env, jobject instance,
jstring path, jstring password,
jboolean isTestNet) {
const char *_path = env->GetStringUTFChars(path, JNI_FALSE);
const char *_password = env->GetStringUTFChars(password, JNI_FALSE);
Bitmonero::Wallet *wallet =
Bitmonero::WalletManagerFactory::getWalletManager()->openWallet(
std::string(_path),
std::string(_password),
isTestNet);
env->ReleaseStringUTFChars(path, _path);
env->ReleaseStringUTFChars(password, _password);
return reinterpret_cast<jlong>(wallet);
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_recoveryWalletJ(JNIEnv *env, jobject instance,
jstring path, jstring mnemonic,
jboolean isTestNet,
jlong restoreHeight) {
const char *_path = env->GetStringUTFChars(path, JNI_FALSE);
const char *_mnemonic = env->GetStringUTFChars(mnemonic, JNI_FALSE);
Bitmonero::Wallet *wallet =
Bitmonero::WalletManagerFactory::getWalletManager()->recoveryWallet(
std::string(_path),
std::string(_mnemonic),
isTestNet,
restoreHeight);
env->ReleaseStringUTFChars(path, _path);
env->ReleaseStringUTFChars(mnemonic, _mnemonic);
return reinterpret_cast<jlong>(wallet);
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_createWalletFromKeysJ(JNIEnv *env, jobject instance,
jstring path, jstring language,
jboolean isTestNet,
jlong restoreHeight,
jstring addressString,
jstring viewKeyString,
jstring spendKeyString) {
const char *_path = env->GetStringUTFChars(path, JNI_FALSE);
const char *_language = env->GetStringUTFChars(language, JNI_FALSE);
const char *_addressString = env->GetStringUTFChars(addressString, JNI_FALSE);
const char *_viewKeyString = env->GetStringUTFChars(viewKeyString, JNI_FALSE);
const char *_spendKeyString = env->GetStringUTFChars(spendKeyString, JNI_FALSE);
Bitmonero::Wallet *wallet =
Bitmonero::WalletManagerFactory::getWalletManager()->createWalletFromKeys(
std::string(_path),
std::string(_language),
isTestNet,
restoreHeight,
std::string(_addressString),
std::string(_viewKeyString),
std::string(_spendKeyString));
env->ReleaseStringUTFChars(path, _path);
env->ReleaseStringUTFChars(language, _language);
env->ReleaseStringUTFChars(addressString, _addressString);
env->ReleaseStringUTFChars(viewKeyString, _viewKeyString);
env->ReleaseStringUTFChars(spendKeyString, _spendKeyString);
return reinterpret_cast<jlong>(wallet);
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_walletExists(JNIEnv *env, jobject instance,
jstring path) {
const char *_path = env->GetStringUTFChars(path, JNI_FALSE);
bool exists =
Bitmonero::WalletManagerFactory::getWalletManager()->walletExists(std::string(_path));
env->ReleaseStringUTFChars(path, _path);
return exists;
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_verifyWalletPassword(JNIEnv *env, jobject instance,
jstring keys_file_name,
jstring password,
jboolean watch_only) {
const char *_keys_file_name = env->GetStringUTFChars(keys_file_name, JNI_FALSE);
const char *_password = env->GetStringUTFChars(password, JNI_FALSE);
bool passwordOk =
Bitmonero::WalletManagerFactory::getWalletManager()->verifyWalletPassword(
std::string(_keys_file_name), std::string(_password), watch_only);
env->ReleaseStringUTFChars(keys_file_name, _keys_file_name);
env->ReleaseStringUTFChars(password, _password);
return passwordOk;
}
JNIEXPORT jobject JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_findWallets(JNIEnv *env, jobject instance,
jstring path) {
const char *_path = env->GetStringUTFChars(path, JNI_FALSE);
std::vector<std::string> walletPaths =
Bitmonero::WalletManagerFactory::getWalletManager()->findWallets(std::string(_path));
env->ReleaseStringUTFChars(path, _path);
return cpp2java(env, walletPaths);
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_getErrorString(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return env->NewStringUTF(wallet->errorString().c_str());
}
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_setDaemonAddressJ(JNIEnv *env, jobject instance,
jstring address) {
const char *_address = env->GetStringUTFChars(address, JNI_FALSE);
Bitmonero::WalletManagerFactory::getWalletManager()->setDaemonAddress(std::string(_address));
env->ReleaseStringUTFChars(address, _address);
}
JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_getConnectedDaemonVersion(JNIEnv *env,
jobject instance) {
uint32_t version;
bool isConnected =
Bitmonero::WalletManagerFactory::getWalletManager()->connected(&version);
if (!isConnected) version = 0;
return version;
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_getBlockchainHeight(JNIEnv *env, jobject instance) {
return Bitmonero::WalletManagerFactory::getWalletManager()->blockchainHeight();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_getBlockchainTargetHeight(JNIEnv *env,
jobject instance) {
return Bitmonero::WalletManagerFactory::getWalletManager()->blockchainTargetHeight();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_getNetworkDifficulty(JNIEnv *env, jobject instance) {
return Bitmonero::WalletManagerFactory::getWalletManager()->networkDifficulty();
}
JNIEXPORT jdouble JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_getMiningHashRate(JNIEnv *env, jobject instance) {
return Bitmonero::WalletManagerFactory::getWalletManager()->miningHashRate();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_getBlockTarget(JNIEnv *env, jobject instance) {
return Bitmonero::WalletManagerFactory::getWalletManager()->blockTarget();
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_isMining(JNIEnv *env, jobject instance) {
return Bitmonero::WalletManagerFactory::getWalletManager()->isMining();
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_startMining(JNIEnv *env, jobject instance,
jstring address,
jboolean background_mining,
jboolean ignore_battery) {
const char *_address = env->GetStringUTFChars(address, JNI_FALSE);
bool success =
Bitmonero::WalletManagerFactory::getWalletManager()->startMining(std::string(_address),
background_mining,
ignore_battery);
env->ReleaseStringUTFChars(address, _address);
return success;
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_stopMining(JNIEnv *env, jobject instance) {
return Bitmonero::WalletManagerFactory::getWalletManager()->stopMining();
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_resolveOpenAlias(JNIEnv *env, jobject instance,
jstring address,
jboolean dnssec_valid) {
const char *_address = env->GetStringUTFChars(address, JNI_FALSE);
bool _dnssec_valid = (bool) dnssec_valid;
std::string resolvedAlias =
Bitmonero::WalletManagerFactory::getWalletManager()->resolveOpenAlias(
std::string(_address),
_dnssec_valid);
env->ReleaseStringUTFChars(address, _address);
return env->NewStringUTF(resolvedAlias.c_str());
}
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
// actually a WalletManager function, but logically in Wallet
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_close(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
bool closeSuccess = Bitmonero::WalletManagerFactory::getWalletManager()->closeWallet(wallet);
if (closeSuccess) {
MyWalletListener *walletListener = getHandle<MyWalletListener>(env, instance,
"listenerHandle");
if (walletListener != nullptr) {
walletListener->deleteGlobalJavaRef(env);
}
delete walletListener;
}
LOGD("wallet closed");
return closeSuccess;
}
/**********************************/
/************ Wallet **************/
/**********************************/
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getSeed(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
const char *address = wallet->seed().c_str();
return env->NewStringUTF(address);
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getSeedLanguage(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
const char *address = wallet->getSeedLanguage().c_str();
return env->NewStringUTF(address);
}
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_setSeedLanguage(JNIEnv *env, jobject instance,
jstring language) {
const char *_language = env->GetStringUTFChars(language, JNI_FALSE);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->setSeedLanguage(std::string(_language));
env->ReleaseStringUTFChars(language, _language);
}
JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getStatusJ(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->status();
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getErrorString(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return env->NewStringUTF(wallet->errorString().c_str());
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_setPassword(JNIEnv *env, jobject instance,
jstring password) {
const char *_password = env->GetStringUTFChars(password, JNI_FALSE);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
bool success = wallet->setPassword(std::string(_password));
env->ReleaseStringUTFChars(password, _password);
return success;
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getAddress(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
const char *address = wallet->address().c_str();
return env->NewStringUTF(address);
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getPath(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
const char *path = wallet->path().c_str();
return env->NewStringUTF(path);
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_isTestNet(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->testnet();
}
//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;
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getIntegratedAddress(JNIEnv *env, jobject instance,
jstring payment_id) {
const char *_payment_id = env->GetStringUTFChars(payment_id, JNI_FALSE);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
std::string address = wallet->integratedAddress(_payment_id);
env->ReleaseStringUTFChars(payment_id, _payment_id);
return env->NewStringUTF(address.c_str());
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getSecretViewKey(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
//return env->NewStringUTF(wallet->secretViewKey().c_str()); // changed in head
return env->NewStringUTF(wallet->privateViewKey().c_str());
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_store(JNIEnv *env, jobject instance,
jstring path) {
const char *_path = env->GetStringUTFChars(path, JNI_FALSE);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
bool success = wallet->store(std::string(_path));
env->ReleaseStringUTFChars(path, _path);
return success;
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getFilename(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return env->NewStringUTF(wallet->filename().c_str());
}
// virtual std::string keysFilename() const = 0;
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_init(JNIEnv *env, jobject instance,
jstring daemon_address,
long upper_transaction_size_limit) {
// const std::string &daemon_username = "", const std::string &daemon_password = "") = 0;
const char *_daemon_address = env->GetStringUTFChars(daemon_address, JNI_FALSE);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
bool status = wallet->init(_daemon_address, upper_transaction_size_limit);
env->ReleaseStringUTFChars(daemon_address, _daemon_address);
return status;
}
// 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;
JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getConnectionStatusJ(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->connected();
}
//TODO virtual void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0;
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->balance();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getUnlockedBalance(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->unlockedBalance();
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_isWatchOnly(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->watchOnly();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getBlockChainHeight(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->blockChainHeight();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getApproximateBlockChainHeight(JNIEnv *env,
jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->approximateBlockChainHeight();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getDaemonBlockChainHeight(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->daemonBlockChainHeight();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getDaemonBlockChainTargetHeight(JNIEnv *env,
jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->daemonBlockChainTargetHeight();
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_isSynchronized(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->synchronized();
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getDisplayAmount(JNIEnv *env, jobject clazz,
jlong amount) {
return env->NewStringUTF(Bitmonero::Wallet::displayAmount(amount).c_str());
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getAmountFromString(JNIEnv *env, jobject clazz,
jstring amount) {
const char *_amount = env->GetStringUTFChars(amount, JNI_FALSE);
uint64_t x = Bitmonero::Wallet::amountFromString(_amount);
env->ReleaseStringUTFChars(amount, _amount);
return x;
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getAmountFromDouble(JNIEnv *env, jobject clazz,
jdouble amount) {
return Bitmonero::Wallet::amountFromDouble(amount);
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_generatePaymentId(JNIEnv *env, jobject clazz) {
return env->NewStringUTF(Bitmonero::Wallet::genPaymentId().c_str());
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_isPaymentIdValid(JNIEnv *env, jobject clazz,
jstring payment_id) {
const char *_payment_id = env->GetStringUTFChars(payment_id, JNI_FALSE);
bool isValid = Bitmonero::Wallet::paymentIdValid(_payment_id);
env->ReleaseStringUTFChars(payment_id, _payment_id);
return isValid;
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_isAddressValid(JNIEnv *env, jobject clazz,
jstring address, jboolean isTestNet) {
const char *_address = env->GetStringUTFChars(address, JNI_FALSE);
bool isValid = Bitmonero::Wallet::addressValid(_address, isTestNet);
env->ReleaseStringUTFChars(address, _address);
return isValid;
}
//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getPaymentIdFromAddress(JNIEnv *env, jobject clazz,
jstring address,
jboolean isTestNet) {
const char *_address = env->GetStringUTFChars(address, JNI_FALSE);
std::string payment_id = Bitmonero::Wallet::paymentIdFromAddress(_address, isTestNet);
env->ReleaseStringUTFChars(address, _address);
return env->NewStringUTF(payment_id.c_str());
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getMaximumAllowedAmount(JNIEnv *env, jobject clazz) {
return Bitmonero::Wallet::maximumAllowedAmount();
}
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_startRefresh(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->startRefresh();
}
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_pauseRefresh(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->pauseRefresh();
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_refresh(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->refresh();
}
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_refreshAsync(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->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<uint64_t> 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;
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getHistoryJ(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return reinterpret_cast<jlong>(wallet->history());
}
//virtual AddressBook * addressBook() const = 0;
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_setListenerJ(JNIEnv *env, jobject instance,
jobject javaListener) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->setListener(nullptr); // clear old listener
// delete old listener
MyWalletListener *oldListener = getHandle<MyWalletListener>(env, instance,
"listenerHandle");
if (oldListener != nullptr) {
oldListener->deleteGlobalJavaRef(env);
delete oldListener;
}
if (javaListener == nullptr) {
LOGD("null listener");
return 0;
} else {
MyWalletListener *listener = new MyWalletListener(env, javaListener);
wallet->setListener(listener);
return reinterpret_cast<jlong>(listener);
}
}
JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getDefaultMixin(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->defaultMixin();
}
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_setDefaultMixin(JNIEnv *env, jobject instance, jint mixin) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return wallet->setDefaultMixin(mixin);
}
//virtual bool setUserNote(const std::string &txid, const std::string &note) = 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<std::string> &unknown_parameters, std::string &error) = 0;
//virtual bool rescanSpent() = 0;
// TransactionHistory
JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_TransactionHistory_getCount(JNIEnv *env, jobject instance) {
Bitmonero::TransactionHistory *history = getHandle<Bitmonero::TransactionHistory>(env,
instance);
return history->count();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_TransactionHistory_getTransactionByIndexJ(JNIEnv *env,
jobject instance,
jint i) {
Bitmonero::TransactionHistory *history = getHandle<Bitmonero::TransactionHistory>(env,
instance);
Bitmonero::TransactionInfo *info = history->transaction(i);
return reinterpret_cast<jlong>(info);
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_TransactionHistory_getTransactionByIdJ(JNIEnv *env,
jobject instance,
jstring id) {
const char *_id = env->GetStringUTFChars(id, JNI_FALSE);
Bitmonero::TransactionHistory *history = getHandle<Bitmonero::TransactionHistory>(env,
instance);
Bitmonero::TransactionInfo *info = history->transaction(std::string(_id));
env->ReleaseStringUTFChars(id, _id);
return reinterpret_cast<jlong>(info);
}
jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) {
jmethodID c = env->GetMethodID(class_TransactionInfo, "<init>", "(J)V");
return env->NewObject(class_TransactionInfo, c, reinterpret_cast<jlong>(info));
}
#include <stdio.h>
#include <stdlib.h>
jobject cpp2java(JNIEnv *env, std::vector<Bitmonero::TransactionInfo *> vector) {
jmethodID java_util_ArrayList_ = env->GetMethodID(class_ArrayList, "<init>", "(I)V");
jmethodID java_util_ArrayList_add = env->GetMethodID(class_ArrayList, "add",
"(Ljava/lang/Object;)Z");
//LOGD(std::to_string(vector.size()).c_str());
jobject arrayList = env->NewObject(class_ArrayList, java_util_ArrayList_, vector.size());
for (Bitmonero::TransactionInfo *s: vector) {
jobject info = newTransactionInfo(env, s);
env->CallBooleanMethod(arrayList, java_util_ArrayList_add, info);
env->DeleteLocalRef(info);
}
return arrayList;
}
JNIEXPORT jobject JNICALL
Java_com_m2049r_xmrwallet_model_TransactionHistory_getAll(JNIEnv *env, jobject instance) {
Bitmonero::TransactionHistory *history = getHandle<Bitmonero::TransactionHistory>(env,
instance);
return cpp2java(env, history->getAll());
}
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_TransactionHistory_refresh(JNIEnv *env, jobject instance) {
Bitmonero::TransactionHistory *history = getHandle<Bitmonero::TransactionHistory>(env,
instance);
history->refresh();
}
/* this is wrong - history object belongs to wallet
JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_TransactionHistory_dispose(JNIEnv *env, jobject instance) {
Bitmonero::TransactionHistory *history = getHandle<Bitmonero::TransactionHistory>(env,
instance);
if (history != nullptr) {
setHandle<long>(env, instance, 0);
delete history;
}
}
*/
// TransactionInfo
JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_getDirectionJ(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return info->direction();
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_isPending(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return info->isPending();
}
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_isFailed(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return info->isFailed();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_getAmount(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return info->amount();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_getFee(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return info->fee();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_getBlockHeight(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return info->blockHeight();
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_getConfirmations(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return info->confirmations();
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_getHash(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return env->NewStringUTF(info->hash().c_str());
}
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_getTimestamp(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return info->timestamp();
}
JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_getPaymentId(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return env->NewStringUTF(info->paymentId().c_str());
}
jobject newTransferInstance(JNIEnv *env, jobject transactionInfo, long amount,
const std::string &address) {
jmethodID methodID = env->GetMethodID(class_TransactionInfo$Transfer, "<init>",
"(JL/java.lang/String;)V");
jstring _address = env->NewStringUTF(address.c_str());
jobject transfer = env->NewObject(class_TransactionInfo$Transfer, methodID, amount, _address);
env->DeleteLocalRef(_address);
return transfer;
}
JNIEXPORT jobject JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_getTransfersJ(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
const std::vector<Bitmonero::TransactionInfo::Transfer> &transfers = info->transfers();
// make new ArrayList
jmethodID java_util_ArrayList_ = env->GetMethodID(class_ArrayList, "<init>", "(I)V");
jmethodID java_util_ArrayList_add = env->GetMethodID(class_ArrayList, "add",
"(Ljava/lang/Object;)Z");
jobject result = env->NewObject(class_ArrayList, java_util_ArrayList_, transfers.size());
// create Transfer objects and stick them in the List
for (const Bitmonero::TransactionInfo::Transfer &s: transfers) {
jobject element = newTransferInstance(env, instance, s.amount, s.address);
env->CallBooleanMethod(result, java_util_ArrayList_add, element);
env->DeleteLocalRef(element);
}
return result;
}
JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_TransactionInfo_getTransferCount(JNIEnv *env, jobject instance) {
Bitmonero::TransactionInfo *info = getHandle<Bitmonero::TransactionInfo>(env, instance);
return info->transfers().size();
}
#ifdef __cplusplus
}
#endif

@ -0,0 +1,39 @@
//
// Created by m2049r on 15.04.2017.
//
#ifndef XMRWALLET_WALLET_LIB_H
#define XMRWALLET_WALLET_LIB_H
#include <jni.h>
/*
#include <android/log.h>
#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 <typename T>
T *getHandle(JNIEnv *env, jobject obj, const char* fieldName = "handle") {
jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName));
return reinterpret_cast<T *>(handle);
}
void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) {
env->SetLongField(obj, getHandleField(env, obj), handle);
}
template <typename T>
void setHandle(JNIEnv *env, jobject obj, T *t) {
jlong handle = reinterpret_cast<jlong>(t);
setHandleFromLong(env, obj, handle);
}
#endif //XMRWALLET_WALLET_LIB_H

@ -0,0 +1,332 @@
package com.m2049r.xmrwallet;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class LoginActivity extends Activity {
static final String TAG = "LoginActivity";
ListView listView;
List<String> walletList = new ArrayList<>();
List<String> displayedList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
final EditText etDaemonAddress = (EditText) findViewById(R.id.etDaemonAddress);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
etDaemonAddress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(etDaemonAddress, InputMethodManager.SHOW_IMPLICIT);
}
});
etDaemonAddress.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)) {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
return false;
}
return false;
}
});
ToggleButton tbMainNet = (ToggleButton) findViewById(R.id.tbMainNet);
tbMainNet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean mainnet = ((ToggleButton) v).isChecked(); // current state
Log.d(TAG, "CLICK NET! mainnet=" + mainnet);
savePrefs(true); // use previous state as we just clicked it
filterList();
((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
}
});
loadPrefs();
filterList();
listView = (ListView) findViewById(R.id.list);
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, android.R.id.text1, this.displayedList);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
EditText tvDaemonAddress = (EditText) findViewById(R.id.etDaemonAddress);
if (tvDaemonAddress.getText().toString().length() == 0) {
Toast.makeText(LoginActivity.this, getString(R.string.prompt_daemon_missing), Toast.LENGTH_LONG).show();
return;
}
String itemValue = (String) listView.getItemAtPosition(position);
if ((isMainNet() && itemValue.charAt(1) != '4')
|| (!isMainNet() && itemValue.charAt(1) != '9')) {
Toast.makeText(LoginActivity.this, getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
return;
}
final int preambleLength = "[123456] ".length();
if (itemValue.length() <= (preambleLength)) {
Toast.makeText(LoginActivity.this, "something's wrong", Toast.LENGTH_LONG).show();
return;
}
String wallet = itemValue.substring(preambleLength);
promptPassword(wallet);
}
});
if (Helper.getWritePermission(this)) {
new LoadListTask().execute();
} else {
Log.i(TAG, "Waiting for permissions");
}
}
// adapted from http://www.mkyong.com/android/android-prompt-user-input-dialog-example/
void promptPassword(final String wallet) {
Context context = LoginActivity.this;
LayoutInflater li = LayoutInflater.from(context);
View promptsView = li.inflate(R.layout.prompt_password, null);
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
alertDialogBuilder.setView(promptsView);
final EditText etPassword = (EditText) promptsView.findViewById(R.id.etPassword);
final TextView tvPasswordLabel = (TextView) promptsView.findViewById(R.id.tvPasswordLabel);
tvPasswordLabel.setText(LoginActivity.this.getString(R.string.prompt_password) + " " + wallet);
// set dialog message
alertDialogBuilder
.setCancelable(false)
.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
String pass = etPassword.getText().toString();
processPasswordEntry(wallet, pass);
}
})
.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
dialog.cancel();
}
});
final AlertDialog alertDialog = alertDialogBuilder.create();
// request keyboard
alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
// accept keyboard "ok"
etPassword.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)) {
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
String pass = etPassword.getText().toString();
alertDialog.cancel();
processPasswordEntry(wallet, pass);
return false;
}
return false;
}
});
alertDialog.show();
}
private void processPasswordEntry(String walletName, String pass) {
if (checkWalletPassword(walletName, pass)) {
startWallet(walletName, pass);
} else {
Toast.makeText(this, getString(R.string.bad_password), Toast.LENGTH_SHORT).show();
}
}
private boolean checkWalletPassword(String walletName, String password) {
String walletPath = new File(Helper.getStorageRoot(getApplicationContext()),
walletName + ".keys").getAbsolutePath();
// only test view key
return WalletManager.getInstance().verifyWalletPassword(walletPath, password, true);
}
@Override
protected void onPause() {
Log.d(TAG, "onPause()");
savePrefs(false);
super.onPause();
}
boolean isMainNet() {
ToggleButton tbMainNet = (ToggleButton) findViewById(R.id.tbMainNet);
return tbMainNet.isChecked();
}
void setMainNet(boolean mainnet) {
ToggleButton tbMainNet = (ToggleButton) findViewById(R.id.tbMainNet);
tbMainNet.setChecked(mainnet);
}
String getDaemon() {
EditText tvDaemonAddress = (EditText) findViewById(R.id.etDaemonAddress);
return tvDaemonAddress.getText().toString();
}
void setDaemon(String address) {
EditText tvDaemonAddress = (EditText) findViewById(R.id.etDaemonAddress);
tvDaemonAddress.setText(address);
}
private static final String PREF_DAEMON_TESTNET = "daemon_testnet";
private static final String PREF_DAEMON_MAINNET = "daemon_mainnet";
private static final String PREF_MAINNET = "mainnet";
private String daemonTestNet;
private String daemonMainNet;
void loadPrefs() {
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
boolean mainnet = sharedPref.getBoolean(PREF_MAINNET, false);
daemonMainNet = sharedPref.getString(PREF_DAEMON_MAINNET, "localhost:18081");
daemonTestNet = sharedPref.getString(PREF_DAEMON_TESTNET, "localhost:28081");
setMainNet(mainnet);
if (mainnet) {
setDaemon(daemonMainNet);
} else {
setDaemon(daemonTestNet);
}
}
void savePrefs(boolean usePreviousState) {
// save the daemon address for the net
boolean mainnet = isMainNet() ^ usePreviousState;
String daemon = getDaemon();
if (mainnet) {
daemonMainNet = daemon;
} else {
daemonTestNet = daemon;
}
SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putBoolean(PREF_MAINNET, mainnet);
editor.putString(PREF_DAEMON_MAINNET, daemonMainNet);
editor.putString(PREF_DAEMON_TESTNET, daemonTestNet);
editor.apply();
}
void startWallet(String walletName, String walletPassword) {
Log.d(TAG, "startWallet()");
savePrefs(false);
EditText tvDaemonAddress = (EditText) findViewById(R.id.etDaemonAddress);
boolean testnet = !isMainNet();
Intent intent = new Intent(getApplicationContext(), WalletActivity.class);
String daemon = tvDaemonAddress.getText().toString();
if (!daemon.contains(":")) {
daemon = daemon + (testnet ? ":28081" : ":18081");
}
intent.putExtra("daemon", daemon);
intent.putExtra("testnet", testnet);
intent.putExtra("wallet", walletName);
intent.putExtra("password", walletPassword);
startActivity(intent);
}
private void filterList() {
displayedList.clear();
char x = isMainNet() ? '4' : '9';
for (String s : walletList) {
if (s.charAt(1) == x) displayedList.add(s);
}
}
private class LoadListTask extends AsyncTask<String, Void, Integer> {
protected void onPreExecute() {
//Toast.makeText(LoginActivity.this, getString(R.string.status_walletlist_loading), Toast.LENGTH_LONG).show();
}
protected Integer doInBackground(String... params) {
WalletManager mgr = WalletManager.getInstance();
List<WalletManager.WalletInfo> walletInfos =
mgr.findWallets(Helper.getStorageRoot(getApplicationContext()));
walletList.clear();
for (WalletManager.WalletInfo walletInfo : walletInfos) {
Log.d(TAG, walletInfo.address);
String displayAddress = walletInfo.address;
if (displayAddress.length() == 95) {
displayAddress = walletInfo.address.substring(0, 6);
}
walletList.add("[" + displayAddress + "] " + walletInfo.name);
}
return 0;
}
protected void onPostExecute(Integer result) {
if (result == 0) {
filterList();
((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
Log.d(TAG, "onRequestPermissionsResult()");
switch (requestCode) {
case Helper.PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE:
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
new LoadListTask().execute();
} else {
String msg = getString(R.string.message_strorage_not_permitted);
Log.e(TAG, msg);
throw new IllegalStateException(msg);
}
break;
default:
}
}
}

@ -0,0 +1,368 @@
package com.m2049r.xmrwallet;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
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.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.model.WalletListener;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import java.io.File;
import java.util.List;
public class WalletActivity extends AppCompatActivity implements TransactionInfoAdapter.OnInteractionListener {
private static final String TAG = "WalletActivity";
TransactionInfoAdapter adapter;
@Override
protected void onPause() {
Log.d(TAG, "onPause()");
Toast.makeText(this, getString(R.string.status_wallet_unloading), Toast.LENGTH_LONG).show();
if (walletControl != null) {
walletControl.stop();
walletControl.destroy();
walletControl = null;
}
super.onPause();
}
@Override
protected void onResume() {
Log.d(TAG, "onResume()");
Toast.makeText(this, getString(R.string.status_wallet_loading), Toast.LENGTH_LONG).show();
load();
super.onResume();
setActivityTitle();
if (walletControl != null) {
updateStatus(walletControl.wallet);
}
}
private String walletName = null;
private String walletPassword = null;
private boolean testnet = true;
private String daemon = null;
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart()");
Bundle extras = getIntent().getExtras();
if (extras != null) {
this.walletName = extras.getString("wallet");
this.walletPassword = extras.getString("password");
this.daemon = extras.getString("daemon", "localhost:28081");
this.testnet = extras.getBoolean("testnet", true);
} else {
throw new IllegalStateException("No extras passed! Panic!");
}
Log.d(TAG, "onStart() done.");
}
WalletControl walletControl;
void load() {
if (walletControl == null) {
Log.d(TAG, "load wallet");
Wallet aWallet = getOrCreateTestWallet();
walletControl = new WalletControl(aWallet);
walletControl.start();
Log.d(TAG, "control started");
}
}
void setActivityTitle() {
if (walletControl != null) {
String shortName = new File(walletControl.wallet.getPath()).getName();
if (shortName.length() > 16) {
shortName = shortName.substring(0, 14) + "...";
}
setTitle("[" + walletControl.wallet.getAddress().substring(0, 6) + "] " + shortName);
} else {
setTitle(getString(R.string.prompt_problems));
}
}
@Override
protected void onStop() {
Log.d(TAG, "onStop()");
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(TAG, "onDestroy()");
super.onDestroy();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate()");
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.");
}
void updateStatus(Wallet 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);
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);
} else {
sync = getString(R.string.status_synced) + ": " + wallet.getBlockChainHeight();
}
}
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));
}
public void onRefreshed(final Wallet wallet, final boolean full) {
Log.d(TAG, "onRefreshed()");
runOnUiThread(new Runnable() {
public void run() {
if (full) {
List<TransactionInfo> list = wallet.getHistory().getAll();
adapter.setInfos(list);
adapter.notifyDataSetChanged();
}
updateStatus(wallet);
}
});
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "NEW INTENT");
// and ignore it
}
// 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();
}
private class WalletControl implements WalletListener {
private Wallet wallet;
boolean updated = true;
WalletControl(Wallet aWallet) {
if (aWallet == null) throw new IllegalArgumentException("Cannot open wallet!");
this.wallet = aWallet;
}
private void start() {
Log.d(TAG, "start()");
if (wallet == null) throw new IllegalStateException("No wallet!");
acquireWakeLock();
wallet.setListener(this);
wallet.startRefresh();
}
private void stop() {
Log.d(TAG, "stop()");
if (wallet == null) throw new IllegalStateException("No wallet!");
wallet.pauseRefresh();
wallet.setListener(null);
releaseWakeLock();
}
private void destroy() {
if (wallet == null) throw new IllegalStateException("No wallet!");
Log.d(TAG, "closing");
wallet.close();
Log.d(TAG, "closed");
wallet = null;
}
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");
}
////////////////////////////////////////////////////////////////////////////////////////////////
/// 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 (lastBlockTime < System.currentTimeMillis() - 2000) {
Log.d(TAG, "newBlock() " + height);
lastBlockTime = System.currentTimeMillis();
onRefreshed(wallet, false);
}
}
public void updated() {
Log.d(TAG, "updated() " + wallet.getBalance());
updated = true;
}
public void refreshed() {
Log.d(TAG, "refreshed() " + wallet.getBalance() + " sync=" + wallet.isSynchronized());
if (wallet.isSynchronized()) {
releaseWakeLock(); // the idea is to stay awake until synced
}
if (updated) {
wallet.getHistory().refresh();
onRefreshed(wallet, true);
updated = false;
} else {
onRefreshed(wallet, false);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
private Wallet getOrCreateTestWallet() {
String path = Helper.getWalletPath(getApplicationContext(), this.walletName);
WalletManager.getInstance().setDaemonAddress(this.daemon);
Log.d(TAG, "prewallet " + path);
Wallet wallet = getWallet(path, this.walletPassword, this.testnet);
if (wallet == null) {
Log.d(TAG, "creating wallet ...");
wallet = createTestWallet(path, this.walletPassword, this.testnet);
}
Log.d(TAG, "postwallet " + wallet);
if (wallet != null) {
Log.d(TAG, wallet.getStatus().toString());
Log.d(TAG, "Using daemon " + this.daemon);
wallet.init(this.daemon, 0);
Log.d(TAG, wallet.getConnectionStatus().toString());
}
return wallet;
}
private Wallet getWallet(String path, String password, boolean testnet) {
Wallet wallet = null;
WalletManager walletMgr = WalletManager.getInstance();
Log.d(TAG, "got WalletManager testnet=" + testnet);
if (walletMgr.walletExists(path)) {
Log.d(TAG, "open wallet " + path);
wallet = walletMgr.openWallet(path, password, testnet);
Log.d(TAG, "opened wallet");
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);
wallet.close();
wallet = null;
}
}
return wallet;
}
private Wallet createTestWallet(String path, String password, boolean testnet) {
long restoreHeight = 0;
String seed = "camp feline inflamed memoir afloat eight alerts females " +
"gutter cogs menu waveform gather tawny judge gusts " +
"yahoo doctor females biscuit alchemy reef agony austere camp";
WalletManager walletMgr = WalletManager.getInstance();
Wallet wallet = walletMgr.recoveryWallet(path, seed, testnet, restoreHeight);
wallet.setPassword(password);
return wallet;
}
}

@ -0,0 +1,149 @@
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<TransactionInfoAdapter.ViewHolder> {
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<TransactionInfo> 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<TransactionInfo> 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<TransactionInfo>() {
@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));
}
}
}
}
}

@ -0,0 +1,46 @@
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<TransactionInfo> getAll() {
List<Long> handles = getAllJ();
List<TransactionInfo> infoList = new ArrayList<TransactionInfo>();
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<TransactionInfo> getAll();
public native void refresh();
}

@ -0,0 +1,78 @@
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<Transfer> transfers;
public List<Transfer> getTransfers() { // not threadsafe
if (this.transfers == null) {
this.transfers = getTransfersJ();
}
return this.transfers;
}
private native List<Transfer> getTransfersJ();
*/
}

@ -0,0 +1,185 @@
package com.m2049r.xmrwallet.model;
import android.util.Log;
public class Wallet {
static {
System.loadLibrary("monerujo");
}
static final String TAG = "Wallet";
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 boolean close(); // from WalletManager
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 native String getFilename();
// virtual std::string keysFilename() const = 0;
public native boolean init(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<uint64_t> 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 &note) = 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<std::string> &unknown_parameters, std::string &error) = 0;
//virtual bool rescanSpent() = 0;
}

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

@ -0,0 +1,150 @@
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.List;
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;
}
public Wallet createWallet(String path, String password, String language, boolean isTestNet) {
long walletHandle = createWalletJ(path, password, language, isTestNet);
return new Wallet(walletHandle);
}
public Wallet openWallet(String path, String password, boolean isTestNet) {
long walletHandle = openWalletJ(path, password, isTestNet);
return new Wallet(walletHandle);
}
public Wallet recoveryWallet(String path, String mnemonic, boolean isTestNet) {
return recoveryWallet(path, mnemonic, isTestNet, 0);
}
public Wallet recoveryWallet(String path, String mnemonic, boolean isTestNet, long restoreHeight) {
long walletHandle = recoveryWalletJ(path, mnemonic, isTestNet, restoreHeight);
return new Wallet(walletHandle);
}
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 walletExists(String path);
public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
//public native List<String> 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<WalletInfo> findWallets(File path) {
List<WalletInfo> 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;
String daemonAddress = null;
public void setDaemonAddress(String address) {
this.daemonAddress = address;
setDaemonAddressJ(address);
}
public String getDaemonAddress() {
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<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
}

@ -0,0 +1,76 @@
package com.m2049r.xmrwallet.util;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.util.Log;
import com.m2049r.xmrwallet.R;
import java.io.File;
public class Helper {
static final String TAG = "Helper";
static final String WALLET_DIR = "Monerujo";
static public File getStorageRoot(Context context) {
if (!isExternalStorageWritable()) {
String msg = context.getString(R.string.message_strorage_not_writable);
Log.e(TAG, msg);
throw new IllegalStateException(msg);
}
File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR);
if (!dir.exists()) {
Log.i(TAG, "Creating " + dir.getAbsolutePath());
dir.mkdirs(); // try to make it
}
if (!dir.isDirectory()) {
String msg = "Directory " + dir.getAbsolutePath() + " does not exists.";
Log.e(TAG, msg);
throw new IllegalStateException(msg);
}
return dir;
}
public static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
static public boolean getWritePermission(Activity context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_DENIED) {
Log.d("permission", "permission denied to WRITE_EXTERNAL_STORAGE - requesting it");
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
context.requestPermissions(permissions, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
return false;
} else {
return true;
}
} else {
return true;
}
}
static public String getWalletPath(Context context, String aWalletName) {
File walletDir = getStorageRoot(context);
Log.d(TAG, "walletdir=" + walletDir.getAbsolutePath());
if (!walletDir.exists()) {
Log.d(TAG, "walletdir did not exist!");
walletDir.mkdirs();
}
File f = new File(walletDir, aWalletName);
Log.d(TAG, "wallet = " + f.getAbsolutePath() + " size=" + f.length());
return f.getAbsolutePath();
}
/* Checks if external storage is available for read and write */
static public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
}

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z" />
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

@ -0,0 +1,58 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.m2049r.xmrwallet.LoginActivity">
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ToggleButton
android:id="@+id/tbMainNet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:textOff="@string/connect_testnet"
android:textOn="@string/connect_mainnet"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
</ToggleButton>
<EditText
android:id="@+id/etDaemonAddress"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:gravity="center"
android:hint="@string/prompt_daemon"
android:backgroundTint="@color/colorPrimary"
android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true"
android:inputType="text"
android:textIsSelectable="true"
android:textSize="15sp"
android:maxLines="1"
android:imeOptions="actionDone"
app:layout_constraintBaseline_toBaselineOf="@+id/tbMainNet"
app:layout_constraintRight_toLeftOf="@+id/tbMainNet"
app:layout_constraintLeft_toLeftOf="parent" />
</android.support.constraint.ConstraintLayout>
<ListView
android:id="@+id/list"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_marginTop="4dp">
</ListView>
</LinearLayout>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_root"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:padding="10dp" >
<TextView
android:id="@+id/tvPasswordLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Wallet Password"
android:labelFor="@+id/etPassword"
android:textAppearance="?android:attr/textAppearanceLarge" />
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="normal"
android:inputType="textPassword" >
</EditText>
</LinearLayout>

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
app:cardBackgroundColor="@color/main_background"
android:layout_height="wrap_content"
android:layout_margin="1dp">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout_editor_absoluteY="0dp"
tools:layout_editor_absoluteX="0dp">
<TextView
android:id="@+id/tx_amount"
android:gravity="end"
android:layout_width="70sp"
android:layout_height="wrap_content"
android:text="99999999"
android:textColor="@android:color/holo_red_light"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tx_amount_point"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="."
android:textColor="@android:color/holo_red_light"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@+id/tx_amount"
android:layout_marginLeft="0dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tx_amount_decimal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="999999999999"
android:textColor="@android:color/holo_red_light"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@+id/tx_amount_point"
android:layout_marginLeft="0dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tx_date"
android:textColor="@android:color/black"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="2017-05-22"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"
app:layout_constraintRight_toLeftOf="@+id/tx_time"
android:layout_marginRight="8dp"/>
<TextView
android:id="@+id/tx_time"
android:textColor="@android:color/black"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="21:32:11"
app:layout_constraintRight_toLeftOf="parent"
android:layout_marginRight="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"/>
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>

@ -0,0 +1,94 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvBalanceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10sp"
android:layout_marginRight="4dp"
android:text="@string/label_balance"
app:layout_constraintBaseline_toBaselineOf="@+id/tvBalance"
app:layout_constraintRight_toLeftOf="@+id/tvBalance" />
<TextView
android:id="@+id/tvBalance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:text="00000000.000000000000"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvUnlockedBalanceLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10sp"
android:layout_marginRight="4dp"
android:text="@string/label_unlockedBalance"
app:layout_constraintBaseline_toBaselineOf="@+id/tvUnlockedBalance"
app:layout_constraintRight_toLeftOf="@+id/tvUnlockedBalance" />
<TextView
android:id="@+id/tvUnlockedBalance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="00000000.000000000000"
app:layout_constraintRight_toRightOf="@+id/tvBalance"
app:layout_constraintTop_toBottomOf="@+id/tvBalance" />
<TextView
android:id="@+id/tvConnectionStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10sp"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp"
android:text="Connecting..."
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvBlockHeightProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="10sp"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp"
android:text="Connecting..."
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvConnectionStatus" />
</android.support.constraint.ConstraintLayout>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:layout_marginTop="8dp"
app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/transaction_item" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@ -0,0 +1,8 @@
<resources>
<declare-styleable name="MyView">
<attr name="exampleString" format="string" />
<attr name="exampleDimension" format="dimension" />
<attr name="exampleColor" format="color" />
<attr name="exampleDrawable" format="color|reference" />
</declare-styleable>
</resources>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="main_background">#f0eeef</color>
<color name="menu_background_color">#1c1c1c</color>
<color name="tx_green">#36b05b</color>
<color name="tx_red">#ff4f41</color>
</resources>

@ -0,0 +1,6 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">8dp</dimen>
<dimen name="activity_vertical_margin">8dp</dimen>
<!--dimen name="text_margin">16dp</dimen-->
</resources>

@ -0,0 +1,40 @@
<resources>
<string name="app_name">Monerujo</string>
<string name="login_activity_name">Select Wallet</string>
<string name="wallet_activity_name">Wallet</string>
<!-- Strings related to login -->
<string name="prompt_daemon">Daemon Address</string>
<string name="prompt_mainnet">Net Selection</string>
<string name="connect_testnet">TestNet</string>
<string name="connect_mainnet">MainNet</string>
<string name="status_walletlist_loading">Loading Wallet List</string>
<string name="status_wallet_loading">Loading Wallet &#8230;</string>
<string name="status_wallet_unloading">Saving Wallet &#8230;</string>
<string name="prompt_password">Password for</string>
<string name="bad_password">Bad password!</string>
<string name="prompt_daemon_missing">Daemon address must be set!</string>
<string name="prompt_wrong_net">Daemon does not fit wallet!</string>
<string name="title_amount">Amount</string>
<string name="title_date">Date</string>
<string name="label_balance">Balance</string>
<string name="label_unlockedBalance">Available</string>
<string name="label_transactions">Transactions</string>
<string name="text_daemonConnected">Daemon connected!</string>
<string name="service_description">Monerujo Service</string>
<string name="local_service_started">Monerujo Service Running</string>
<string name="local_service_stopped">Monerujo Service Stopped</string>
<string name="local_service_label">Monerujo Service</string>
<string name="local_service_connected">Monerujo Service Connected</string>
<string name="local_service_disconnected">Monerujo Service Disconnected</string>
<string name="status_synced">Synced</string>
<string name="status_remaining">Blocks remaining</string>
<string name="prompt_problems">Problems</string>
<string name="message_strorage_not_writable">External Storage is not writable! Panic!</string>
<string name="message_strorage_not_permitted">External Storage permission not granted! Panic!</string>
</resources>

@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

@ -0,0 +1,22 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

@ -0,0 +1,46 @@
# FAQ
## Do you have any screenshots of what it looks like and how it works?
### [Select Wallet](images/A-wallet_selection.png)
Here you see a list of installed wallets and an entry field at the top to enter the daemon address. To the right there is a pushbutton to change between testnet and mainnet. The entered daemon is saved and displayed according to the state of this button.
### [Wallet Password](images/B-enter_password.png)
After selecting the wallet, the password is entered.
### [Wallet Syncing](images/C-wallet_syncing.png)
After some seconds the wallet is displyed with it's last known state and synced to the network. If it says "disconnected" or takes forever to show this screen then the entered daemon is wrong or unreachable. (Yes, I need to check the daemon availability on the login screen ...) Go back, and check that.
During syncing, the number of remaining blocks is displayed - when this reaches 0 the blockchain is fully synced.
The balance is updated during sync.
### [Wallet Synced](images/D-wallet_synced.png)
When the blockchain is synced, the screen shows "Synced" and the current blockchain height. When new blocks become available they are also synced and new transactions are displayed.
## What features does it have?
That's about it. Select a wallet and show the balance. Behind the scenes it keeps in sync with the blockchain while the app is open. So currently it is a view only wallet. You can use it to monitor your wallets on the go.
In future it will have the possibility of executing transactions. And generating wallets. Technically, it can generate wallets now, but they are pointless since you need another client to make transactions anyway - so you can make the wallets on the other client.
## What files do I need to copy?
You need to copy the wallet files from you current Monero client. These are:
```
WalletName
WalletName.address.txt
WalletName.keys
```
### From where?
This depends on your installation - you could search for them in your home directory or check the settings of your current client. Places to try are `C:\Users\<YOURUSERNAME>\Documents\Monero\wallets` for Windows or `~/.bitmonero/wallet` in Linux. Or just search for `WalletName.keys`.
### What if don't have these files?
As this is a view-only App right now, you need another client for generating wallets and sending transactions. This will change soon<sup>TM</sup>.

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

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

Loading…
Cancel
Save