wownero
/
wownerujo
Archived
4
0
Fork 0
async wallet load / close by service
wakelock & async loading / closing looks good
progress indicator
ode cleanup
added license boilerplates
upstream
m2049r 7 years ago
parent e576dedb9b
commit 2682399600

2
.gitignore vendored

@ -0,0 +1,2 @@
.gradle
build

1
.idea/.gitignore vendored

@ -0,0 +1 @@
workspace.xml

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

@ -0,0 +1,49 @@
# Monerujo
Another Android Monero Wallet
### QUICKSTART
- Download APK (Release) and install it
- Copy over synced wallet (all three files) onto sdcard in directory Monerujo (created first time app is started)
- Start app (again)
- see the [FAQ](doc/FAQ.md)
### Disclaimer
This is my first serious Android App.
### Notes
- Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/
- Special thanks to /u/gkruja for inspiration to pick this project up again
- Based off monero v0.10.3.1 with pull requests #2238 & #2239 applied => so can be used in mainnet!
- currently only android32
- sorry for my messups in github
- currently only use is checking incoming/outgoing transactions
- works in testnet & mainnet (please use your own daemons)
- takes forever to sync on mainnet (even with own daemon)
- use your own daemon - it's easy
- screen stays on until first sync is complete
### TODO
- make it pretty
- adjust layout so we can use bigger font sizes
- provide detailed build instructions for third party binaries
- visibility of methods/classes
- sensible error dialogs (e.g. when no write permissions granted) instead of just crashing on purpose
- ~~sensible loading/saving progress bars instead of just freezing up~~
- spend monero - not so difficult with wallet api
- ~~figure out how to make it all flow better (loading/saving takes forever and does not run in background)~~
- ~~currently loading in background thread produces segfaults in JNI~~
- check licenses of included libraries
- License Dialog
### Issues
- screen rotation crashes the app
- turning the display off/on during sync stops sync
### HOW TO BUILD
No need to build. Binaries are included:
- openssl-1.0.2l
- monero-v0.10.3.1 + pull requests #2238 & #2239
- boost_1_64_0
If you want to build - fire up Android Studio and build. Also you can rebuild all of the above.

2
app/.gitignore vendored

@ -0,0 +1,2 @@
.externalNativeBuild
build

@ -0,0 +1,172 @@
cmake_minimum_required(VERSION 3.4.1)
message(STATUS ABI_INFO = ${ANDROID_ABI})
add_library( monerujo
SHARED
src/main/cpp/monerujo.cpp )
set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../external-libs)
############
# OpenSSL
############
add_library(crypto STATIC IMPORTED)
set_target_properties(crypto PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a)
add_library(ssl STATIC IMPORTED)
set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libssl.a)
############
# Boost
############
add_library(boost_chrono STATIC IMPORTED)
set_target_properties(boost_chrono PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_chrono.a)
add_library(boost_date_time STATIC IMPORTED)
set_target_properties(boost_date_time PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_date_time.a)
add_library(boost_filesystem STATIC IMPORTED)
set_target_properties(boost_filesystem PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_filesystem.a)
add_library(boost_program_options STATIC IMPORTED)
set_target_properties(boost_program_options PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_program_options.a)
add_library(boost_regex STATIC IMPORTED)
set_target_properties(boost_regex PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_regex.a)
add_library(boost_serialization STATIC IMPORTED)
set_target_properties(boost_serialization PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_serialization.a)
add_library(boost_system STATIC IMPORTED)
set_target_properties(boost_system PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_system.a)
add_library(boost_thread STATIC IMPORTED)
set_target_properties(boost_thread PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_thread.a)
add_library(boost_wserialization STATIC IMPORTED)
set_target_properties(boost_wserialization PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_wserialization.a)
#############
# Monero set(libs_to_merge wallet cryptonote_core cryptonote_basic mnemonics common cncrypto ringct)
#############
add_library(wallet STATIC IMPORTED)
set_target_properties(wallet PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet.a)
add_library(cryptonote_core STATIC IMPORTED)
set_target_properties(cryptonote_core PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcryptonote_core.a)
add_library(cryptonote_basic STATIC IMPORTED)
set_target_properties(cryptonote_basic PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcryptonote_basic.a)
add_library(mnemonics STATIC IMPORTED)
set_target_properties(mnemonics PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libmnemonics.a)
add_library(common STATIC IMPORTED)
set_target_properties(common PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcommon.a)
add_library(cncrypto STATIC IMPORTED)
set_target_properties(cncrypto PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcncrypto.a)
add_library(ringct STATIC IMPORTED)
set_target_properties(ringct PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct.a)
#####
add_library(p2p STATIC IMPORTED)
set_target_properties(p2p PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libp2p.a)
add_library(blockchain_db STATIC IMPORTED)
set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblockchain_db.a)
add_library(lmdb STATIC IMPORTED)
set_target_properties(lmdb PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/liblmdb.a)
add_library(easylogging STATIC IMPORTED)
set_target_properties(easylogging PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libeasylogging.a)
add_library(unbound STATIC IMPORTED)
set_target_properties(unbound PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libunbound.a)
####
add_library(epee STATIC IMPORTED)
set_target_properties(epee PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libepee.a)
add_library(blocks STATIC IMPORTED)
set_target_properties(blocks PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblocks.a)
add_library(miniupnpc STATIC IMPORTED)
set_target_properties(miniupnpc PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libminiupnpc.a)
#############
# System
#############
find_library( log-lib log )
include_directories( ${EXTERNAL_LIBS_DIR}/monero/include )
message(STATUS EXTERNAL_LIBS_DIR : ${EXTERNAL_LIBS_DIR})
target_link_libraries( monerujo
wallet
cryptonote_core
cryptonote_basic
mnemonics
ringct
common
cncrypto
blockchain_db
lmdb
#easylogging # not for 0.10.3.1
unbound
p2p
epee
blocks
miniupnpc
boost_chrono
boost_date_time
boost_filesystem
boost_program_options
boost_regex
boost_serialization
boost_system
boost_thread
boost_wserialization
ssl
crypto
${log-lib}
)

@ -0,0 +1,147 @@
<?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-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
<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/reload-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/split-apk" />
<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 2
versionName "0.2.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
arguments '-DANDROID_STL=c++_shared'
}
}
ndk {
abiFilters 'armeabi-v7a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.android.support:cardview-v7:25.3.1'
testCompile 'junit:junit:4.12'
}

@ -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,40 @@
<?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>
<service
android:name=".service.WalletService"
android:description="@string/service_description"
android:exported="false"
android:label="Monero Wallet Service" />
</application>
</manifest>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,51 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef XMRWALLET_WALLET_LIB_H
#define XMRWALLET_WALLET_LIB_H
#include <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,353 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.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
savePrefs(true); // use previous state as we just clicked it
if (mainnet) {
setDaemon(daemonMainNet);
} else {
setDaemon(daemonTestNet);
}
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);
boolean testnet = !isMainNet();
String daemon = getDaemon();
Intent intent = new Intent(getApplicationContext(), WalletActivity.class);
if (!daemon.contains(":")) {
daemon = daemon + (testnet ? ":28081" : ":18081");
}
WalletManager.getInstance().setDaemon(daemon, testnet);
intent.putExtra(WalletActivity.REQUEST_ID, walletName);
intent.putExtra(WalletActivity.REQUEST_PW, 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,351 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.service.WalletService;
import java.util.List;
public class WalletActivity extends AppCompatActivity
implements TransactionInfoAdapter.OnInteractionListener, WalletService.Observer {
private static final String TAG = "WalletActivity";
public static final String REQUEST_ID = "id";
public static final String REQUEST_PW = "pw";
TransactionInfoAdapter adapter;
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart()");
acquireWakeLock();
Bundle extras = getIntent().getExtras();
if (extras != null) {
String walletId = extras.getString(REQUEST_ID);
String walletPassword = extras.getString(REQUEST_PW);
connectWalletService(walletId, walletPassword);
} else {
throw new IllegalStateException("No extras passed! Panic!");
}
showProgress();
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
onProgress(10); // look like we are working!
}
}, 250);
//Log.d(TAG, "onStart() done.");
}
private String title = null;
void setActivityTitle(Wallet wallet) {
if ((wallet == null) || (title != null)) return;
String shortName = wallet.getName();
if (shortName.length() > 16) {
shortName = shortName.substring(0, 14) + "...";
}
this.title = "[" + wallet.getAddress().substring(0, 6) + "] " + shortName;
setTitle(this.title);
onProgress(100);
Log.d(TAG, "wallet title is " + this.title);
}
@Override
protected void onStop() {
Log.d(TAG, "onStop()");
releaseWakeLock();
disconnectWalletService();
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(TAG, "onDestroy()");
super.onDestroy();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate()");
super.onCreate(savedInstanceState);
setContentView(R.layout.wallet_activity);
// TODO do stuff with savedInstanceState
if (savedInstanceState != null) {
return;
}
//Log.d(TAG, "no savedInstanceState");
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
RecyclerView.ItemDecoration itemDecoration = new
DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
recyclerView.addItemDecoration(itemDecoration);
this.adapter = new TransactionInfoAdapter(this);
recyclerView.setAdapter(adapter);
setTitle(getString(R.string.status_wallet_loading));
//Log.d(TAG, "onCreate() done.");
}
private long firstBlock = 0;
private boolean synced = false;
private void updateStatus(Wallet wallet) {
setActivityTitle(wallet);
final TextView balanceView = (TextView) findViewById(R.id.tvBalance);
final TextView unlockedView = (TextView) findViewById(R.id.tvUnlockedBalance);
final TextView syncProgressView = (TextView) findViewById(R.id.tvBlockHeightProgress);
final TextView connectionStatusView = (TextView) findViewById(R.id.tvConnectionStatus);
//Wallet wallet = getWallet();
balanceView.setText(Wallet.getDisplayAmount(wallet.getBalance()));
unlockedView.setText(Wallet.getDisplayAmount(wallet.getUnlockedBalance()));
String sync = "";
if (wallet.getConnectionStatus() == Wallet.ConnectionStatus.ConnectionStatus_Connected) {
if (!wallet.isSynchronized()) {
long n = wallet.getDaemonBlockChainHeight() - wallet.getBlockChainHeight();
sync = n + " " + getString(R.string.status_remaining);
if (firstBlock == 0) {
firstBlock = wallet.getBlockChainHeight();
}
int x = 100 - Math.round(100f * n / (1f * wallet.getDaemonBlockChainHeight() - firstBlock));
//Log.d(TAG, n + "/" + (wallet.getDaemonBlockChainHeight() - firstBlock));
onProgress(getString(R.string.status_syncing) + " " + sync);
onProgress(x);
} else {
sync = getString(R.string.status_synced) + ": " + wallet.getBlockChainHeight();
if (!synced) {
hideProgress();
synced = true;
}
}
}
String t = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
syncProgressView.setText(sync);
connectionStatusView.setText(t + " " + wallet.getConnectionStatus().toString().substring(17));
}
@Override
public void onRefreshed(final Wallet wallet, final boolean full) {
Log.d(TAG, "onRefreshed()");
if (wallet.isSynchronized()) {
releaseWakeLock(); // the idea is to stay awake until synced
}
runOnUiThread(new Runnable() {
public void run() {
if (full) {
List<TransactionInfo> list = wallet.getHistory().getAll();
adapter.setInfos(list);
adapter.notifyDataSetChanged();
}
updateStatus(wallet);
}
});
}
Wallet getWallet() {
if (mBoundService == null) throw new IllegalStateException("WalletService not bound.");
return mBoundService.getWallet();
}
private WalletService mBoundService = null;
private boolean mIsBound = false;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = ((WalletService.WalletServiceBinder) service).getService();
//Log.d(TAG, "setting observer of " + mBoundService);
mBoundService.setObserver(WalletActivity.this);
//TODO show current progress (eg. if the service is already busy saving last wallet)
Log.d(TAG, "CONNECTED");
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
setTitle(getString(R.string.wallet_activity_name));
Log.d(TAG, "DISCONNECTED");
}
};
void connectWalletService(String walletName, String walletPassword) {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
Intent intent = new Intent(getApplicationContext(), WalletService.class);
intent.putExtra(WalletService.REQUEST_WALLET, walletName);
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_LOAD);
intent.putExtra(WalletService.REQUEST_CMD_LOAD_PW, walletPassword);
startService(intent);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
Log.d(TAG, "BOUND");
}
void disconnectWalletService() {
if (mIsBound) {
// Detach our existing connection.
mBoundService.setObserver(null);
unbindService(mConnection);
mIsBound = false;
Toast.makeText(getApplicationContext(), getString(R.string.status_wallet_unloading), Toast.LENGTH_LONG).show();
Log.d(TAG, "UNBOUND");
}
}
// Callbacks from TransactionInfoAdapter
@Override
public void onInteraction(final View view, final TransactionInfo infoItem) {
final Context ctx = view.getContext();
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle("Transaction details");
builder.setNegativeButton("Copy TX ID", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ClipboardManager clipboardManager = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("TX", infoItem.getHash());
clipboardManager.setPrimaryClip(clip);
}
});
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setMessage("TX ID: " + infoItem.getHash() +
"\nPayment ID: " + infoItem.getPaymentId() +
"\nBlockHeight: " + infoItem.getBlockHeight() +
"\nAmount: " + Wallet.getDisplayAmount(infoItem.getAmount()) +
"\nFee: " + Wallet.getDisplayAmount(infoItem.getFee()));
AlertDialog alert1 = builder.create();
alert1.show();
}
@Override
protected void onPause() {
Log.d(TAG, "onPause()");
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
}
private PowerManager.WakeLock wl = null;
void acquireWakeLock() {
if ((wl != null) && wl.isHeld()) return;
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
this.wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name));
try {
wl.acquire();
Log.d(TAG, "WakeLock acquired");
} catch (SecurityException ex) {
Log.d(TAG, "WakeLock NOT acquired");
Log.d(TAG, ex.getLocalizedMessage());
wl = null;
}
}
void releaseWakeLock() {
if ((wl == null) || !wl.isHeld()) return;
wl.release();
wl = null;
Log.d(TAG, "WakeLock released");
}
private void showProgress() {
runOnUiThread(new Runnable() {
public void run() {
LinearLayout llProgress = (LinearLayout) findViewById(R.id.llProgress);
llProgress.setVisibility(View.VISIBLE);
}
});
}
private void hideProgress() {
runOnUiThread(new Runnable() {
public void run() {
LinearLayout llProgress = (LinearLayout) findViewById(R.id.llProgress);
llProgress.setVisibility(View.GONE);
}
});
}
@Override
public void onProgress(final String text) {
runOnUiThread(new Runnable() {
public void run() {
TextView progressText = (TextView) findViewById(R.id.tvProgress);
progressText.setText(text);
}
});
}
@Override
public void onProgress(final int n) {
runOnUiThread(new Runnable() {
public void run() {
ProgressBar progress = (ProgressBar) findViewById(R.id.pbProgress);
progress.setProgress(n);
}
});
}
}

@ -0,0 +1,165 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.layout;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
public class TransactionInfoAdapter extends RecyclerView.Adapter<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,62 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.model;
import java.util.List;
public class TransactionHistory {
static {
System.loadLibrary("monerujo");
}
private long handle;
public TransactionHistory(long handle) {
this.handle = handle;
}
public TransactionInfo getTransaction(int i) {
long infoHandle = getTransactionByIndexJ(i);
return new TransactionInfo(infoHandle);
}
public TransactionInfo getTransaction(String id) {
long infoHandle = getTransactionByIdJ(id);
return new TransactionInfo(infoHandle);
}
/*
public List<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,94 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.model;
public class TransactionInfo {
static {
System.loadLibrary("monerujo");
}
public long handle;
public TransactionInfo(long handle) {
this.handle = handle;
}
public enum Direction {
Direction_In,
Direction_Out
}
public class Transfer {
long amount;
String address;
public Transfer(long amount, String address) {
this.amount = amount;
this.address = address;
}
public long getAmount() {
return amount;
}
public String getAddress() {
return address;
}
}
public String toString() {
return getDirection() + "@" + getBlockHeight() + " " + getAmount();
}
public Direction getDirection() {
return TransactionInfo.Direction.values()[getDirectionJ()];
}
public native int getDirectionJ();
public native boolean isPending();
public native boolean isFailed();
public native long getAmount();
public native long getFee();
public native long getBlockHeight();
public native long getConfirmations();
public native String getHash();
public native long getTimestamp();
public native String getPaymentId();
/*
private List<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,215 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.model;
import android.util.Log;
import java.io.File;
public class Wallet {
static {
System.loadLibrary("monerujo");
}
static final String TAG = "Wallet";
public String getName() {
String p = getPath();
return new File(p).getName();
}
private long handle = 0;
private long listenerHandle = 0;
public Wallet(long handle) {
this.handle = handle;
}
public enum Status {
Status_Ok,
Status_Error,
Status_Critical
}
public enum ConnectionStatus {
ConnectionStatus_Disconnected,
ConnectionStatus_Connected,
ConnectionStatus_WrongVersion
}
//public native long createWalletListenerJ();
public native String getSeed();
public native String getSeedLanguage();
public native void setSeedLanguage(String language);
public Status getStatus() {
return Wallet.Status.values()[getStatusJ()];
}
private native int getStatusJ();
public native String getErrorString();
public native boolean setPassword(String password);
public native String getAddress();
public native String getPath();
public native boolean isTestNet();
//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
public native String getIntegratedAddress(String payment_id);
public native String getSecretViewKey();
public boolean store() {
return store(this.getPath());
}
public native boolean store(String path);
public boolean close() {
return WalletManager.getInstance().close(this);
}
public native String getFilename();
// virtual std::string keysFilename() const = 0;
public boolean init(long upper_transaction_size_limit) {
return initJ(WalletManager.getInstance().getDaemonAddress(), upper_transaction_size_limit);
}
private native boolean initJ(String daemon_address, long upper_transaction_size_limit);
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
// virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0;
// virtual bool connectToDaemon() = 0;
public ConnectionStatus getConnectionStatus() {
int s = getConnectionStatusJ();
return Wallet.ConnectionStatus.values()[s];
}
private native int getConnectionStatusJ();
//TODO virtual void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0;
public native long getBalance();
public native long getUnlockedBalance();
public native boolean isWatchOnly();
public native long getBlockChainHeight();
public native long getApproximateBlockChainHeight();
public native long getDaemonBlockChainHeight();
public native long getDaemonBlockChainTargetHeight();
public native boolean isSynchronized();
public static native String getDisplayAmount(long amount);
public static native long getAmountFromString(String amount);
public static native long getAmountFromDouble(double amount);
public static native String generatePaymentId();
public static native boolean isPaymentIdValid(String payment_id);
public static native boolean isAddressValid(String address, boolean isTestNet);
//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);
public static native String getPaymentIdFromAddress(String address, boolean isTestNet);
public static native long getMaximumAllowedAmount();
public native void startRefresh();
public native void pauseRefresh();
public native boolean refresh();
public native void refreshAsync();
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
//TODO virtual int autoRefreshInterval() const = 0;
//virtual PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id,
// optional<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,57 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.model;
public interface WalletListener {
/**
* moneySpent - called when money spent
* @param txId - transaction id
* @param amount - tvAmount
*/
void moneySpent(String txId, long amount);
/**
* moneyReceived - called when money received
* @param txId - transaction id
* @param amount - tvAmount
*/
void moneyReceived(String txId, long amount);
/**
* unconfirmedMoneyReceived - called when payment arrived in tx pool
* @param txId - transaction id
* @param amount - tvAmount
*/
void unconfirmedMoneyReceived(String txId, long amount);
/**
* newBlock - called when new block received
* @param height - block height
*/
void newBlock(long height);
/**
* updated - generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet;
*/
void updated();
/**
* refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously
*/
void refreshed();
}

@ -0,0 +1,230 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.model;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WalletManager {
final static String TAG = "WalletManager";
static {
System.loadLibrary("monerujo");
}
// no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it)
private static WalletManager Instance = null;
public static WalletManager getInstance() { // TODO not threadsafe
if (WalletManager.Instance == null) {
WalletManager.Instance = new WalletManager();
}
return WalletManager.Instance;
}
private WalletManager() {
this.managedWallets = new HashMap<>();
}
private Map<String, Wallet> managedWallets;
public Wallet getWallet(String walletId) {
return managedWallets.get(walletId);
}
private void manageWallet(String walletId, Wallet wallet) {
if (getWallet(walletId) != null) {
throw new IllegalStateException("Wallet already under management!");
}
Log.d(TAG, "Managing " + walletId);
managedWallets.put(walletId, wallet);
}
private void unmanageWallet(String walletId) {
if (getWallet(walletId) == null) {
throw new IllegalStateException("Wallet not under management!");
}
Log.d(TAG, "Unmanaging " + walletId);
managedWallets.remove(walletId);
}
public Wallet createWallet(String path, String password, String language) {
long walletHandle = createWalletJ(path, password, language, isTestNet());
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet.getName(), wallet);
return wallet;
}
public Wallet openWallet(String path, String password) {
long walletHandle = openWalletJ(path, password, isTestNet());
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet.getName(), wallet);
return wallet;
}
public Wallet recoveryWallet(String path, String mnemonic) {
Wallet wallet = recoveryWallet(path, mnemonic, 0);
manageWallet(wallet.getName(), wallet);
return wallet;
}
public Wallet recoveryWallet(String path, String mnemonic, long restoreHeight) {
long walletHandle = recoveryWalletJ(path, mnemonic, isTestNet(), restoreHeight);
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet.getName(), wallet);
return wallet;
}
private native long createWalletJ(String path, String password, String language, boolean isTestNet);
private native long openWalletJ(String path, String password, boolean isTestNet);
private native long recoveryWalletJ(String path, String mnemonic, boolean isTestNet, long restoreHeight);
private native long createWalletFromKeysJ(String path, String language,
boolean isTestNet,
long restoreHeight,
String addressString,
String viewKeyString,
String spendKeyString);
public native boolean closeJ(Wallet wallet);
public boolean close(Wallet wallet) {
String walletId = new File(wallet.getFilename()).getName();
unmanageWallet(walletId);
boolean closed = closeJ(wallet);
if (!closed) {
// in case we could not close it
// we unmanage it
manageWallet(walletId, wallet);
}
return closed;
}
public native boolean walletExists(String path);
public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
//public native List<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;
private String daemonAddress = null;
private boolean testnet = true;
public boolean isTestNet() {
if (daemonAddress == null) {
// assume testnet not explicitly initialised
throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
}
return testnet;
}
public void setDaemon(String address, boolean testnet) {
this.daemonAddress = address;
this.testnet = testnet;
setDaemonAddressJ(address);
}
public String getDaemonAddress() {
if (daemonAddress == null) {
// assume testnet not explicitly initialised
throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
}
return this.daemonAddress;
}
private native void setDaemonAddressJ(String address);
public native int getConnectedDaemonVersion();
public native long getBlockchainHeight();
public native long getBlockchainTargetHeight();
public native long getNetworkDifficulty();
public native double getMiningHashRate();
public native long getBlockTarget();
public native boolean isMining();
public native boolean startMining(String address, boolean background_mining, boolean ignore_battery);
public native boolean stopMining();
public native String resolveOpenAlias(String address, boolean dnssec_valid);
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
}

@ -0,0 +1,352 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.service;
import android.app.ProgressDialog;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
import android.util.Log;
import android.widget.Toast;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletListener;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
// Bind / Unbind
// Activity onCreate() / onDestroy()
// or
// Activity onStart() / onStop()
public class WalletService extends Service {
final static String TAG = "WalletService";
public static final String REQUEST = "request";
public static final String REQUEST_WALLET = "wallet";
public static final String REQUEST_CMD_LOAD = "load";
public static final String REQUEST_CMD_LOAD_PW = "walletPassword";
public static final int START_SERVICE = 1;
public static final int STOP_SERVICE = 2;
private MyWalletListener listener = null;
private class MyWalletListener implements WalletListener {
private Wallet wallet;
boolean updated = true;
Wallet getWallet() {
return wallet;
}
MyWalletListener(Wallet aWallet) {
if (aWallet == null) throw new IllegalArgumentException("Cannot open wallet!");
this.wallet = aWallet;
}
public void start() {
Log.d(TAG, "MyWalletListener.start()");
if (wallet == null) throw new IllegalStateException("No wallet!");
//acquireWakeLock();
wallet.setListener(this);
wallet.startRefresh();
}
public void stop() {
Log.d(TAG, "MyWalletListener.stop()");
if (wallet == null) throw new IllegalStateException("No wallet!");
wallet.pauseRefresh();
wallet.setListener(null);
//releaseWakeLock();
}
// WalletListener callbacks
public void moneySpent(String txId, long amount) {
Log.d(TAG, "moneySpent() " + amount + " @ " + txId);
}
public void moneyReceived(String txId, long amount) {
Log.d(TAG, "moneyReceived() " + amount + " @ " + txId);
}
public void unconfirmedMoneyReceived(String txId, long amount) {
Log.d(TAG, "unconfirmedMoneyReceived() " + amount + " @ " + txId);
}
long lastBlockTime = 0;
public void newBlock(long height) {
if (wallet == null) throw new IllegalStateException("No wallet!");
// don't flood with an update for every block ...
if (lastBlockTime < System.currentTimeMillis() - 2000) {
Log.d(TAG, "newBlock() @" + height + "with observer " + observer);
lastBlockTime = System.currentTimeMillis();
if (observer != null) {
observer.onRefreshed(wallet, false);
}
}
}
public void updated() {
Log.d(TAG, "updated() " + wallet.getBalance());
if (wallet == null) throw new IllegalStateException("No wallet!");
updated = true;
}
public void refreshed() {
if (wallet == null) throw new IllegalStateException("No wallet!");
Log.d(TAG, "refreshed() " + wallet.getBalance() + " sync=" + wallet.isSynchronized() + "with observer " + observer);
if (updated) {
if (observer != null) {
wallet.getHistory().refresh();
observer.onRefreshed(wallet, true);
updated = false;
}
}
}
}
/////////////////////////////////////////////
// communication back to client (activity) //
/////////////////////////////////////////////
// NB: This allows for only one observer, i.e. only a single activity bound here
private Observer observer = null;
public void setObserver(Observer anObserver) {
observer = anObserver;
Log.d(TAG, "setObserver " + observer);
}
public interface Observer {
void onRefreshed(Wallet wallet, boolean full);
void onProgress(String text);
void onProgress(int n);
}
private void showProgress(String text) {
if (observer != null) {
observer.onProgress(text);
}
}
private void showProgress(int n) {
if (observer != null) {
observer.onProgress(n);
}
}
//
public Wallet getWallet() {
if (listener == null) throw new IllegalStateException("no listener");
return listener.getWallet();
}
/////////////////////////////////////////////
/////////////////////////////////////////////
private Looper mServiceLooper;
private WalletService.ServiceHandler mServiceHandler;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "Handling " + msg.arg2);
switch (msg.arg2) {
case START_SERVICE: {
Bundle extras = msg.getData();
String walletId = extras.getString(REQUEST_WALLET, null);
String walletPw = extras.getString(REQUEST_CMD_LOAD_PW, null);
Log.d(TAG, "LOAD wallet " + walletId);// + ":" + walletPw);
if (walletId != null) {
start(walletId, walletPw); // TODO What if this fails?
}
}
break;
case STOP_SERVICE:
stop();
break;
default:
Log.e(TAG, "UNKNOWN " + msg.arg2);
}
}
}
@Override
public void onCreate() {
//mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//showNotification();
// We are using a HandlerThread and a Looper to avoid loading and closing
// concurrency
HandlerThread thread = new HandlerThread("WalletService",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new WalletService.ServiceHandler(mServiceLooper);
Log.d(TAG, "Service created");
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy()");
// Cancel the persistent notification.
//mNM.cancel(NOTIFICATION);
if (this.listener != null) {
Log.w(TAG, "onDestroy() with active listener");
// no need to stop() here because the wallet closing should have been triggered
// through onUnbind() already
}
}
public class WalletServiceBinder extends Binder {
public WalletService getService() {
return WalletService.this;
}
}
private final IBinder mBinder = new WalletServiceBinder();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// when the activity satrts the service, it expects to start it for a new wallet
// the service is possibly still occupied with saving the last opened wallet
// so we queue the open request
// this should not matter since the old activity is not getting updates
// and the new one is not listening yet (although it will be bound)
Log.d(TAG, "onStartCommand()");
//acquireWakeLock(); // we want to be awake for the fun stuff
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = mServiceHandler.obtainMessage();
msg.arg2 = START_SERVICE;
msg.setData(intent.getExtras());
mServiceHandler.sendMessage(msg);
//Log.d(TAG, "onStartCommand() message sent");
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// Very first client binds
Log.d(TAG, "onBind()");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind()");
// All clients have unbound with unbindService()
Message msg = mServiceHandler.obtainMessage();
msg.arg2 = STOP_SERVICE;
mServiceHandler.sendMessage(msg);
Log.d(TAG, "onUnbind() message sent");
return true; // true is important so that onUnbind is also called next time
}
private void start(String walletName, String walletPassword) {
// if there is an listener it is always started / syncing
Log.d(TAG, "start()");
showProgress(getString(R.string.status_wallet_loading));
showProgress(10);
if (listener == null) {
Log.d(TAG, "start() loadWallet");
Wallet aWallet = loadWallet(walletName, walletPassword);
listener = new MyWalletListener(aWallet);
listener.start();
showProgress(95);
}
Log.d(TAG, "start() done");
}
public void stop() {
Log.d(TAG, "stop()");
setObserver(null); // in case it was not reset already
if (listener != null) {
listener.stop();
Log.d(TAG, "stop() closing");
listener.getWallet().close();
Log.d(TAG, "stop() closed");
listener = null;
}
stopSelf();
// TODO ensure the Looper & thread actually stop and go away?
}
private Wallet loadWallet(String walletName, String walletPassword) {
String path = Helper.getWalletPath(getApplicationContext(), walletName);
//Log.d(TAG, "open wallet " + path);
Wallet wallet = openWallet(walletName, walletPassword);
//Log.d(TAG, "wallet opened: " + wallet);
if (wallet != null) {
//Log.d(TAG, wallet.getStatus().toString());
Log.d(TAG, "Using daemon " + WalletManager.getInstance().getDaemonAddress());
showProgress(55);
wallet.init(0);
showProgress(90);
Log.d(TAG, wallet.getConnectionStatus().toString());
}
return wallet;
}
private Wallet openWallet(String walletName, String walletPassword) {
String path = Helper.getWalletPath(getApplicationContext(), walletName);
showProgress(20);
Wallet wallet = null;
WalletManager walletMgr = WalletManager.getInstance();
Log.d(TAG, "WalletManager testnet=" + walletMgr.isTestNet());
showProgress(30);
if (walletMgr.walletExists(path)) {
Log.d(TAG, "open wallet " + path);
wallet = walletMgr.openWallet(path, walletPassword);
showProgress(60);
Log.d(TAG, "wallet opened");
Wallet.Status status = wallet.getStatus();
Log.d(TAG, "wallet status is " + status);
if (status != Wallet.Status.Status_Ok) {
Log.d(TAG, "wallet status is " + status);
WalletManager.getInstance().close(wallet); // TODO close() failed?
wallet = null;
// TODO what do we do with the progress??
}
}
return wallet;
}
}

@ -0,0 +1,88 @@
/**
* Copyright (c) 2017 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util;
import 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);
//d(TAG, "walletdir=" + walletDir.getAbsolutePath());
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,119 @@
<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>
<LinearLayout
android:id="@+id/llProgress"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone" >
<TextView
android:id="@+id/tvProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp"
android:text="Connecting..." />
<ProgressBar
android:id="@+id/pbProgress"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:progress="0"/>
</LinearLayout>
<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,41 @@
<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</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="status_syncing">Syncing:</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