From e9c74d4d9ce1b580b6fe0a96059b712efdade66d Mon Sep 17 00:00:00 2001 From: m2049r Date: Sun, 15 Apr 2018 16:33:20 +0200 Subject: [PATCH] Random fixes (#228) * adapt for android studio 3.1 and remove witness * set networktype from node * add monero logging for DEBUG builds * do not reset timestamps in apk * no witness * new version & apk naming --- .gitignore | 2 + app/build.gradle | 74 +++++----------- app/src/main/cpp/monerujo.cpp | 79 ++++++++++++++++++ .../com/m2049r/xmrwallet/LoginActivity.java | 2 + .../com/m2049r/xmrwallet/data/WalletNode.java | 4 + .../m2049r/xmrwallet/model/WalletManager.java | 17 +++- .../com/m2049r/xmrwallet/util/Helper.java | 16 ++++ build.gradle | 6 +- external-libs/gradle-witness.jar | Bin 16694 -> 0 bytes gradle.properties | 3 + gradle/wrapper/gradle-wrapper.properties | 4 +- 11 files changed, 148 insertions(+), 59 deletions(-) delete mode 100644 external-libs/gradle-witness.jar diff --git a/.gitignore b/.gitignore index 532b316..661833e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ *.iml /.idea/libraries /.idea/workspace.xml +/.idea/caches +/.idea/codeStyles /local.properties /captures .externalNativeBuild diff --git a/app/build.gradle b/app/build.gradle index aaa6b03..61a6797 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,15 +1,14 @@ apply plugin: 'com.android.application' -apply plugin: 'witness' android { compileSdkVersion 25 - buildToolsVersion '26.0.2' + buildToolsVersion '27.0.3' defaultConfig { applicationId "com.m2049r.xmrwallet" minSdkVersion 21 targetSdkVersion 25 - versionCode 86 - versionName "1.4.6 'Monero Spedner'" + versionCode 87 + versionName "1.4.7 'Monero Spedner'" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { @@ -17,9 +16,6 @@ android { arguments '-DANDROID_STL=c++_shared' } } - ndk { - abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' - } } buildTypes { @@ -52,59 +48,35 @@ android { // APKs for the same app that all have the same version information. android.applicationVariants.all { variant -> // Assigns a different version code for each output APK. - variant.outputs.each { + variant.outputs.all { output -> def abiName = output.getFilter(com.android.build.OutputFile.ABI) output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode + + if (abiName == null) abiName = "universal" + def v = "${variant.versionName}".replaceFirst(" .*\$", "").replace(".", "x") + outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk" } } } dependencies { - compile 'com.android.support:appcompat-v7:25.4.0' - compile 'com.android.support:design:25.4.0' - compile 'com.android.support:support-v4:25.4.0' - compile 'com.android.support:recyclerview-v7:25.4.0' - compile 'com.android.support:cardview-v7:25.4.0' - compile 'com.android.support.constraint:constraint-layout:1.0.2' - compile 'me.dm7.barcodescanner:zxing:1.9.8' + implementation 'com.android.support:appcompat-v7:25.4.0' + implementation 'com.android.support:design:25.4.0' + implementation 'com.android.support:support-v4:25.4.0' + implementation 'com.android.support:recyclerview-v7:25.4.0' + implementation 'com.android.support:cardview-v7:25.4.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation 'me.dm7.barcodescanner:zxing:1.9.8' - compile "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion" - compile "com.jakewharton.timber:timber:$rootProject.ext.timberVersion" + implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion" + implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion" - compile 'com.nulab-inc:zxcvbn:1.2.3' + implementation 'com.nulab-inc:zxcvbn:1.2.3' - testCompile "junit:junit:$rootProject.ext.junitVersion" - testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" - testCompile "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" - testCompile 'org.json:json:20140107' - testCompile 'net.jodah:concurrentunit:0.4.2' -} - -dependencyVerification { - verify = [ - 'com.android.support:design:3f409bf2019967ffc344cfaf11e52131fac982468a1707aaeb25bf3c52838966', - 'com.android.support:appcompat-v7:70551e62660db15b790c5275f56b9de4dd9407d1494d07c8f3dd5698f3638677', - 'com.android.support:transition:848270144fb180efd2bf928a00ed176dbbc5290badfd638390ffba90088df8b3', - 'me.dm7.barcodescanner:zxing:d43973c9527c23fa8e6d338c6a2c458e373ce1ac6bcaa3bc41d11ae49116000d', - 'me.dm7.barcodescanner:core:a5c8a704089b58029db166172ed8e55d756877d010a85a0b1c94fdc96ffb8f9a', - 'com.android.support:support-v4:ee44c481a1f4d6978568e223e8125379b52b2ececdd53450e09ebae144bd377d', - 'com.android.support:recyclerview-v7:a2fe121f9d01ed8980e97095b4a3fe9700a0aa0a7d4b0f8c594f765ad8455a0d', - 'com.android.support:cardview-v7:f3fbbe1fcfdbec7333c6a2c516c5fd511a909d1975271818e268d6fe297d8c70', - 'com.android.support.constraint:constraint-layout:b0c688cc2b7172608f8153a689d746da40f71e52d7e2fe2bfd9df2f92db77085', - 'com.android.support:animated-vector-drawable:628ab1d56a6ee4cbedf32617af8b2a1fe02964ed0628e8f898cc09ddba6e1835', - 'com.android.support:support-vector-drawable:077009d13882ee96f061e4bc2dbe7cce7ae1762d8297592a787ff741afbfb1f2', - 'com.android.support:support-fragment:316d35d4d2d2902057efad104a73e4bdb50bee260a7075678185b8cd71170945', - 'com.android.support:support-core-ui:e72ae29b823889686cff6fcb948d6745c2baf6d4c2af4fdffa1ec1e42e3833a3', - 'com.android.support:support-media-compat:566a161d9cb0083ef62a53e46b71ce5b3d455b8635b1a0a4ae28d96d4b583de8', - 'com.android.support:support-core-utils:34b8437dfa95ff28d29cf57ffa3b1354a9fa9bfe4059f0fd5ce2f5e4326a1748', - 'com.android.support:support-compat:54019c63614ce08b02d7b9605490cd2b29ba5b2505f394a9517450b5f72b30ca', - 'com.android.support:support-annotations:a774272036941b4e912eb426d70c848bde7f06a3bf5fb491f75a427dc6595270', - 'com.android.support.constraint:constraint-layout-solver:8c62525a9bc5cff5633a96cb9b32fffeccaf41b8841aa87fc22607070dea9b8d', - 'com.google.zxing:core:bba7724e02a997cec38213af77133ee8e24b0d5cf5fa7ecbc16a4fa93f11ee0d', - 'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850', - 'com.squareup.okhttp3:okhttp:7265adbd6f028aade307f58569d814835cd02bc9beffb70c25f72c9de50d61c4', - 'com.jakewharton.timber:timber:35c22867f2673132e97e17857d36bb2fc25f5790f0425406833ed0254d62fc66', - 'com.nulab-inc:zxcvbn:18d7862a6abd2705defec478d77dedadf8f3bb7cf811df22995494f05485785f', - ] + testImplementation "junit:junit:$rootProject.ext.junitVersion" + testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" + testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" + testImplementation 'org.json:json:20140107' + testImplementation 'net.jodah:concurrentunit:0.4.2' } diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp index 626f713..01f2342 100644 --- a/app/src/main/cpp/monerujo.cpp +++ b/app/src/main/cpp/monerujo.cpp @@ -1075,6 +1075,85 @@ Java_com_m2049r_xmrwallet_model_PendingTransaction_getTxCount(JNIEnv *env, jobje } +// these are all in Bitmonero::Wallet - which I find wrong, so they are here! +//static void init(const char *argv0, const char *default_log_base_name); +//static void debug(const std::string &category, const std::string &str); +//static void info(const std::string &category, const std::string &str); +//static void warning(const std::string &category, const std::string &str); +//static void error(const std::string &category, const std::string &str); +JNIEXPORT void JNICALL +Java_com_m2049r_xmrwallet_model_WalletManager_initLogger(JNIEnv *env, jobject instance, + jstring argv0, jstring default_log_base_name) { + + const char *_argv0 = env->GetStringUTFChars(argv0, NULL); + const char *_default_log_base_name = env->GetStringUTFChars(default_log_base_name, NULL); + + Bitmonero::Wallet::init(_argv0, _default_log_base_name); + + env->ReleaseStringUTFChars(argv0, _argv0); + env->ReleaseStringUTFChars(default_log_base_name, _default_log_base_name); +} + +JNIEXPORT void JNICALL +Java_com_m2049r_xmrwallet_model_WalletManager_logDebug(JNIEnv *env, jobject instance, + jstring category, jstring message) { + + const char *_category = env->GetStringUTFChars(category, NULL); + const char *_message = env->GetStringUTFChars(message, NULL); + + Bitmonero::Wallet::debug(_category, _message); + + env->ReleaseStringUTFChars(category, _category); + env->ReleaseStringUTFChars(message, _message); +} + +JNIEXPORT void JNICALL +Java_com_m2049r_xmrwallet_model_WalletManager_logInfo(JNIEnv *env, jobject instance, + jstring category, jstring message) { + + const char *_category = env->GetStringUTFChars(category, NULL); + const char *_message = env->GetStringUTFChars(message, NULL); + + Bitmonero::Wallet::info(_category, _message); + + env->ReleaseStringUTFChars(category, _category); + env->ReleaseStringUTFChars(message, _message); +} + +JNIEXPORT void JNICALL +Java_com_m2049r_xmrwallet_model_WalletManager_logWarning(JNIEnv *env, jobject instance, + jstring category, jstring message) { + + const char *_category = env->GetStringUTFChars(category, NULL); + const char *_message = env->GetStringUTFChars(message, NULL); + + Bitmonero::Wallet::warning(_category, _message); + + env->ReleaseStringUTFChars(category, _category); + env->ReleaseStringUTFChars(message, _message); +} + +JNIEXPORT void JNICALL +Java_com_m2049r_xmrwallet_model_WalletManager_logError(JNIEnv *env, jobject instance, + jstring category, jstring message) { + + const char *_category = env->GetStringUTFChars(category, NULL); + const char *_message = env->GetStringUTFChars(message, NULL); + + Bitmonero::Wallet::error(_category, _message); + + env->ReleaseStringUTFChars(category, _category); + env->ReleaseStringUTFChars(message, _message); +} + +JNIEXPORT void JNICALL +Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jobject instance, + jint level) { + Bitmonero::WalletManagerFactory::setLogLevel(level); +} + + + #ifdef __cplusplus } #endif diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index 48deca4..271d084 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -743,7 +743,9 @@ public class LoginActivity extends SecureActivity } void startLoginFragment() { + // we set these here because we cannot be ceratin we have permissions for storage before Helper.setMoneroHome(this); + Helper.initLogger(this); Fragment fragment = new LoginFragment(); getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, fragment).commit(); diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/WalletNode.java b/app/src/main/java/com/m2049r/xmrwallet/data/WalletNode.java index 378b5c4..42705ab 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/data/WalletNode.java +++ b/app/src/main/java/com/m2049r/xmrwallet/data/WalletNode.java @@ -86,6 +86,10 @@ public class WalletNode { return name; } + public NetworkType getNetworkType() { + return networkType; + } + public String getAddress() { return host + ":" + port; } diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java index 911e9e1..47b2ed1 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java @@ -41,6 +41,7 @@ public class WalletManager { if (WalletManager.Instance == null) { WalletManager.Instance = new WalletManager(); } + return WalletManager.Instance; } @@ -215,7 +216,7 @@ public class WalletManager { //public void setDaemon(String address, NetworkType networkType, String username, String password) { public void setDaemon(WalletNode walletNode) { this.daemonAddress = walletNode.getAddress(); - this.networkType = networkType; + this.networkType = walletNode.getNetworkType(); this.daemonUsername = walletNode.getUsername(); this.daemonPassword = walletNode.getPassword(); setDaemonAddressJ(daemonAddress); @@ -269,5 +270,17 @@ public class WalletManager { //TODO static std::tuple checkUpdates(const std::string &software, const std::string &subdir); - + static public native void initLogger(String argv0, String defaultLogBaseName); + //TODO: maybe put these in an enum like in monero core - but why? + static public int LOGLEVEL_SILENT = -1; + static public int LOGLEVEL_WARN = 0; + static public int LOGLEVEL_INFO = 1; + static public int LOGLEVEL_DEBUG = 2; + static public int LOGLEVEL_TRACE = 3; + static public int LOGLEVEL_MAX = 4; + static public native void setLogLevel(int level); + static public native void logDebug(String category, String message); + static public native void logInfo(String category, String message); + static public native void logWarning(String category, String message); + static public native void logError(String category, String message); } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java index 0c8ea06..db5d823 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java @@ -38,6 +38,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.inputmethod.InputMethodManager; +import com.m2049r.xmrwallet.BuildConfig; import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.Wallet; @@ -287,4 +288,19 @@ public class Helper { throw new IllegalStateException(ex); } } + + static public void initLogger(Context context) { + if (BuildConfig.DEBUG) { + initLogger(context, WalletManager.LOGLEVEL_DEBUG); + } + // no logger if not debug + } + + // TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ? + static public void initLogger(Context context, int level) { + String home = getStorage(context, HOME_DIR).getAbsolutePath(); + WalletManager.initLogger(home + "/monerujo", "monerujo.log"); + if (level >= WalletManager.LOGLEVEL_SILENT) + WalletManager.setLogLevel(level); + } } diff --git a/build.gradle b/build.gradle index b338480..30c6cfd 100644 --- a/build.gradle +++ b/build.gradle @@ -6,10 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - classpath files('external-libs/gradle-witness.jar') - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.android.tools.build:gradle:3.1.1' } } @@ -27,6 +24,7 @@ task clean(type: Delete) { } ext { + apkName = 'monerujo' okHttpVersion = '3.9.0' junitVersion = '4.12' mockitoVersion = '1.10.19' diff --git a/external-libs/gradle-witness.jar b/external-libs/gradle-witness.jar deleted file mode 100644 index a82cb01f921c0691c3be809972b7f8daea75cd9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16694 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