From d22fdfe2dc657d987c8e962860ab68c341027d1d Mon Sep 17 00:00:00 2001 From: m2049r Date: Tue, 13 Aug 2024 17:28:04 +0200 Subject: [PATCH] Sidekick Support for Monerokon --- .gitignore | 8 +- app/build.gradle | 14 +- app/src/main/AndroidManifest.xml | 5 + app/src/main/cpp/monerujo.cpp | 92 ++- app/src/main/cpp/monerujo.h | 48 +- app/src/main/ic_launcher-playstore.png | Bin 0 -> 13511 bytes .../com/m2049r/xmrwallet/BaseActivity.java | 96 ++- .../m2049r/xmrwallet/BluetoothFragment.java | 326 +++++++++ .../m2049r/xmrwallet/GenerateFragment.java | 38 +- .../xmrwallet/GenerateReviewFragment.java | 7 +- .../com/m2049r/xmrwallet/LoginActivity.java | 126 ++-- .../com/m2049r/xmrwallet/LoginFragment.java | 43 +- .../xmrwallet/SidekickConnectFragment.java | 172 +++++ .../m2049r/xmrwallet/SubaddressFragment.java | 2 +- .../com/m2049r/xmrwallet/WalletActivity.java | 93 +-- .../xmrwallet/XmrWalletApplication.java | 9 +- .../m2049r/xmrwallet/data/BluetoothInfo.java | 42 ++ .../layout/BluetoothInfoAdapter.java | 147 ++++ .../layout/TransactionInfoAdapter.java | 2 +- .../com/m2049r/xmrwallet/ledger/Hardware.java | 4 + .../com/m2049r/xmrwallet/ledger/Ledger.java | 3 +- .../com/m2049r/xmrwallet/model/Wallet.java | 6 +- .../m2049r/xmrwallet/model/WalletManager.java | 9 +- .../onboarding/OnBoardingManager.java | 2 + .../xmrwallet/service/BluetoothService.java | 662 ++++++++++++++++++ .../service/MoneroHandlerThread.java | 2 +- .../com/m2049r/xmrwallet/util/Flasher.java | 65 ++ .../drawable-v24/ic_launcher_foreground.xml | 32 + app/src/main/res/drawable/ic_bluetooth_24.xml | 10 + .../drawable/ic_bluetooth_connected_24.xml | 10 + .../res/drawable/ic_bluetooth_disabled_24.xml | 10 + .../drawable/ic_bluetooth_searching_24.xml | 11 + .../res/drawable/ic_launcher_background.xml | 97 ++- .../res/drawable/ic_launcher_foreground.xml | 12 - .../main/res/drawable/ic_sidekick_restore.xml | 10 + app/src/main/res/layout/activity_sidekick.xml | 33 + app/src/main/res/layout/activity_wallet.xml | 5 + .../main/res/layout/bluetooth_device_name.xml | 18 + .../main/res/layout/fragment_bluetooth.xml | 50 ++ app/src/main/res/layout/fragment_login.xml | 173 ++--- app/src/main/res/layout/fragment_sidekick.xml | 82 +++ .../res/layout/fragment_sidekick_connect.xml | 40 ++ app/src/main/res/layout/item_bluetooth.xml | 39 ++ app/src/main/res/layout/layout_fabmenu.xml | 55 +- .../main/res/menu/create_wallet_ledger.xml | 2 +- .../main/res/menu/create_wallet_sidekick.xml | 12 + app/src/main/res/menu/list_menu.xml | 6 + .../main/res/menu/sidekick_connect_menu.xml | 10 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2667 -> 0 bytes app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1724 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4325 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 3578 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1917 -> 0 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 1388 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2800 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 2390 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 3701 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 2486 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 6277 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 5178 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 5431 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 3050 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 9265 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 7206 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 7132 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 5614 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 13045 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 9770 bytes app/src/main/res/values-cat/help.xml | 33 + app/src/main/res/values-cat/strings.xml | 13 + app/src/main/res/values-de/help.xml | 33 + app/src/main/res/values-de/strings.xml | 13 + app/src/main/res/values-el/help.xml | 33 + app/src/main/res/values-el/strings.xml | 13 + app/src/main/res/values-eo/help.xml | 33 + app/src/main/res/values-eo/strings.xml | 13 + app/src/main/res/values-es/help.xml | 42 +- app/src/main/res/values-es/strings.xml | 13 + app/src/main/res/values-et/help.xml | 33 + app/src/main/res/values-et/strings.xml | 13 + app/src/main/res/values-fa/help.xml | 33 + app/src/main/res/values-fa/strings.xml | 13 + app/src/main/res/values-fr/help.xml | 33 + app/src/main/res/values-fr/strings.xml | 13 + app/src/main/res/values-he/help.xml | 33 + app/src/main/res/values-he/strings.xml | 13 + app/src/main/res/values-hu/help.xml | 33 + app/src/main/res/values-hu/strings.xml | 13 + app/src/main/res/values-it/help.xml | 33 + app/src/main/res/values-it/strings.xml | 13 + app/src/main/res/values-ja/help.xml | 33 + app/src/main/res/values-ja/strings.xml | 13 + app/src/main/res/values-nb/help.xml | 33 + app/src/main/res/values-nb/strings.xml | 13 + app/src/main/res/values-nl/help.xml | 33 + app/src/main/res/values-nl/strings.xml | 13 + app/src/main/res/values-pt-rBR/help.xml | 33 + app/src/main/res/values-pt-rBR/strings.xml | 13 + app/src/main/res/values-pt/help.xml | 33 + app/src/main/res/values-pt/strings.xml | 13 + app/src/main/res/values-ro/help.xml | 33 + app/src/main/res/values-ro/strings.xml | 13 + app/src/main/res/values-ru/help.xml | 33 + app/src/main/res/values-ru/strings.xml | 13 + app/src/main/res/values-sk/help.xml | 33 + app/src/main/res/values-sk/strings.xml | 13 + app/src/main/res/values-sr/help.xml | 33 + app/src/main/res/values-sr/strings.xml | 13 + app/src/main/res/values-sv/help.xml | 33 + app/src/main/res/values-sv/strings.xml | 13 + app/src/main/res/values-ta/help.xml | 33 + app/src/main/res/values-ta/strings.xml | 13 + app/src/main/res/values-uk/help.xml | 33 + app/src/main/res/values-uk/strings.xml | 13 + app/src/main/res/values-zh-rCN/help.xml | 33 + app/src/main/res/values-zh-rCN/strings.xml | 13 + app/src/main/res/values-zh-rTW/help.xml | 33 + app/src/main/res/values-zh-rTW/strings.xml | 13 + app/src/main/res/values/help.xml | 47 +- app/src/main/res/values/strings.xml | 31 +- app/src/main/res/values/styles.xml | 3 + build.gradle | 2 +- external-libs/Makefile | 12 +- external-libs/android32.Dockerfile | 34 +- external-libs/android64.Dockerfile | 34 +- external-libs/android64_x86.Dockerfile | 34 +- gradle/wrapper/gradle-wrapper.properties | 5 +- 127 files changed, 3722 insertions(+), 379 deletions(-) create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/com/m2049r/xmrwallet/BluetoothFragment.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/SidekickConnectFragment.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/data/BluetoothInfo.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/layout/BluetoothInfoAdapter.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/ledger/Hardware.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/service/BluetoothService.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/util/Flasher.java create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_bluetooth_24.xml create mode 100644 app/src/main/res/drawable/ic_bluetooth_connected_24.xml create mode 100644 app/src/main/res/drawable/ic_bluetooth_disabled_24.xml create mode 100644 app/src/main/res/drawable/ic_bluetooth_searching_24.xml delete mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_sidekick_restore.xml create mode 100644 app/src/main/res/layout/activity_sidekick.xml create mode 100644 app/src/main/res/layout/bluetooth_device_name.xml create mode 100644 app/src/main/res/layout/fragment_bluetooth.xml create mode 100644 app/src/main/res/layout/fragment_sidekick.xml create mode 100644 app/src/main/res/layout/fragment_sidekick_connect.xml create mode 100644 app/src/main/res/layout/item_bluetooth.xml create mode 100644 app/src/main/res/menu/create_wallet_sidekick.xml create mode 100644 app/src/main/res/menu/sidekick_connect_menu.xml delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/.gitignore b/.gitignore index dc61c4ff..ed76356c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,12 +8,8 @@ .DS_Store /app/build /app/release -/app/alpha -/app/prod -/app/alphaMainnet -/app/prodMainnet -/app/alphaStagenet -/app/prodStagenet +/app/alpha* +/app/prod* /app/.cxx /monerujo.id /external-libs/VERSION diff --git a/app/build.gradle b/app/build.gradle index ae17246d..e6a60be3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { compileSdk 34 minSdkVersion 21 targetSdkVersion 33 - versionCode 3311 - versionName "3.3.11 'Argentina'" + versionCode 4005 + versionName "4.0.5 'Sidekick'" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { @@ -24,7 +24,7 @@ android { } } - flavorDimensions 'type', 'net' + flavorDimensions = ['type', 'net'] productFlavors { mainnet { dimension 'net' @@ -132,10 +132,10 @@ static def getId(name) { } dependencies { - implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0")) + implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.22")) - implementation 'androidx.core:core:1.12.0' - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.core:core:1.13.1' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.cardview:cardview:1.0.0' @@ -143,7 +143,7 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.preference:preference:1.2.1' - implementation 'com.google.android.material:material:1.11.0' + implementation 'com.google.android.material:material:1.12.0' implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation "com.squareup.okhttp3:okhttp:4.12.0" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4e0e0e1e..a592db59 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,11 @@ android:name="android.hardware.camera" android:required="false" /> + + + + + diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp index 773c8d5a..4f3dd0ca 100644 --- a/app/src/main/cpp/monerujo.cpp +++ b/app/src/main/cpp/monerujo.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017 m2049r + * Copyright (c) 2017-2024 m2049r *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,6 @@ #include "monerujo.h" #include "wallet2_api.h" -//TODO explicit casting jlong, jint, jboolean to avoid warnings - #ifdef __cplusplus extern "C" { @@ -34,7 +32,6 @@ extern "C" #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG,__VA_ARGS__) static JavaVM *cachedJVM; -static jclass class_String; static jclass class_ArrayList; static jclass class_WalletListener; static jclass class_CoinsInfo; @@ -42,17 +39,11 @@ static jclass class_TransactionInfo; static jclass class_Transfer; static jclass class_Ledger; static jclass class_WalletStatus; +static jclass class_BluetoothService; +static jclass class_SidekickService; std::mutex _listenerMutex; -//void jstringToString(JNIEnv *env, std::string &str, jstring jstr) { -// if (!jstr) return; -// const int len = env->GetStringUTFLength(jstr); -// const char *chars = env->GetStringUTFChars(jstr, nullptr); -// str.assign(chars, len); -// env->ReleaseStringUTFChars(jstr, chars); -//} - JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { cachedJVM = jvm; LOGI("JNI_OnLoad"); @@ -62,8 +53,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { } //LOGI("JNI_OnLoad ok"); - class_String = static_cast(jenv->NewGlobalRef( - jenv->FindClass("java/lang/String"))); class_ArrayList = static_cast(jenv->NewGlobalRef( jenv->FindClass("java/util/ArrayList"))); class_CoinsInfo = static_cast(jenv->NewGlobalRef( @@ -78,6 +67,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger"))); class_WalletStatus = static_cast(jenv->NewGlobalRef( jenv->FindClass("com/m2049r/xmrwallet/model/Wallet$Status"))); + class_BluetoothService = static_cast(jenv->NewGlobalRef( + jenv->FindClass("com/m2049r/xmrwallet/service/BluetoothService"))); return JNI_VERSION_1_6; } #ifdef __cplusplus @@ -1686,6 +1677,79 @@ int LedgerFind(char *buffer, size_t len) { return ret; } +// +// SidekickWallet Stuff +// + +/** + * @brief BtExchange - exchange data with Monerujo Device + * @param request - buffer for data to send + * @param request_len - length of data to send + * @param response - buffer for received data + * @param max_resp_len - size of receive buffer + * + * @return length of received data in response or -1 if error, -2 if response buffer too small + */ +int BtExchange( + unsigned char *request, + unsigned int request_len, + unsigned char *response, + unsigned int max_resp_len) { + JNIEnv *jenv; + int envStat = attachJVM(&jenv); + if (envStat == JNI_ERR) return -16; + + jmethodID exchangeMethod = jenv->GetStaticMethodID(class_BluetoothService, "Exchange", + "([B)[B"); + + auto reqLen = static_cast(request_len); + jbyteArray reqData = jenv->NewByteArray(reqLen); + jenv->SetByteArrayRegion(reqData, 0, reqLen, (jbyte *) request); + LOGD("BtExchange cmd: 0x%02x with %u bytes", request[0], reqLen); + auto dataRecv = (jbyteArray) + jenv->CallStaticObjectMethod(class_BluetoothService, exchangeMethod, reqData); + jenv->DeleteLocalRef(reqData); + if (dataRecv == nullptr) { + detachJVM(jenv, envStat); + LOGD("BtExchange: error reading"); + return -1; + } + jsize respLen = jenv->GetArrayLength(dataRecv); + LOGD("BtExchange response is %u bytes", respLen); + if (respLen <= max_resp_len) { + jenv->GetByteArrayRegion(dataRecv, 0, respLen, (jbyte *) response); + jenv->DeleteLocalRef(dataRecv); + detachJVM(jenv, envStat); + return static_cast(respLen);; + } else { + jenv->DeleteLocalRef(dataRecv); + detachJVM(jenv, envStat); + LOGE("BtExchange response buffer too small: %u < %u", respLen, max_resp_len); + return -2; + } +} + +/** + * @brief ConfirmTransfers + * @param transfers - string of "fee (':' address ':' amount)+" + * + * @return true on accept, false on reject + */ +bool ConfirmTransfers(const char *transfers) { + JNIEnv *jenv; + int envStat = attachJVM(&jenv); + if (envStat == JNI_ERR) return -16; + + jmethodID confirmMethod = jenv->GetStaticMethodID(class_SidekickService, "ConfirmTransfers", + "(Ljava/lang/String;)Z"); + + jstring _transfers = jenv->NewStringUTF(transfers); + auto confirmed = + jenv->CallStaticBooleanMethod(class_SidekickService, confirmMethod, _transfers); + jenv->DeleteLocalRef(_transfers); + return confirmed; +} + #ifdef __cplusplus } #endif diff --git a/app/src/main/cpp/monerujo.h b/app/src/main/cpp/monerujo.h index 0fb34444..f11df08d 100644 --- a/app/src/main/cpp/monerujo.h +++ b/app/src/main/cpp/monerujo.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017 m2049r + * Copyright (c) 2017-2024 m2049r *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ #include +#include + /* #include @@ -28,6 +30,10 @@ #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) */ +void ThrowException(JNIEnv *jenv, const char* type, const char* msg) { + jenv->ThrowNew(jenv->FindClass(type), msg); +} + jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handle") { jclass c = env->GetObjectClass(obj); return env->GetFieldID(c, fieldName, "J"); // of type long @@ -35,8 +41,16 @@ jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handl template T *getHandle(JNIEnv *env, jobject obj, const char *fieldName = "handle") { + return reinterpret_cast(env->GetLongField(obj, getHandleField(env, obj, fieldName))); +} + +template +void destroyNativeObject(JNIEnv *env, T nativeObjectHandle, jobject obj, const char *fieldName = "handle") { jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName)); - return reinterpret_cast(handle); + if (handle != 0) { + ThrowException(env, "java/lang/IllegalStateException", "invalid handle (destroy)"); + } + delete reinterpret_cast(nativeObjectHandle); } void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) { @@ -54,7 +68,7 @@ extern "C" { #endif -extern const char* const MONERO_VERSION; // the actual monero core version +extern const char *const MONERO_VERSION; // the actual monero core version // from monero-core crypto/hash-ops.h - avoid #including monero code here enum { @@ -62,18 +76,40 @@ enum { HASH_DATA_AREA = 136 }; -void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height); +void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, + uint64_t height); inline void slow_hash(const void *data, const size_t length, char *hash) { cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/); } inline void slow_hash_broken(const void *data, char *hash, int variant) { - cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, 0 /*height*/); + cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, + 0 /*height*/); } - #ifdef __cplusplus } #endif +namespace Monerujo { + class SidekickWallet { + public: + enum Status { + Status_Ok, + Status_Error, + Status_Critical + }; + + SidekickWallet(uint8_t networkType, std::string a, std::string b); + + ~SidekickWallet(); + + std::string call(int commandId, const std::string &request); + + void reset(); + + Status status() const; + + }; +} #endif //XMRWALLET_WALLET_LIB_H diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..b44759f5d76edb24c91bf51c75399483a32a6d11 GIT binary patch literal 13511 zcmeHuS6Ecb)@?O8h$Ime5ET#s$qE7z)Gbj(Tasip3T~7nId!+9qDWA(5*1o(vLHF5 zf*^uqMKTCYme7Qz!>vZoKA!OZPxs+I+?NGkuT@pE#vF6ZS+#0Cym&!}gKZxh0DwbJ z_YWihbnss~z`_Loton8n0XP<+_s1zCU+ek7kW!^L|(i~(ip7t__CPW-^A);nt0vc$GGqE4RTG- zM^}z2A-unL$RKX6fXV~FNW2kA697z`4jgynAk7F)Zg}y1055fC7~pmPA$a-0g@8|m zu)u#${sAxl|Iz=45E#iGO(Z;^`uN_4o)He~N8Z6z>Mi7u#eCLoN2#rIH7BDJCfd}D zZcPbhP`)9e)I3~S8(j@|`QCv(y>NW2o#)(_OScr)o{+8uV~Go0JWN)H?5xJ5a$+*p z=Ead!j(HOf3;BQAnKnlZh3>)ILRNV0tNtr;Q2SO~TgVATN zd6HfS?BZ!0Mod8}eb&=<*RGouFCLaB*gu5LJseYmVr@V@j{O19B_* zv=DAeR^U*v;ZT_C_w%_0=5b?V9-;|l>BN8sm&)PU6q3!fOT#)^*Msmm*%Z+b4`Up|hb`rAh1rG03tQVs7mbF^fP$EdCdKg=mS=>_O zl$*RG0K>+u1D}f}HK;qQ5UEpH=u&MqcAck1^{liey3_DOdHSd@LYJ{2cJo)2z# zp%*1o9V^c{a0((d9$K0tL0GJPu-GMi#SVAiyEsL%{^{19pS&n@S?xud1?F|5HzXl% z`i-;o34@uU%Cl}BOeaEUkt63~Au;5h5{k(m|F(AciXB$8rzcy~=alBk;{9j{1%8*k zzur>sQ*~EnBdI0zz;x3u@MVSP}WVL)J#!pDFzbi zzQqiRoR8-oXbPbBt1?3{Yc2}-%i$s?_>&I37c0tGosdRWEr#L)DdiqJ$8>^Dp80=I z`n4SQ)jYkb<0@AFE?M-1y-)L}0h?P>`;V5da7GU>l0`MQt~Y;DJ$-9RNV2?$GiD$x zNGzcwlOfvY$y`Ei0A+nilh>tVee{)dz<&Cfn(&1w56)@79j(ModAHFg)TKRNbkt4O z9_**De<$Q8m^RK0+`4L027F>5;2qJvs5bei+E?hvM~06ZitVw!eS#92evgcX{Leuq z9_=w4j_WrS)+2AVW7objBdx0SB^NcsV*3{2mx~NCR_lzBWiElMwbZ^;EX9&M#oVCa_`{s_gJ>~Zpc)jPoju?+jpC=DT-bNtJP9XJtY(&31E?it+=|Yn6e1tu|QWcA{ z!$C3xsJw)6W&i#3`8gLOV=b_rRAXe7N6D_d)E`v!oiA)p-q2)yxf*LrTANuM8c1Sx zV?hArR~)9A)7m=A#D^OV>7wZOauP%8rjoMx7Y!$}#g6i0`DiJCGB55FTxO`hZh7=x zwzjTIN$8y`A=w;uHplzC8BX!Mz+V=ibPxJR^!JP(pvK?saIo^5w#Ef5)!f}YM_TxH zHJj39j-$-0NNA#>ce$!rEUy?&`yI{t@xWRIDIX=&I1UyL0Ncz_G*ZV~?UhKSy1#Ww z^$3fE%u{T{kY<>sY^kfU6mo0TSz-uvp

U1%scze}v@=ixLM}GP2*2@NuPsn}W?e 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 H?VbMvA6mgz literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java b/app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java index b714ea27..1ab1d5bd 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java @@ -16,7 +16,17 @@ package com.m2049r.xmrwallet; +import android.Manifest; +import android.app.AlertDialog; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -24,7 +34,8 @@ import android.os.PowerManager; import androidx.activity.OnBackPressedCallback; import androidx.annotation.CallSuper; -import androidx.fragment.app.FragmentActivity; +import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.dialog.ProgressDialog; @@ -159,4 +170,87 @@ public class BaseActivity extends SecureActivity barcodeData = null; return popped; } + + public boolean isNetworkAvailable() { + ConnectivityManager connectivityManager = (ConnectivityManager) getApplication().getSystemService(Context.CONNECTIVITY_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Network network = connectivityManager.getActiveNetwork(); + if (network == null) return false; + NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network); + return networkCapabilities != null && (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH)); + } else { + NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); + return networkInfo != null && networkInfo.isConnected(); + } + } + + static private final int REQUEST_CODE_BLUETOOTH_PERMISSIONS = 32423; + + void btPermissionGranted() { + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_CODE_BLUETOOTH_PERMISSIONS) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) { + btPermissionGranted(); + } // else onResume() takes care of trying again + } + } + + private void showBtPermissionsDialog() { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setMessage(R.string.bluetooth_permissions); + alertDialogBuilder.setPositiveButton(R.string.bluetooth_permissions_ok, + (dialog, which) -> requestBtPermissions()); + alertDialogBuilder.setCancelable(false); + AlertDialog alertDialog = alertDialogBuilder.create(); + alertDialog.show(); + } + + private void showAppInfoDialog() { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setMessage(R.string.bluetooth_permissions); + alertDialogBuilder.setPositiveButton(R.string.bluetooth_permissions_settings, (dialog, which) -> { + Intent i = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + i.addCategory(Intent.CATEGORY_DEFAULT); + i.setData(Uri.parse("package:" + getPackageName())); + startActivity(i); + }); + alertDialogBuilder.setNegativeButton(R.string.bluetooth_permissions_cancel, (dialog, which) -> { + finish(); + }); + alertDialogBuilder.setCancelable(false); + AlertDialog alertDialog = alertDialogBuilder.create(); + alertDialog.show(); + } + + private void requestBtPermissions() { + ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN}, REQUEST_CODE_BLUETOOTH_PERMISSIONS); + } + + private boolean firstCheck = true; + + void checkBtPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if ((ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) || + (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)) { + if (shouldShowRequestPermissionRationale(android.Manifest.permission.BLUETOOTH_SCAN) || shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) { + showBtPermissionsDialog(); + } else { + if (firstCheck) { + requestBtPermissions(); + } else { + showAppInfoDialog(); + } + } + } else { + btPermissionGranted(); + } + firstCheck = false; + } else { + btPermissionGranted(); + } + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/BluetoothFragment.java b/app/src/main/java/com/m2049r/xmrwallet/BluetoothFragment.java new file mode 100644 index 00000000..d7ad8410 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/BluetoothFragment.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// mostly from BluetoothChatFragment https://github.com/android/connectivity-samples + +package com.m2049r.xmrwallet; + +import android.app.Activity; +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.m2049r.xmrwallet.service.BluetoothService; +import com.m2049r.xmrwallet.util.Flasher; + +import java.security.SecureRandom; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import timber.log.Timber; + +public class BluetoothFragment extends Fragment { + + public BluetoothFragment() { + super(); + } + + interface Listener { + void onDeviceConnected(String connectedDeviceName); + + void abort(String message); + + void onReceive(int commandId); + } + + //TODO enable discover only after wallet is loaded + Listener activityCallback; + + // Intent request codes + private static final int REQUEST_ENABLE_BT = 3; + + private ImageView btIcon; + private ProgressBar pbConnecting; + private TextView btCode; + private TextView btName; + + private String connectedDeviceName = null; + + private BluetoothAdapter bluetoothAdapter = null; + + private BluetoothService bluetoothService = null; + + public enum Mode { + CLIENT, SERVER + } + + private Mode mode = Mode.CLIENT; + + public BluetoothFragment(Mode mode) { + super(); + this.mode = mode; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Get local Bluetooth adapter + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + // If the adapter is null, then Bluetooth is not supported + if (bluetoothAdapter == null) { + if (activityCallback != null) + activityCallback.abort("Bluetooth is not available"); //TODO strings.xml + } + } + + public void start() { + if (bluetoothAdapter == null) return; + + // If BT is not on, request that it be enabled. + // setupComm() will then be called during onActivityResult + if (!bluetoothAdapter.isEnabled()) { + Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + startActivityForResult(enableIntent, REQUEST_ENABLE_BT); + } else if (bluetoothService == null) { + setupCommunication(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (bluetoothService != null) { + bluetoothService.setUiHandler(null); + bluetoothService = null; + } + // The BluetoothService is stopped in LoginActivity::onDestroy + } + + @Override + public void onPause() { + Timber.d("onPause %s", mode); + super.onPause(); + } + + @Override + public void onResume() { + Timber.d("onResume %s", mode); + super.onResume(); + + // Performing this check in onResume() covers the case in which BT was + // not enabled during onStart(), so we were paused to enable it... + // onResume() will be called when ACTION_REQUEST_ENABLE activity returns. + if (bluetoothService != null) { + // Only if the state is STATE_NONE, do we know that we haven't started already + if (!bluetoothService.isStarted()) { + // Start the Bluetooth services + bluetoothService.start(); + } + } + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (context instanceof BluetoothFragment.Listener) { + activityCallback = (BluetoothFragment.Listener) context; + } else { + throw new ClassCastException(context.toString() + + " must implement Listener"); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + Timber.d("onCreateView"); + return inflater.inflate(R.layout.fragment_bluetooth, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + Timber.d("onViewCreated"); + super.onCreate(savedInstanceState); + if (savedInstanceState != null) return; + btName = view.findViewById(R.id.btName); + btCode = view.findViewById(R.id.btCode); + btIcon = view.findViewById(R.id.btIcon); + pbConnecting = view.findViewById(R.id.pbConnecting); + setConnecting(false); + setInfo(null, null); + } + + /** + * Set up the UI and background operations for comms. + */ + private void setupCommunication() { + Timber.d("startCommunication()"); + if (!isAdded()) { + return; + } + + if (bluetoothService != null) throw new IllegalStateException("bluetoothService != null"); + + // Initialize the BluetoothService to perform bluetooth connections + bluetoothService = BluetoothService.GetInstance(); + bluetoothService.setUiHandler(handler); + if (mode == Mode.SERVER) + bluetoothService.start(); + setInfo(bluetoothService.getConnectedName(), bluetoothService.getConnectedCode()); + showState(bluetoothService.getState()); + } + + private void showState(int state) { + if (!isAdded()) return; + Light light; + switch (state) { + case BluetoothService.State.LISTEN: + light = Light.LISTEN; + break; + case BluetoothService.State.CONNECTING: + light = Light.CONNECTING; + break; + case BluetoothService.State.CONNECTED: + light = Light.CONNECTED; + break; + case BluetoothService.State.NONE: + default: + light = Light.NONE; + } + final Flasher flash = new Flasher(requireContext(), light); + btIcon.setImageDrawable(flash.getDrawable()); + btFlash = flash; + } + + @Getter + @RequiredArgsConstructor + public enum Light implements Flasher.Light { + LISTEN(R.drawable.ic_bluetooth_24), + + CONNECTING(R.drawable.ic_bluetooth_searching_24), + + CONNECTED(R.drawable.ic_bluetooth_connected_24), + + NONE(R.drawable.ic_bluetooth_disabled_24); + + final private int drawableId; + } + + Flasher btFlash; + + private void flashState() { + if (btFlash != null) + btFlash.flash(getView()); + } + + /** + * The Handler that gets information back from the BluetoothService + */ + private final Handler handler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case BluetoothService.MessageType.STATE_CHANGE: + showState(msg.arg1); + if (msg.arg1 <= BluetoothService.State.LISTEN) { + setInfo(null, null); + connectedDeviceName = null; + activityCallback.onDeviceConnected(null); // i.e. disconnected - ugly :( + setConnecting(false); + } + break; + case BluetoothService.MessageType.WRITE: + Timber.d("WRITE_MESSAGE: %d bytes", msg.arg1); + break; + case BluetoothService.MessageType.READ_CMD: + Timber.d("READ_COMMAND 0x%x (%d bytes)", msg.arg2, msg.arg1); + if (activityCallback != null) { + activityCallback.onReceive(msg.arg2); + } + break; + case BluetoothService.MessageType.CODE: + Timber.d("CODE: %s", msg.obj); + btCode.setText((String) msg.obj); + break; + case BluetoothService.MessageType.DEVICE_NAME: + connectedDeviceName = (String) msg.obj; + setInfo(connectedDeviceName, null); + activityCallback.onDeviceConnected(connectedDeviceName); + setConnecting(false); + if (mode == Mode.CLIENT) { + final int code = new SecureRandom().nextInt(10000); + bluetoothService.write(code); + } + break; + case BluetoothService.MessageType.TOAST: + if (isAdded()) + Toast.makeText(getActivity(), (String) msg.obj, Toast.LENGTH_SHORT).show(); + break; + } + flashState(); + } + }; + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_ENABLE_BT) {// When the request to enable Bluetooth returns + if (resultCode == Activity.RESULT_OK) { + // Bluetooth is now enabled, so set up a chat session + setupCommunication(); + } else { + // User did not enable Bluetooth or an error occurred + Timber.d("BT not enabled"); + if (activityCallback != null) + activityCallback.abort("Bluetooth is not enabled"); //TODO strings.xml + } + } else { + Timber.w("Unhandled request code %d", requestCode); + } + flashState(); + } + + private void setConnecting(boolean enable) { + pbConnecting.setVisibility(enable ? View.VISIBLE : View.INVISIBLE); + } + + public void connectDevice(String address) { + setConnecting(true); + bluetoothService.connect(bluetoothAdapter.getRemoteDevice(address)); + } + + private void setInfo(String name, String code) { + try { + btName.setText(name == null ? getResources().getString(R.string.sidekick_not_connected) : name); + btCode.setText(getResources().getString(R.string.sidekick_pin, code != null ? code : "----")); + } catch (IllegalStateException ex) { // no context, so no strings + // never mind + } + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java index 57df4b1a..b37020f8 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java @@ -18,7 +18,6 @@ package com.m2049r.xmrwallet; import android.app.Activity; import android.content.Context; -import android.content.DialogInterface; import android.os.Bundle; import android.text.Editable; import android.text.Html; @@ -67,8 +66,20 @@ public class GenerateFragment extends Fragment { static final String TYPE_KEY = "key"; static final String TYPE_SEED = "seed"; static final String TYPE_LEDGER = "ledger"; + static final String TYPE_SIDEKICK = "sidekick"; static final String TYPE_VIEWONLY = "view"; + static Wallet.Device getDeviceType(String type) { + switch (type) { + case TYPE_SIDEKICK: + return Wallet.Device.Sidekick; + case TYPE_LEDGER: + return Wallet.Device.Ledger; + default: + return Wallet.Device.Software; + } + } + private TextInputLayout etWalletName; private PasswordEntryView etWalletPassword; private LinearLayout llFingerprintAuth; @@ -195,6 +206,7 @@ public class GenerateFragment extends Fragment { etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_UNSPECIFIED); break; case TYPE_LEDGER: + case TYPE_SIDEKICK: etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE); etWalletPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) @@ -308,7 +320,7 @@ public class GenerateFragment extends Fragment { private boolean checkName() { String name = etWalletName.getEditText().getText().toString(); boolean ok = true; - if (name.length() == 0) { + if (name.isEmpty()) { etWalletName.setError(getString(R.string.generate_wallet_name)); ok = false; } else if (name.charAt(0) == '.') { @@ -450,11 +462,12 @@ public class GenerateFragment extends Fragment { activityCallback.onGenerate(name, crazyPass, seed, offset, height); break; case TYPE_LEDGER: + case TYPE_SIDEKICK: bGenerate.setEnabled(false); if (fingerprintAuthAllowed) { KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password); } - activityCallback.onGenerateLedger(name, crazyPass, height); + activityCallback.onGenerateDevice(getDeviceType(type), name, crazyPass, height); break; case TYPE_KEY: case TYPE_VIEWONLY: @@ -498,6 +511,8 @@ public class GenerateFragment extends Fragment { return getString(R.string.generate_wallet_type_seed); case TYPE_LEDGER: return getString(R.string.generate_wallet_type_ledger); + case TYPE_SIDEKICK: + return getString(R.string.generate_wallet_type_sidekick); case TYPE_VIEWONLY: return getString(R.string.generate_wallet_type_view); default: @@ -515,7 +530,7 @@ public class GenerateFragment extends Fragment { void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height); - void onGenerateLedger(String name, String password, long height); + void onGenerateDevice(Wallet.Device device, String name, String password, long height); void setTitle(String title); @@ -555,6 +570,9 @@ public class GenerateFragment extends Fragment { case TYPE_LEDGER: inflater.inflate(R.menu.create_wallet_ledger, menu); break; + case TYPE_SIDEKICK: + inflater.inflate(R.menu.create_wallet_sidekick, menu); + break; case TYPE_VIEWONLY: inflater.inflate(R.menu.create_wallet_view, menu); break; @@ -581,13 +599,11 @@ public class GenerateFragment extends Fragment { .setCancelable(false) .setPositiveButton(getString(R.string.label_ok), null) .setNegativeButton(getString(R.string.label_cancel), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int id) { - Helper.hideKeyboardAlways(activity); - etWalletMnemonic.getEditText().getText().clear(); - dialog.cancel(); - ledgerDialog = null; - } + (dialog, id) -> { + Helper.hideKeyboardAlways(activity); + etWalletMnemonic.getEditText().getText().clear(); + dialog.cancel(); + ledgerDialog = null; }); ledgerDialog = alertDialogBuilder.create(); diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java index 1fd57d9d..36adc779 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java @@ -254,7 +254,7 @@ public class GenerateReviewFragment extends Fragment { showProgress(); if ((walletPath != null) && (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword()) - == Wallet.Device.Device_Ledger) + == Wallet.Device.Ledger) && (progressCallback != null)) { progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE); dialogOpened = true; @@ -286,10 +286,11 @@ public class GenerateReviewFragment extends Fragment { height = wallet.getRestoreHeight(); seed = wallet.getSeed(getSeedOffset()); switch (wallet.getDeviceType()) { - case Device_Ledger: + case Ledger: viewKey = Ledger.Key(); break; - case Device_Software: + case Software: + case Sidekick: viewKey = wallet.getSecretViewKey(); break; default: diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index b6a9f5fa..a167f3e6 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -55,6 +55,7 @@ import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.service.BluetoothService; import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.KeyStoreHelper; @@ -76,13 +77,12 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import lombok.Getter; import timber.log.Timber; public class LoginActivity extends BaseActivity implements LoginFragment.Listener, GenerateFragment.Listener, GenerateReviewFragment.Listener, GenerateReviewFragment.AcceptListener, - NodeFragment.Listener, SettingsFragment.Listener { + NodeFragment.Listener, SettingsFragment.Listener, SidekickConnectFragment.Listener, BluetoothFragment.Listener { private static final String GENERATE_STACK = "gen"; private static final String NODES_PREFS_NAME = "nodes"; @@ -278,8 +278,15 @@ public class LoginActivity extends BaseActivity } @Override - public boolean hasLedger() { - return Ledger.isConnected(); + public boolean hasDevice(Wallet.Device type) { + switch (type) { + case Ledger: + return Ledger.isConnected(); + case Sidekick: + return BluetoothService.IsConnected(); + default: + return true; + } } @Override @@ -311,8 +318,8 @@ public class LoginActivity extends BaseActivity } }); - loadFavouritesWithNetwork(); - + if (isNetworkAvailable()) + loadFavouritesWithNetwork(); LegacyStorageHelper.migrateWallets(this); if (savedInstanceState == null) startLoginFragment(); @@ -334,7 +341,7 @@ public class LoginActivity extends BaseActivity @Override public boolean onWalletSelected(String walletName, boolean streetmode) { - if (node == null) { + if (isNetworkAvailable() && (node == null)) { Toast.makeText(this, getString(R.string.prompt_daemon_missing), Toast.LENGTH_SHORT).show(); return false; } @@ -682,6 +689,7 @@ public class LoginActivity extends BaseActivity dismissProgressDialog(); unregisterDetachReceiver(); Ledger.disconnect(); + BluetoothService.Stop(); super.onDestroy(); } @@ -695,6 +703,7 @@ public class LoginActivity extends BaseActivity new AsyncWaitForService().execute(); } if (!Ledger.isConnected()) attachLedger(); + if (BluetoothService.IsConnected()) onLedgerAction(); //TODO sidekick & show sidekick fab registerTor(); } @@ -727,14 +736,14 @@ public class LoginActivity extends BaseActivity } } - void startWallet(String walletName, String walletPassword, - boolean fingerprintUsed, boolean streetmode) { + void startWallet(String walletName, String walletPassword, boolean fingerprintUsed, StartMode mode) { Timber.d("startWallet()"); + Intent intent = new Intent(getApplicationContext(), WalletActivity.class); intent.putExtra(WalletActivity.REQUEST_ID, walletName); intent.putExtra(WalletActivity.REQUEST_PW, walletPassword); intent.putExtra(WalletActivity.REQUEST_FINGERPRINT_USED, fingerprintUsed); - intent.putExtra(WalletActivity.REQUEST_STREETMODE, streetmode); + intent.putExtra(WalletActivity.REQUEST_STREETMODE, mode == StartMode.Street); if (uri != null) { intent.putExtra(WalletActivity.REQUEST_URI, uri); uri = null; // use only once @@ -783,6 +792,11 @@ public class LoginActivity extends BaseActivity Timber.d("SettingsFragment placed"); } + void startSidekickConnectFragment() { + replaceFragment(new SidekickConnectFragment(), null, null); + Timber.d("SidekickConnectFragment placed"); + } + void replaceFragment(Fragment newFragment, String stackName, Bundle extras) { if (extras != null) { newFragment.setArguments(extras); @@ -821,7 +835,7 @@ public class LoginActivity extends BaseActivity protected void onPreExecute() { super.onPreExecute(); acquireWakeLock(); - if (walletCreator.isLedger()) { + if (walletCreator.device() == Wallet.Device.Ledger) { showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE); } else { showProgressDialog(R.string.generate_wallet_creating); @@ -890,8 +904,7 @@ public class LoginActivity extends BaseActivity interface WalletCreator { boolean createWallet(File aFile, String password); - boolean isLedger(); - + Wallet.Device device(); } boolean checkAndCloseWallet(Wallet aWallet) { @@ -909,8 +922,8 @@ public class LoginActivity extends BaseActivity createWallet(name, password, new WalletCreator() { @Override - public boolean isLedger() { - return false; + public Wallet.Device device() { + return Wallet.Device.Software; } @Override @@ -933,8 +946,8 @@ public class LoginActivity extends BaseActivity createWallet(name, password, new WalletCreator() { @Override - public boolean isLedger() { - return false; + public Wallet.Device device() { + return Wallet.Device.Software; } @Override @@ -947,20 +960,19 @@ public class LoginActivity extends BaseActivity } @Override - public void onGenerateLedger(final String name, final String password, - final long restoreHeight) { + public void onGenerateDevice(final Wallet.Device device, final String name, final String password, long restoreHeight) { createWallet(name, password, new WalletCreator() { @Override - public boolean isLedger() { - return true; + public Wallet.Device device() { + return device; } @Override public boolean createWallet(File aFile, String password) { Wallet newWallet = WalletManager.getInstance() .createWalletFromDevice(aFile, password, - restoreHeight, "Ledger"); + restoreHeight, device); return checkAndCloseWallet(newWallet); } }); @@ -973,8 +985,8 @@ public class LoginActivity extends BaseActivity createWallet(name, password, new WalletCreator() { @Override - public boolean isLedger() { - return false; + public Wallet.Device device() { + return Wallet.Device.Software; } @Override @@ -1105,6 +1117,9 @@ public class LoginActivity extends BaseActivity } else if (id == R.id.action_create_help_ledger) { HelpFragment.display(getSupportFragmentManager(), R.string.help_create_ledger); return true; + } else if (id == R.id.action_create_help_sidekick) { + HelpFragment.display(getSupportFragmentManager(), R.string.help_create_sidekick); + return true; } else if (id == R.id.action_details_help) { HelpFragment.display(getSupportFragmentManager(), R.string.help_details); return true; @@ -1117,6 +1132,9 @@ public class LoginActivity extends BaseActivity } else if (id == R.id.action_help_node) { HelpFragment.display(getSupportFragmentManager(), R.string.help_node); return true; + } else if (id == R.id.action_help_sidekick) { + HelpFragment.display(getSupportFragmentManager(), R.string.help_sidekick); + return true; } else if (id == R.id.action_default_nodes) { Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container); if ((WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) && @@ -1124,6 +1142,9 @@ public class LoginActivity extends BaseActivity ((NodeFragment) f).restoreDefaultNodes(); } return true; + } else if (id == R.id.action_sidekick) { + checkBtPermissions(); + return true; } else if (id == R.id.action_ledger_seed) { Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container); if (f instanceof GenerateFragment) { @@ -1135,6 +1156,11 @@ public class LoginActivity extends BaseActivity } } + @Override + void btPermissionGranted() { + startSidekickConnectFragment(); + } + // an AsyncTask which tests the node before trying to open the wallet private class AsyncOpenWallet extends AsyncTask { final static int OK = 0; @@ -1172,7 +1198,7 @@ public class LoginActivity extends BaseActivity if (result) { Timber.d("selected wallet is .%s.", node.getName()); // now it's getting real, onValidateFields if wallet exists - promptAndStart(walletName, streetmode); + promptAndStart(walletName, streetmode ? StartMode.Street : StartMode.Normal); } else { if (node.getResponseCode() == 0) { // IOException Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_node_invalid), Toast.LENGTH_LONG).show(); @@ -1184,25 +1210,25 @@ public class LoginActivity extends BaseActivity } - boolean checkDevice(String walletName, String password) { - String keyPath = new File(Helper.getWalletRoot(LoginActivity.this), - walletName + ".keys").getAbsolutePath(); + private boolean checkDevice(String walletName, String password) { + String keyPath = new File(Helper.getWalletRoot(this), walletName + ".keys").getAbsolutePath(); // check if we need connected hardware - Wallet.Device device = WalletManager.getInstance().queryWalletDevice(keyPath, password); - if (device == Wallet.Device.Device_Ledger) { - if (!hasLedger()) { + final Wallet.Device device = WalletManager.getInstance().queryWalletDevice(keyPath, password); + if (!hasDevice(device)) { + if (device == Wallet.Device.Ledger) { toast(R.string.open_wallet_ledger_missing); - } else { - return true; + } else if (device == Wallet.Device.Sidekick) { + toast(R.string.open_wallet_sidekick_missing); } - } else {// device could be undefined meaning the password is wrong - // this gets dealt with later - return true; + return false; } - return false; + // else // device could be undefined meaning the password is wrong + return true; } - void promptAndStart(String walletName, final boolean streetmode) { + enum StartMode {Normal, Street} + + void promptAndStart(String walletName, final StartMode mode) { File walletFile = Helper.getWalletFile(this, walletName); if (WalletManager.getInstance().walletExists(walletFile)) { Helper.promptPassword(LoginActivity.this, walletName, false, @@ -1210,7 +1236,7 @@ public class LoginActivity extends BaseActivity @Override public void act(String walletName, String password, boolean fingerprintUsed) { if (checkDevice(walletName, password)) - startWallet(walletName, password, fingerprintUsed, streetmode); + startWallet(walletName, password, fingerprintUsed, mode); } @Override @@ -1378,6 +1404,28 @@ public class LoginActivity extends BaseActivity return usbManager; } + @Override + public void onDeviceConnected(String connectedDeviceName) { + Timber.d("onDeviceConnected: %s", connectedDeviceName); + try { + SidekickConnectFragment f = (SidekickConnectFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_container); + f.allowClick(); + } catch (ClassCastException ex) { + // ignore it + } + } + + @Override + public void abort(String message) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + onBackPressed(); + } + + @Override + public void onReceive(int command) { + Timber.e("this should not be"); + } + // // Tor (Orbot) stuff // diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java index 8e920adb..abc0e685 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java @@ -46,6 +46,7 @@ import com.google.android.material.progressindicator.CircularProgressIndicator; import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.layout.WalletInfoAdapter; +import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.KeyStoreHelper; @@ -61,6 +62,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import lombok.Getter; import timber.log.Timber; public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInteractionListener, @@ -115,7 +117,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter Set getOrPopulateFavourites(); - boolean hasLedger(); + boolean hasDevice(Wallet.Device type); + + boolean isNetworkAvailable(); void runOnNetCipher(Runnable runnable); } @@ -146,7 +150,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter activityCallback.setToolbarButton(Toolbar.BUTTON_SETTINGS); activityCallback.showNet(); showNetwork(); - //activityCallback.runOnNetCipher(this::pingSelectedNode); } private OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @@ -172,6 +175,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter fabSeed = view.findViewById(R.id.fabSeed); fabImport = view.findViewById(R.id.fabImport); fabLedger = view.findViewById(R.id.fabLedger); + fabSidekick = view.findViewById(R.id.fabSidekick); fabNewL = view.findViewById(R.id.fabNewL); fabViewL = view.findViewById(R.id.fabViewL); @@ -179,6 +183,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter fabSeedL = view.findViewById(R.id.fabSeedL); fabImportL = view.findViewById(R.id.fabImportL); fabLedgerL = view.findViewById(R.id.fabLedgerL); + fabSidekickL = view.findViewById(R.id.fabSidekickL); fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse); fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen); @@ -194,6 +199,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter fabSeed.setOnClickListener(this); fabImport.setOnClickListener(this); fabLedger.setOnClickListener(this); + fabSidekick.setOnClickListener(this); fabScreen.setOnClickListener(this); RecyclerView recyclerView = view.findViewById(R.id.list); @@ -202,6 +208,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter recyclerView.setAdapter(adapter); ViewGroup llNotice = view.findViewById(R.id.llNotice); + Notice.showAll(llNotice, ".*_login"); view.findViewById(R.id.llNode).setOnClickListener(v -> startNodePrefs()); @@ -304,17 +311,14 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter super.onCreateOptionsMenu(menu, inflater); } + @Getter private boolean fabOpen = false; - private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabImport, fabLedger; + private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabImport, fabLedger, fabSidekick; private RelativeLayout fabScreen; - private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabImportL, fabLedgerL; + private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabImportL, fabLedgerL, fabSidekickL; private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen; private Animation fab_pulse; - public boolean isFabOpen() { - return fabOpen; - } - private void setFabOpen(boolean value) { fabOpen = value; onBackPressedCallback.setEnabled(value); @@ -328,6 +332,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter if (fabLedgerL.getVisibility() == View.VISIBLE) { fabLedgerL.startAnimation(fab_close); fabLedger.setClickable(false); + } else if (fabSidekickL.getVisibility() == View.VISIBLE) { + fabSidekickL.startAnimation(fab_close); + fabSidekick.setClickable(false); } else { fabNewL.startAnimation(fab_close); fabNew.setClickable(false); @@ -345,17 +352,25 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter fabScreen.setClickable(true); fabScreen.startAnimation(fab_open_screen); fab.startAnimation(rotate_forward); - if (activityCallback.hasLedger()) { - fabLedgerL.setVisibility(View.VISIBLE); + if ((activityCallback.hasDevice(Wallet.Device.Ledger) + || activityCallback.hasDevice(Wallet.Device.Sidekick))) { fabNewL.setVisibility(View.GONE); fabViewL.setVisibility(View.GONE); fabKeyL.setVisibility(View.GONE); fabSeedL.setVisibility(View.GONE); fabImportL.setVisibility(View.GONE); - fabLedgerL.startAnimation(fab_open); - fabLedger.setClickable(true); + if (activityCallback.hasDevice(Wallet.Device.Ledger)) { + fabLedgerL.setVisibility(View.VISIBLE); + fabLedgerL.startAnimation(fab_open); + fabLedger.setClickable(true); + } else { // Sidekick + fabSidekickL.setVisibility(View.VISIBLE); + fabSidekickL.startAnimation(fab_open); + fabSidekick.setClickable(true); + } } else { + fabSidekickL.setVisibility(View.GONE); fabLedgerL.setVisibility(View.GONE); fabNewL.setVisibility(View.VISIBLE); fabViewL.setVisibility(View.VISIBLE); @@ -404,6 +419,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter Timber.d("FAB_LEDGER"); animateFAB(); activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER); + } else if (id == R.id.fabSidekick) { + Timber.d("FAB_SIDEKICK"); + animateFAB(); + activityCallback.onAddWallet(GenerateFragment.TYPE_SIDEKICK); } else if (id == R.id.fabScreen) { animateFAB(); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/SidekickConnectFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SidekickConnectFragment.java new file mode 100644 index 00000000..1cbbd0fc --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/SidekickConnectFragment.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2018 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.RecyclerView; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.m2049r.xmrwallet.data.BluetoothInfo; +import com.m2049r.xmrwallet.layout.BluetoothInfoAdapter; +import com.m2049r.xmrwallet.util.Helper; +import com.m2049r.xmrwallet.widget.Toolbar; + +import java.util.ArrayList; +import java.util.List; + +import timber.log.Timber; + +public class SidekickConnectFragment extends Fragment + implements BluetoothInfoAdapter.OnInteractionListener { + + private BluetoothAdapter bluetoothAdapter; + + private SwipeRefreshLayout pullToRefresh; + + private BluetoothInfoAdapter infoAdapter; + + private Listener activityCallback; + + public interface Listener { + void setToolbarButton(int type); + + void setSubtitle(String title); + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (context instanceof Listener) { + this.activityCallback = (Listener) context; + } else { + throw new ClassCastException(context + " must implement Listener"); + } + } + + @Override + public void onPause() { + Timber.d("onPause()"); + if (bluetoothAdapter.isDiscovering()) { + bluetoothAdapter.cancelDiscovery(); + } + // the the activity we are connected? why? it can ask the bluetoothservice... + super.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + Timber.d("onResume()"); + activityCallback.setSubtitle(getString(R.string.label_bluetooth)); + activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); + final BluetoothFragment btFragment = (BluetoothFragment) getChildFragmentManager().findFragmentById(R.id.bt_fragment); + assert btFragment != null; + btFragment.start(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + Timber.d("onCreateView"); + View view = inflater.inflate(R.layout.fragment_sidekick_connect, container, false); + + RecyclerView recyclerView = view.findViewById(R.id.list); + infoAdapter = new BluetoothInfoAdapter(this); + recyclerView.setAdapter(infoAdapter); + + pullToRefresh = view.findViewById(R.id.pullToRefresh); + pullToRefresh.setOnRefreshListener(() -> { + populateList(); + pullToRefresh.setRefreshing(false); + }); + + return view; + } + + private void populateList() { + List items = new ArrayList<>(); + for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) { + final int deviceCLass = device.getBluetoothClass().getDeviceClass(); + switch (deviceCLass) { + case BluetoothClass.Device.PHONE_SMART: + //TODO verify these are correct + case BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA: + case BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA: + items.add(new BluetoothInfo(device)); + } + } + infoAdapter.setItems(items); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + Helper.hideKeyboard(getActivity()); + + // Get the local Bluetooth adapter + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + populateList(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.sidekick_connect_menu, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + // Make sure we're not doing discovery anymore + if (bluetoothAdapter != null) { + bluetoothAdapter.cancelDiscovery(); + } + } + + @Override + public void onInteraction(final View view, final BluetoothInfo item) { + Timber.d("onInteraction %s", item); + bluetoothAdapter.cancelDiscovery(); + + final BluetoothFragment btFragment = (BluetoothFragment) getChildFragmentManager().findFragmentById(R.id.bt_fragment); + assert btFragment != null; + btFragment.connectDevice(item.getAddress()); + } + + public void allowClick() { + infoAdapter.allowClick(true); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java index 3a6ce484..b921280a 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java @@ -195,7 +195,7 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte @Override protected void onPreExecute() { super.onPreExecute(); - if ((wallet.getDeviceType() == Wallet.Device.Device_Ledger) && (progressCallback != null)) { + if ((wallet.getDeviceType() == Wallet.Device.Ledger) && (progressCallback != null)) { progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS); dialogOpened = true; } diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java index afa05f6e..365090bf 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 m2049r + * Copyright (c) 2017-2024 m2049r * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,7 @@ import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import androidx.preference.PreferenceManager; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -92,6 +93,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste public static final String REQUEST_FINGERPRINT_USED = "fingerprint"; public static final String REQUEST_STREETMODE = "streetmode"; public static final String REQUEST_URI = "uri"; + public static final String REQUEST_SIDEKICK = "sidekick"; private NavigationView accountsView; private DrawerLayout drawer; @@ -202,19 +204,9 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste return getWallet().getSubaddress(major, minor); } - private void startWalletService() { - Bundle extras = getIntent().getExtras(); - if (extras != null) { - acquireWakeLock(); - String walletId = extras.getString(REQUEST_ID); - // we can set the streetmode height AFTER opening the wallet - requestStreetMode = extras.getBoolean(REQUEST_STREETMODE); - password = extras.getString(REQUEST_PW); - uri = extras.getString(REQUEST_URI); - connectWalletService(walletId, password); - } else { - finish(); - } + private void startWalletService(String walletId) { + acquireWakeLock(); + connectWalletService(walletId, password); } private void stopWalletService() { @@ -330,7 +322,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste }); } - public void onWalletChangePassword() { try { GenerateReviewFragment detailsFragment = (GenerateReviewFragment) getCurrentFragment(); @@ -356,33 +347,39 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste return; } + Bundle extras = getIntent().getExtras(); + if (extras == null) finish(); // we need extras! + + String walletId = extras.getString(REQUEST_ID); + requestStreetMode = extras.getBoolean(REQUEST_STREETMODE); + password = extras.getString(REQUEST_PW); + uri = extras.getString(REQUEST_URI); + boolean sidekick = extras.getBoolean(REQUEST_SIDEKICK); + setContentView(R.layout.activity_wallet); toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayShowTitleEnabled(false); - toolbar.setOnButtonListener(new Toolbar.OnButtonListener() { - @Override - public void onButton(int type) { - switch (type) { - case Toolbar.BUTTON_BACK: - onDisposeRequest(); - getOnBackPressedDispatcher().onBackPressed(); - break; - case Toolbar.BUTTON_CANCEL: - onDisposeRequest(); - Helper.hideKeyboard(WalletActivity.this); - getOnBackPressedDispatcher().onBackPressed(); - break; - case Toolbar.BUTTON_CLOSE: - finish(); - break; - case Toolbar.BUTTON_SETTINGS: - Toast.makeText(WalletActivity.this, getString(R.string.label_credits), Toast.LENGTH_SHORT).show(); - case Toolbar.BUTTON_NONE: - default: - Timber.e("Button " + type + "pressed - how can this be?"); - } + toolbar.setOnButtonListener(type -> { + switch (type) { + case Toolbar.BUTTON_BACK: + onDisposeRequest(); + getOnBackPressedDispatcher().onBackPressed(); + break; + case Toolbar.BUTTON_CANCEL: + onDisposeRequest(); + Helper.hideKeyboard(WalletActivity.this); + getOnBackPressedDispatcher().onBackPressed(); + break; + case Toolbar.BUTTON_CLOSE: + finish(); + break; + case Toolbar.BUTTON_SETTINGS: + Toast.makeText(WalletActivity.this, getString(R.string.label_credits), Toast.LENGTH_SHORT).show(); + case Toolbar.BUTTON_NONE: + default: + Timber.e("Button " + type + "pressed - how can this be?"); } }); @@ -398,11 +395,16 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste showNet(); Fragment walletFragment = new WalletFragment(); - getSupportFragmentManager().beginTransaction() - .add(R.id.fragment_container, walletFragment, WalletFragment.class.getName()).commit(); - Timber.d("fragment added"); + final FragmentTransaction tx = + getSupportFragmentManager().beginTransaction() + .add(R.id.fragment_container, walletFragment, WalletFragment.class.getName()); + if (sidekick) { + tx.add(R.id.fragment_bluetooth, new BluetoothFragment(BluetoothFragment.Mode.CLIENT), BluetoothFragment.class.getName()); + } + tx.commit(); + Timber.d("fragments added"); - startWalletService(); + if (!sidekick) startWalletService(walletId); Timber.d("onCreate() done."); } @@ -616,7 +618,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste @Override public void onWalletOpen(final Wallet.Device device) { - if (device == Wallet.Device.Device_Ledger) { + if (device == Wallet.Device.Ledger) { runOnUiThread(() -> showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE)); } } @@ -766,7 +768,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste intent.putExtra(WalletService.REQUEST_CMD_TX_TAG, tag); startService(intent); Timber.d("CREATE TX request sent"); - if (getWallet().getDeviceType() == Wallet.Device.Device_Ledger) + if (getWallet().getDeviceType() == Wallet.Device.Ledger) showLedgerProgressDialog(LedgerProgressDialog.TYPE_SEND); } else { Timber.e("Service not bound"); @@ -1132,11 +1134,12 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste protected void onPreExecute() { super.onPreExecute(); switch (getWallet().getDeviceType()) { - case Device_Ledger: + case Ledger: showLedgerProgressDialog(LedgerProgressDialog.TYPE_ACCOUNT); dialogOpened = true; break; - case Device_Software: + case Software: + case Sidekick: showProgressDialog(R.string.accounts_progress_new); dialogOpened = true; break; diff --git a/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java b/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java index 3943d23b..287004df 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java +++ b/app/src/main/java/com/m2049r/xmrwallet/XmrWalletApplication.java @@ -22,27 +22,20 @@ import android.content.res.Configuration; import android.os.Build; import androidx.annotation.NonNull; -import androidx.annotation.OptIn; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentStateManagerControl; +import com.m2049r.xmrwallet.BuildConfig; import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.NetCipherHelper; import com.m2049r.xmrwallet.util.NightmodeHelper; -import com.m2049r.xmrwallet.util.ServiceHelper; - -import java.util.Arrays; import timber.log.Timber; public class XmrWalletApplication extends Application { @Override - @OptIn(markerClass = FragmentStateManagerControl.class) public void onCreate() { super.onCreate(); - FragmentManager.enableNewStateManager(false); if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/BluetoothInfo.java b/app/src/main/java/com/m2049r/xmrwallet/data/BluetoothInfo.java new file mode 100644 index 00000000..17f51521 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/data/BluetoothInfo.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.data; + +import android.bluetooth.BluetoothDevice; + +import java.util.Comparator; + +import lombok.Data; +import lombok.Getter; + +@Data +public class BluetoothInfo { + @Getter + final private String name; + @Getter + final private String address; + @Getter + private boolean bonded; + + public BluetoothInfo(BluetoothDevice device) { + name = device.getName().trim(); + address = device.getAddress(); + bonded = device.getBondState() == BluetoothDevice.BOND_BONDED; + } + + static public Comparator NameComparator = (o1, o2) -> o1.name.compareTo(o2.name); +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/BluetoothInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/BluetoothInfoAdapter.java new file mode 100644 index 00000000..687f5eb7 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/BluetoothInfoAdapter.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2018 m2049r + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.m2049r.xmrwallet.layout; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.data.BluetoothInfo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class BluetoothInfoAdapter extends RecyclerView.Adapter { + + public interface OnInteractionListener { + void onInteraction(View view, BluetoothInfo item); + } + + private final List items = new ArrayList<>(); + private final OnInteractionListener listener; + + public BluetoothInfoAdapter(OnInteractionListener listener) { + this.listener = listener; + } + + private static class BluetoothInfoDiff extends DiffCallback { + + public BluetoothInfoDiff(List oldList, List newList) { + super(oldList, newList); + } + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + return mOldList.get(oldItemPosition).getAddress().equals(mNewList.get(newItemPosition).getAddress()); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + final BluetoothInfo oldItem = mOldList.get(oldItemPosition); + final BluetoothInfo newItem = mNewList.get(newItemPosition); + return oldItem.equals(newItem); + } + } + + @Override + public @NonNull + ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_bluetooth, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(final @NonNull ViewHolder holder, int position) { + holder.bind(position); + } + + @Override + public int getItemCount() { + return items.size(); + } + + public void add(BluetoothInfo item) { + if (item == null) return; + List newItems = new ArrayList<>(items); + if (!items.contains(item)) + newItems.add(item); + setItems(newItems); // in case the nodeinfo has changed + } + + public void setItems(Collection newItemsCollection) { + List newItems; + if (newItemsCollection != null) { + newItems = new ArrayList<>(newItemsCollection); + Collections.sort(newItems, BluetoothInfo.NameComparator); + } else { + newItems = new ArrayList<>(); + } + final BluetoothInfoAdapter.BluetoothInfoDiff diffCallback = new BluetoothInfoAdapter.BluetoothInfoDiff(items, newItems); + final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); + items.clear(); + items.addAll(newItems); + diffResult.dispatchUpdatesTo(this); + } + + private boolean itemsClickable = true; + + public void allowClick(boolean clickable) { + itemsClickable = clickable; + notifyDataSetChanged(); + } + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + final TextView tvName; + final TextView tvAddress; + BluetoothInfo item; + + ViewHolder(View itemView) { + super(itemView); + tvName = itemView.findViewById(R.id.tvName); + tvAddress = itemView.findViewById(R.id.tvAddress); + itemView.setOnClickListener(this); + } + + void bind(int position) { + item = items.get(position); + tvName.setText(item.getName()); + tvAddress.setText(item.getAddress()); + itemView.setClickable(itemsClickable); + itemView.setEnabled(itemsClickable); + } + + @Override + public void onClick(View view) { + if (listener != null) { + int position = getBindingAdapterPosition(); // gets item position + if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it + final BluetoothInfo node = items.get(position); + allowClick(false); + listener.onInteraction(view, node); + } + } + } + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java index 599ad91a..91b5894a 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java @@ -86,7 +86,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter 0) && !infoItems.get(0).isConfirmed(); + return (!infoItems.isEmpty()) && !infoItems.get(0).isConfirmed(); } private static class TransactionInfoDiff extends DiffCallback { diff --git a/app/src/main/java/com/m2049r/xmrwallet/ledger/Hardware.java b/app/src/main/java/com/m2049r/xmrwallet/ledger/Hardware.java new file mode 100644 index 00000000..82a19930 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/ledger/Hardware.java @@ -0,0 +1,4 @@ +package com.m2049r.xmrwallet.ledger; + +public interface Hardware { +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java b/app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java index 03582827..7cde2f4b 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java +++ b/app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java @@ -35,12 +35,11 @@ import java.nio.charset.StandardCharsets; import timber.log.Timber; -public class Ledger { +public class Ledger implements Hardware { static final public boolean ENABLED = true; // 5:20 is same as wallet2.cpp::restore() static public final int LOOKAHEAD_ACCOUNTS = 5; static public final int LOOKAHEAD_SUBADDRESSES = 20; - static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES; private static final byte PROTOCOL_VERSION = 0x03; public static final int SW_OK = 0x9000; diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java b/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java index d97456b9..77d72cfa 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java @@ -111,7 +111,11 @@ public class Wallet { @RequiredArgsConstructor @Getter public enum Device { - Device_Undefined(0, 0), Device_Software(50, 200), Device_Ledger(5, 20); + Undefined(0, 0), + Software(50, 200), + Ledger(5, 20), + Trezor(5, 20), + Sidekick(5, 20); private final int accountLookahead; private final int subaddressLookahead; } 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 71f3e124..c6b2a126 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java @@ -18,7 +18,6 @@ package com.m2049r.xmrwallet.model; import com.m2049r.xmrwallet.XmrWalletApplication; import com.m2049r.xmrwallet.data.Node; -import com.m2049r.xmrwallet.ledger.Ledger; import com.m2049r.xmrwallet.util.RestoreHeight; import java.io.File; @@ -161,10 +160,12 @@ public class WalletManager { String spendKeyString); public Wallet createWalletFromDevice(File aFile, String password, long restoreHeight, - String deviceName) { + Wallet.Device device) { + final String lookahead = device.getAccountLookahead() + ":" + device.getSubaddressLookahead(); + Timber.d("Creating from %s with %s lookahead", device, lookahead); long walletHandle = createWalletFromDeviceJ(aFile.getAbsolutePath(), password, - getNetworkType().getValue(), deviceName, restoreHeight, - Ledger.SUBADDRESS_LOOKAHEAD); + getNetworkType().getValue(), device.name(), restoreHeight, + lookahead); Wallet wallet = new Wallet(walletHandle); manageWallet(wallet); return wallet; diff --git a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java index b0c52d0d..93e9fd64 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java +++ b/app/src/main/java/com/m2049r/xmrwallet/onboarding/OnBoardingManager.java @@ -19,6 +19,8 @@ package com.m2049r.xmrwallet.onboarding; import android.content.Context; import android.content.SharedPreferences; +import com.m2049r.xmrwallet.util.Helper; + import java.util.Date; import timber.log.Timber; diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/BluetoothService.java b/app/src/main/java/com/m2049r/xmrwallet/service/BluetoothService.java new file mode 100644 index 00000000..aaa1ec06 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/service/BluetoothService.java @@ -0,0 +1,662 @@ +/* + * Copyright (C) 2021 m2049r@monerujo.io + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// mostly from BluetoothChatService https://github.com/android/connectivity-samples + +package com.m2049r.xmrwallet.service; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothServerSocket; +import android.bluetooth.BluetoothSocket; +import android.os.Handler; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; + +import lombok.Getter; +import lombok.Setter; +import timber.log.Timber; + +/** + * This class does all the work for setting up and managing Bluetooth + * connections with other devices. It has a thread that listens for + * incoming connections, a thread for connecting with a device, and a + * thread for performing data transmissions when connected. + */ +public class BluetoothService { + final static private byte[] MAGIC = "SIDEKICK".getBytes(StandardCharsets.US_ASCII); + + public interface MessageType { + int STATE_CHANGE = 1; + int READ = 2; + int WRITE = 3; + int DEVICE_NAME = 4; + int TOAST = 5; + int READ_CMD = 6; + int CODE = 42; + } + + //TODO refactor this using an enum with resource ids for messages and stuff + public interface Toasts { + String CONNECT_FAILED = "Unable to connect device"; + String CONNECTION_LOST = "Device connection was lost"; + int READ = 2; + int WRITE = 3; + int DEVICE_NAME = 4; + int TOAST = 5; + } + + // Constants that indicate the current connection state + public interface State { + int NONE = 0; // we're doing nothing + int LISTEN = 1; // now listening for incoming connections + int CONNECTING = 2; // now initiating an outgoing connection + int CONNECTED = 3; // now connected to a remote device + } + + // Name for the SDP record when creating server socket + private static final String SDP_NAME = "Monerujo"; + + // Unique UUID for this application + private static final UUID SDP_UUID = UUID.fromString("2150154b-58ce-4c58-99e3-ccfdd14bed3b"); + + static final BluetoothService Instance = new BluetoothService(); + + public static BluetoothService GetInstance() { + return Instance; + } + + public static boolean IsConnected() { + return Instance.isConnected(); + } + + public static void Stop() { + Instance.stop(); + } + + // Member fields + private final BluetoothAdapter bluetoothAdapter; + @Setter + private Handler uiHandler; + @Setter + private Handler commHandler; + private AcceptThread acceptThread; + private ConnectThread connectThread; + private ConnectedThread connectedThread; + @Getter + private int state = State.NONE; + + @Getter + private String connectedName; + @Getter + private String connectedCode; + + public boolean isStarted() { + return state != State.NONE; + } + + public boolean isConnected() { + return state == State.CONNECTED; + } + + public BluetoothService() { + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + + /** + * Notify UI of state changes + */ + private synchronized void onStateChanged() { + Timber.d("onStateChanged() -> %s", state); + if (uiHandler != null) + uiHandler.obtainMessage(MessageType.STATE_CHANGE, state, -1).sendToTarget(); + } + + /** + * Notify UI that we've connected + */ + private synchronized void onConnected(String remoteName) { + Timber.d("onConnected() -> %s", remoteName); + connectedName = remoteName; + if (uiHandler != null) + uiHandler.obtainMessage(MessageType.DEVICE_NAME, remoteName).sendToTarget(); + } + + /** + * Start the service: Start AcceptThread to begin a session in listening (server) mode. + */ + public synchronized void start() { + Timber.d("start"); + halt = false; + + // Cancel any thread attempting to make a connection + if (connectThread != null) { + connectThread.cancel(); + connectThread = null; + } + + // Cancel any thread currently running a connection + if (connectedThread != null) { + connectedThread.cancel(); + connectedThread = null; + } + + // Start the thread to listen on a BluetoothServerSocket (or let it run if already running) + if (acceptThread == null) { + acceptThread = new AcceptThread(); + acceptThread.start(); + } + + onStateChanged(); + } + + /** + * Start the ConnectThread to initiate a connection to a remote device. + * + * @param device The BluetoothDevice to connect + */ + public synchronized void connect(BluetoothDevice device) { + Timber.d("connect to: %s", device); + + // Cancel any thread attempting to make a connection + if (state == State.CONNECTING) { + if (connectThread != null) { + connectThread.cancel(); + connectThread = null; + } + } + + // Cancel any thread currently running a connection + if (connectedThread != null) { + reconnect = true; + connectedThread.cancel(); + connectedThread = null; + } + + // Start the thread to connect with the given device + connectThread = new ConnectThread(device); + connectThread.start(); + + onStateChanged(); + } + + boolean reconnect = false; + boolean halt = false; + + /** + * Start the ConnectedThread to begin managing a Bluetooth connection + * + * @param socket The BluetoothSocket on which the connection was made + * @param device The BluetoothDevice that has been connected + */ + public synchronized void startConnected(BluetoothSocket socket, BluetoothDevice device) { + Timber.d("startConnected"); + + // Cancel the thread that completed the connection + if (connectThread != null) { + connectThread.cancel(); + connectThread = null; + } + + // Cancel any thread currently running a connection + if (connectedThread != null) { + connectedThread.cancel(); + connectedThread = null; + } + + // Cancel the accept thread because we only want to connect to one device + if (acceptThread != null) { + acceptThread.cancel(); + acceptThread = null; + } + + // Start the thread to manage the connection and perform transmissions + connectedThread = new ConnectedThread(socket); + connectedThread.start(); + + onConnected(device.getName()); + onStateChanged(); + } + + /** + * Stop all threads + */ + public synchronized void stop() { + Timber.d("stop"); + halt = true; + + if (connectThread != null) { + connectThread.cancel(); + connectThread = null; + } + + if (connectedThread != null) { + connectedThread.cancel(); + connectedThread = null; + } + + if (acceptThread != null) { + acceptThread.cancel(); + acceptThread = null; + } + + state = State.NONE; + + onStateChanged(); + + setUiHandler(null); + } + + /** + * Write to the ConnectedThread in an unsynchronized manner + * + * @param out The bytes to write + * @see ConnectedThread#write(byte[]) + */ + public boolean write(byte[] out) { + // Synchronize a copy of the ConnectedThread + ConnectedThread connectedThread; + synchronized (this) { + if (state != State.CONNECTED) return false; + connectedThread = this.connectedThread; + } + // Perform the write itself unsynchronized + return connectedThread.write(out); + } + + public boolean write(int code) { + // Synchronize a copy of the ConnectedThread + ConnectedThread connectedThread; + synchronized (this) { + if (state != State.CONNECTED) return false; + connectedThread = this.connectedThread; + } + + final byte[] buffer = new byte[2]; + buffer[0] = (byte) (code >> 8); + buffer[1] = (byte) (code & 0xff); + + connectedCode = String.format(Locale.US, "%04d", code); + if (uiHandler != null) + uiHandler.obtainMessage(MessageType.CODE, connectedCode).sendToTarget(); + + return connectedThread.write(buffer); + } + + public byte[] exchange(byte[] buffer) { + // Synchronize a copy of the ConnectedThread + ConnectedThread connectedThread; + synchronized (this) { + if (state != State.CONNECTED) return null; //TODO maybe exception? + connectedThread = this.connectedThread; + } + CountDownLatch signal = new CountDownLatch(1); + connectedThread.setReadSignal(signal); + connectedThread.write(buffer); + try { + signal.await(); //TODO what happens when the reader is canceled? + return connectedThread.getReadBuffer(); + } catch (InterruptedException ex) { + Timber.d(ex); + return null; + } + } + + /** + * Indicate that the connection attempt failed and notify + */ + private void onConnectFailed() { + Timber.d("onConnectFailed()"); + if (uiHandler != null) + uiHandler.obtainMessage(MessageType.TOAST, Toasts.CONNECT_FAILED).sendToTarget(); + + state = State.NONE; + + // don't notify as start() notifies immediately afterwards + // onStateChanged(); + + // Start the service over to restart listening mode + if (!halt) start(); + } + + /** + * Indicate that the connection was lost + */ + private void onConnectionLost() { + Timber.d("onConnectionLost()"); + connectedName = null; + connectedCode = null; + if (reconnect) return; + + state = State.NONE; + + if (halt) return; + + // don't notify as start() notifies immediately afterwards + // onStateChanged(); + + // Start the service over to restart listening mode + start(); + } + + /** + * This thread runs while listening for incoming connections. It behaves + * like a server-side client. It runs until a connection is accepted + * (or until cancelled). + */ + private class AcceptThread extends Thread { + private final BluetoothServerSocket serverSocket; + + public AcceptThread() { + // Create a new listening server socket + try { + serverSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(SDP_NAME, SDP_UUID); + state = BluetoothService.State.LISTEN; + } catch (IOException ex) { + Timber.d(ex, "listen() failed"); + throw new IllegalStateException(); + } + } + + public void run() { + Timber.d("BEGIN AcceptThread %s", this); + + BluetoothSocket socket; + + // Listen to the server socket if we're not connected + while (state != BluetoothService.State.CONNECTED) { + try { + // This is a blocking call and will only return on a + // successful connection or an exception + socket = serverSocket.accept(); + } catch (IOException ex) { + Timber.d(ex, "accept() failed"); // this also happens on socket.close() + break; + } + + // If a connection was accepted + if (socket != null) { + synchronized (BluetoothService.this) { + switch (state) { + case BluetoothService.State.LISTEN: + case BluetoothService.State.CONNECTING: + // Situation normal. Start the ConnectedThread. + startConnected(socket, socket.getRemoteDevice()); + break; + case BluetoothService.State.NONE: + case BluetoothService.State.CONNECTED: + // Either not ready or already connected. Terminate new socket. + try { + socket.close(); + } catch (IOException ex) { + Timber.d(ex, "Could not close unwanted socket"); + } + break; + } + } + } + } + Timber.d("END AcceptThread %s", this); + } + + public void cancel() { + Timber.d("cancel() %s", this); + try { + serverSocket.close(); + } catch (IOException ex) { + Timber.d(ex, "close() of server failed"); + } + } + } + + /** + * This thread runs while attempting to make an outgoing connection + * with a device. It runs straight through; the connection either + * succeeds or fails. + */ + private class ConnectThread extends Thread { + private final BluetoothSocket socket; + private final BluetoothDevice device; + + public ConnectThread(BluetoothDevice device) { + this.device = device; + + // Create a BluetoothSocket + try { + socket = device.createRfcommSocketToServiceRecord(SDP_UUID); + state = BluetoothService.State.CONNECTING; + } catch (IOException ex) { + Timber.d(ex, "create() failed"); + throw new IllegalStateException(); //TODO really die here? + } + } + + public void run() { + Timber.d("BEGIN ConnectThread"); + + // Always cancel discovery because it will slow down a connection + bluetoothAdapter.cancelDiscovery(); //TODO show & remember discovery state? + + // Make a connection to the BluetoothSocket + try { + // This is a blocking call and will only return on a + // successful connection or an exception + socket.connect(); // sometimes this fails - why? + } catch (IOException ex) { + try { + socket.close(); + } catch (IOException exClose) { + Timber.d(exClose, "unable to close() socket during connection failure"); + } + onConnectFailed(); + return; + } finally { + // Reset the ConnectThread because we're done + synchronized (BluetoothService.this) { + connectThread = null; + } + } + + // Start the ConnectedThread + startConnected(socket, device); + } + + public void cancel() { + try { + socket.close(); + } catch (IOException ex) { + Timber.d(ex, "close() of connect socket failed"); + } + } + } + + /** + * This thread runs during a connection with a remote device. + * It handles all incoming and outgoing transmissions. + */ + private class ConnectedThread extends Thread { + private final BluetoothSocket socket; + private final InputStream in; + private final OutputStream out; + + private final ByteArrayOutputStream bytesIn = new ByteArrayOutputStream(); + private final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + + private CountDownLatch readSignal = null; // TODO this needs to be a Map with correlationIds + + public void setReadSignal(CountDownLatch signal) { //TODO see above + readSignal = signal; + readBuffer = null; + } + + @Getter + private byte[] readBuffer = null; + + public ConnectedThread(BluetoothSocket bluetoothSocket) { + Timber.d("ConnectedThread()"); + socket = bluetoothSocket; + InputStream tmpIn = null; + OutputStream tmpOut; + + // Get the BluetoothSocket input and output streams + try { + tmpIn = socket.getInputStream(); + tmpOut = socket.getOutputStream(); + } catch (IOException ex) { + Timber.d(ex, "temp sockets not created"); + if (tmpIn != null) + try { + tmpIn.close(); + } catch (IOException exIn) { + Timber.d(exIn); + } + throw new IllegalStateException(); + } + + in = tmpIn; + out = tmpOut; + state = BluetoothService.State.CONNECTED; + } + + public void run() { + Timber.d("BEGIN ConnectedThread %s", this); + final byte[] buffer = new byte[4096]; + int bytesRead; + + // Keep listening to the InputStream while connected + while (state == BluetoothService.State.CONNECTED) { + // Protocol: "SIDEKICK"|1-byte:reserved|2-bytes:length|buffer + try { + // protocol header + bytesRead = in.read(buffer, 0, MAGIC.length + 3); + int a = in.available() + bytesRead; + if (bytesRead != MAGIC.length + 3) + throw new IllegalStateException("message too short"); + for (int i = 0; i < MAGIC.length; i++) { + if (buffer[i] != MAGIC[i]) throw new IllegalStateException("no MAGIC"); + } + final int options = buffer[MAGIC.length]; // 0 regular message, else CODE instead of payload length + final int payloadLength = ((0xff & buffer[MAGIC.length + 1]) << 8) + (0xff & buffer[MAGIC.length + 2]); + Timber.d("READ options %d, payloadLength=%d, available=%d", options, payloadLength, a); + if ((options & 0x01) != 0) { // CODE + connectedCode = String.format(Locale.US, "%04d", payloadLength); + if (uiHandler != null) + uiHandler.obtainMessage(MessageType.CODE, connectedCode).sendToTarget(); + continue; + } + + int remainingBytes = payloadLength; + bytesIn.reset(); + while (remainingBytes > 0) { + bytesRead = in.read(buffer, 0, Math.min(remainingBytes, buffer.length)); + remainingBytes -= bytesRead; + bytesIn.write(buffer, 0, bytesRead); + } + + readBuffer = bytesIn.toByteArray(); + if (readSignal != null) { // someone is awaiting this + readSignal.countDown(); + } else if (commHandler != null) { // we are the counterparty + final int command = readBuffer[0]; + commHandler.obtainMessage(command, readBuffer).sendToTarget(); + if (uiHandler != null) { + uiHandler.obtainMessage(MessageType.READ_CMD, readBuffer.length, command).sendToTarget(); + } + } else { + throw new IllegalStateException("would drop a message"); + } + + } catch (IOException ex) { + Timber.d(ex, "disconnected"); + if (readSignal != null) readSignal.countDown(); // readBudder is still null + onConnectionLost(); + reconnect = false; + break; + } + } + Timber.d("END ConnectedThread %s", this); + } + + /** + * Write to the connected OutStream. + *

+ * Protocol: "SIDEKICK"|1-byte:reserved|2-bytes:length|buffer + * + * @param buffer The bytes to write + */ + public boolean write(byte[] buffer) { + boolean sendCode = buffer.length == 2; // TODO undo this hack + try { + final int len = buffer.length; + if (len > 65535) { + Timber.w("buffer too long %d", len); + return false; + } + bytesOut.reset(); + bytesOut.write(MAGIC); + if (sendCode) { + bytesOut.write(0x01); // options bit 0 is CODE + } else { + bytesOut.write(0); + bytesOut.write(len >> 8); + bytesOut.write(len & 0xff); + } + bytesOut.write(buffer); + out.write(bytesOut.toByteArray()); + + if (uiHandler != null) { + uiHandler.obtainMessage(MessageType.WRITE, buffer.length, -1).sendToTarget(); + } + } catch (IOException ex) { + Timber.d(ex, "Exception during write"); + return false; + //TODO probably kill the connection if this happens? + // but the read operation probably throws it as well, and that takes care of that! + } + return true; + } + + public void cancel() { + try { + socket.close(); + } catch (IOException ex) { + Timber.d(ex, "close() of connect socket failed"); + } + } + + } + + // for direct communication from JNI + // this should block + static public byte[] Exchange(byte[] request) { + Timber.d("EXCHANGE req = %d bytes", request.length); + final byte[] response = Instance.exchange(request); + Timber.d("EXCHANGE resp = %d bytes", response.length); + return response; + } + + static public boolean Write(byte[] request) { + return Instance.write(request); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java b/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java index 79ac2467..6e5a4609 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/MoneroHandlerThread.java @@ -26,7 +26,7 @@ import android.os.Process; /** * Handy class for starting a new thread that has a looper. The looper can then be * used to create handler classes. Note that start() must still be called. - * The started Thread has a stck size of STACK_SIZE (=5MB) + * The started Thread has a stack size of STACK_SIZE (=5MB) */ public class MoneroHandlerThread extends Thread { // from src/cryptonote_config.h diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Flasher.java b/app/src/main/java/com/m2049r/xmrwallet/util/Flasher.java new file mode 100644 index 00000000..a6da1680 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/util/Flasher.java @@ -0,0 +1,65 @@ +package com.m2049r.xmrwallet.util; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.View; + +import androidx.core.content.res.ResourcesCompat; + +import lombok.Getter; + +public class Flasher { + public interface Light { + int getDrawableId(); + } + + final private static int ON_TIME = 80; //ms + final private static int DURATION = 100 + ON_TIME; //ms + final private static int OFF_TIME = 600; //ms + @Getter + final private Drawable drawable; + private long t; + private ValueAnimator animator; + + private final int colorOff, colorOn; + private int colorCurrent; + + public Flasher(Context ctx, Light light) { + colorOff = ThemeHelper.getThemedColor(ctx, android.R.attr.colorControlNormal); + colorOn = ThemeHelper.getThemedColor(ctx, android.R.attr.colorControlActivated); + drawable = getDrawable(ctx, light.getDrawableId()); + drawable.setTint(colorOff); + } + + public void flash(View view) { + if (view == null) return; + if (animator != null) animator.cancel(); + + final long now = System.currentTimeMillis(); + t = now; + + animator = ValueAnimator.ofArgb(colorOff, colorOn); // always blink nomatter what + animator.addUpdateListener(valueAnimator -> { + colorCurrent = (Integer) valueAnimator.getAnimatedValue(); + drawable.setTint(colorCurrent); + }); + animator.setDuration(ON_TIME); + animator.start(); + view.postDelayed(() -> { + if (t == now) { // only turn it off if we turned it on last + animator = ValueAnimator.ofArgb(colorCurrent, colorOff); + animator.addUpdateListener(valueAnimator -> { + colorCurrent = (Integer) valueAnimator.getAnimatedValue(); + drawable.setTint(colorCurrent); + }); + animator.setDuration(Math.abs((long) (1. * OFF_TIME * ((colorCurrent - colorOff) / (colorOn - colorOff))))); + animator.start(); + } + }, DURATION); + } + + private Drawable getDrawable(Context ctx, int drawableId) { + return ResourcesCompat.getDrawable(ctx.getResources(), drawableId, null); + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..3143685e --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_bluetooth_24.xml b/app/src/main/res/drawable/ic_bluetooth_24.xml new file mode 100644 index 00000000..1e9621bf --- /dev/null +++ b/app/src/main/res/drawable/ic_bluetooth_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_bluetooth_connected_24.xml b/app/src/main/res/drawable/ic_bluetooth_connected_24.xml new file mode 100644 index 00000000..95db32a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_bluetooth_connected_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_bluetooth_disabled_24.xml b/app/src/main/res/drawable/ic_bluetooth_disabled_24.xml new file mode 100644 index 00000000..111179bc --- /dev/null +++ b/app/src/main/res/drawable/ic_bluetooth_disabled_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_bluetooth_searching_24.xml b/app/src/main/res/drawable/ic_bluetooth_searching_24.xml new file mode 100644 index 00000000..5f9dd59f --- /dev/null +++ b/app/src/main/res/drawable/ic_bluetooth_searching_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 85c122df..ca3826a4 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,29 +1,74 @@ - + - - - - - - - - - + xmlns:android="http://schemas.android.com/apk/res/android"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 2c2fd4b4..00000000 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_sidekick_restore.xml b/app/src/main/res/drawable/ic_sidekick_restore.xml new file mode 100644 index 00000000..c6ba5443 --- /dev/null +++ b/app/src/main/res/drawable/ic_sidekick_restore.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_sidekick.xml b/app/src/main/res/layout/activity_sidekick.xml new file mode 100644 index 00000000..3f0b8c6a --- /dev/null +++ b/app/src/main/res/layout/activity_sidekick.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_wallet.xml b/app/src/main/res/layout/activity_wallet.xml index 583b039a..75672049 100644 --- a/app/src/main/res/layout/activity_wallet.xml +++ b/app/src/main/res/layout/activity_wallet.xml @@ -20,6 +20,11 @@ android:background="@drawable/backgound_toolbar_mainnet" android:minHeight="?android:attr/actionBarSize" /> + + + diff --git a/app/src/main/res/layout/fragment_bluetooth.xml b/app/src/main/res/layout/fragment_bluetooth.xml new file mode 100644 index 00000000..a1e06b16 --- /dev/null +++ b/app/src/main/res/layout/fragment_bluetooth.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml index 7f582df3..aee49490 100644 --- a/app/src/main/res/layout/fragment_login.xml +++ b/app/src/main/res/layout/fragment_login.xml @@ -19,94 +19,101 @@ android:layout_height="wrap_content" android:orientation="vertical" /> - + android:orientation="vertical"> - - - - - - - - - - - - - - - - - - + android:layout_marginStart="8dp" + android:gravity="start|center" + android:padding="8dp" + android:text="@string/label_daemon" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_sidekick_connect.xml b/app/src/main/res/layout/fragment_sidekick_connect.xml new file mode 100644 index 00000000..451f37db --- /dev/null +++ b/app/src/main/res/layout/fragment_sidekick_connect.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_bluetooth.xml b/app/src/main/res/layout/item_bluetooth.xml new file mode 100644 index 00000000..c3333547 --- /dev/null +++ b/app/src/main/res/layout/item_bluetooth.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_fabmenu.xml b/app/src/main/res/layout/layout_fabmenu.xml index 02778f85..88f84612 100644 --- a/app/src/main/res/layout/layout_fabmenu.xml +++ b/app/src/main/res/layout/layout_fabmenu.xml @@ -29,8 +29,8 @@ android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginEnd="8dp" - android:text="@string/fab_create_new" - android:contentDescription="@string/fab_create_new" /> + android:contentDescription="@string/fab_create_new" + android:text="@string/fab_create_new" /> + android:contentDescription="@string/fab_restore_viewonly" + android:text="@string/fab_restore_viewonly" /> + android:contentDescription="@string/fab_restore_key" + android:text="@string/fab_restore_key" /> + android:contentDescription="@string/fab_restore_seed" + android:text="@string/fab_restore_seed" /> + android:contentDescription="@string/menu_restore" + android:text="@string/menu_restore" /> + + + + + + + + + app:tint="?attr/toolbarTextColor" /> diff --git a/app/src/main/res/menu/create_wallet_ledger.xml b/app/src/main/res/menu/create_wallet_ledger.xml index 8bd54cb6..9fe4aded 100644 --- a/app/src/main/res/menu/create_wallet_ledger.xml +++ b/app/src/main/res/menu/create_wallet_ledger.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> +

+ + + + \ No newline at end of file diff --git a/app/src/main/res/menu/list_menu.xml b/app/src/main/res/menu/list_menu.xml index f660304a..da7d5a0d 100644 --- a/app/src/main/res/menu/list_menu.xml +++ b/app/src/main/res/menu/list_menu.xml @@ -8,4 +8,10 @@ android:orderInCategory="100" android:title="@string/menu_help" app:showAsAction="ifRoom" /> + \ No newline at end of file diff --git a/app/src/main/res/menu/sidekick_connect_menu.xml b/app/src/main/res/menu/sidekick_connect_menu.xml new file mode 100644 index 00000000..3957b5db --- /dev/null +++ b/app/src/main/res/menu/sidekick_connect_menu.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 37b6c08a1c775c5e4d5aea675633bff7c7ac647d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2667 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 Z{{nNIV68@aE3g0n002ovPDHLkV1m$q?T`Qf diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..506c08980ad2abc5072d9305b9d95f3b7ea61339 GIT binary patch literal 1724 zcmV;t21EH$Nk&Gr1^@t8MM6+kP&iDd1^@srN5Byf3B_&OMv#-Avj4*Au|h=uC!jcY zJeXu@GSVbbwQ3IAAQ(lGq$rn;Vf+i*_evNE z2H8f=HOm1!!uNjyPzL~CghNe$1U5AH_LU4s1RNY4G665Zv3wu_5CCYiM*su>eS(jJ z@B=&q2c>J<4oTAUMX7si+vd((+1}Z<<3G&l)mS^*wr$%}W@fzaAme=lt>qID*?pvD z+qV46dB5Kehvvz%FF<4s9Rd=GEm(l;jp!SAch~Q|CEK=b+Rm73?|biyos_J)ZKL{9 zw{4^DRBhXKN@@84+t$;?UT|yMR;|o)ad(;^bC>uJA}fG4D24{y-7+$VCJ5c0IB?s> zku)kz=q@M`8&p*aF>;{viAw^Cl4=bkpDw@s+O87#SQIrW72r`Qpfd0z z~Rr?(En+QWGSEs8-z`ossJWHV5L=| zED&hb0)#VcQYLF-W-T7vU>8Boji879QM(JR`?n=3eaS!>r( z4)E+elTa*||2u6d4e_%) z@=|N;Idaxd)ZuX460AoehU_CdMJ`){4Fi(o7sWI1mTQs}~sInpLDBsCZ%J!FYU004VQf1(SA zT(@_;9fJs{!59;`rQ$#U#|w!AEE%)!kSPKn zcOP!E0gf_HHtTU&Ensit@KoDqGhib0*c)~X_{uhh5ZAZcB%YU;|58w~)#cR1?KbT;`Ll|%#8h6VK9&gYoAj#Mu z7zhC59kaGGKAxvRXY(B~DjYm!0!WhU=PJ+k6cZ&bH2Kf<=kZFs)-p;`tP-=&$OCXf zaOTI09%y|jrS(#9`LfG0X#(&^k*qV;>qFlR2``<|a;Y2G{X%cR%zl`)dz(oJ4tW&&s?FVJ- zaNdtwdPIQ9{?nG&%H#dRMg$qw;eyIW$yhC&k>7hr;_@UlNKx#S zoE3CJP4c!~B*>L$r66`+qN7F*`KKpd={8lKdEJHx3LwDjn0|8T$T)FCivg07d*=i{ zdCU>^P=NwCklZX!ZrEF4gGeAG-|arPbu2Rn2LeHcb(pz{lC@z3K=RX`cj?C#2J;4O z5^yhL0Z1Z}6XcFYx6Dxn^O6K!hx+5T0H&Ge8;z3p8ayTX6(mKj4VV&c6ks5ju4JlY z?I%T+Jjt`bWKtpufe1>W>Vagc^u9022!RQ*6i`7RBLr!qD_^=%BZ`uKujz#R(yvGI zw(>SQT<8x2I%QEA+b~3bqxH6MN4y{`;BF3-zhAg^w!*Es-F=4`l1(YYL^e@!6 z-tstj_0(RCbN4cf8n?#*zDKTYTbX_F-*WFk?+-f>1`0-M>ATfafHmupyhCoIcOhHs4B>d(_Uh*<{ea|8A ziigyARRRL%)PNVX0xdu-5QPVz1pa_GoXmb8gfD}0pK7qDb`!qEYW%ZQIM_mgJ)YrP S#V7EeNe2kB_zf4H3IPCd9Wa#u literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 415f6a06956b3c2b8ad0f812eb9af582e668e3e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4325 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 DOrwD7 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..f4a94931c7de9197d05067e497e290484d394436 GIT binary patch literal 1388 zcmV-y1(W(xNk&Fw1pok7MM6+kP&iCi1pojqFTe{B2}Et%NZ#$Y@F#pEBKjW>b`Dcq z)qSpo7OW!4YSaWJ2%zw<<|!at`Z^(OBS~`7FDwc9M~10|NdOoM(l%gGcKRQP4RoP>?wQZ}~GWWf|VHwdk$O(xkU8daA)nPfTjL{rwfAuQPDtdqkLxs{ucihE z4EHg-)WApjsvjq%)713HQu@UklA5SOrh^N9Cr!wyo;K&6T8b~O16CTNt(*~@LP*l` z$ICtBDSQ0|GnqYSW}OjK!9Wiq$d7cjPGf!l#FG!P)Nf|(gaNyW*LWbv^}?yNgz zVD`uu0FXRxug=v?-mg&xWm%E%S%}CMR2b?gTtNQlrTeQNW_JFwEJDRmI(pL$?0&> z*#oA;eIht}wQ4dXExf7Dw&TBbCT!}p34jC#Vu)2?vISKp0M+4$7q6wBaRf$p`!YN)tdMFe+Nf%xwNVKv-I0z&vm$B4JGLvYASW&8_PDKTv z4=vCW#Q->#0*VK6D4-Nxj!@Dl6+;9Z6O}HYFF>LYjOaOED4`&wZb(wdsH_A`dqLnJ zfGDB^dJ-Tq70a;{PVpRB6eJ;l#1N~JYN=z@SOBQyFY75z8GULRMS=X)kA7IU{n2+c z2uOj5q{t9zG z8(af$J)1?&+Xbs-0PkOCb%3gYEJ+FqUKF55g9~@qcGWZ(KH7_3af2jCSOKA=Q5X&a z^u)r|yJ(~J&)+dO_lzrgoWuZP0|Cm4gvk~-#sGV3$npQaYBG=RwfdrONFt_1l7c#! zs(|Rxn5#1@2LDCR0_W_J;h!oH17&W5a$r;m49~?xXSzKCx|#Q;ADRpHEK{q31~f*I z69Oa+pmEsLXK(dKJn9z_h7@vcwtAjx&o_L4Vqs1NqH*{Q{~|s7bLH0@x+;N2S&XcV z+QsZhJ~M-__8s;#LMepEWjHdM`s^vs|J6`19B0aBt5w%;Qf4$QOr! uyrhxmwLM^NF;FSuSUvCtjF|Tb#~oALU7YN89G9LAIP)`d!?(`lyb=HvXo*(< literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 40999689155648bbd3356a2c04663c4f2d8e4633..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2800 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 ICW7gW0NJjPs{jB1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4fd96202d1740c7fac5c3e8fe3c84b3c142aa246..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3701 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 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..605274b1afbe128701e15e5969493fd0b4f91d9b GIT binary patch literal 2486 zcmV;n2}$-+Nk&Gl2><|BMM6+kP&iDY2><{uU%(d-2}NxiNlJHrDSyIyLWt=91aKWs zCfVi6rN|sWH)?aJtq1kA`eANh;da3!wry2gJ^cfq#{MyW1XTP;e)lkfnn;qOu8a7= zzcAucKq0|Q+cs8IK;w^XNB<`P@)JTh2x18QQ?>{pDc_AOq@a@|AeoV$WC2bbkibI8 z4Olz?&}MZ8k>w4+55Tb$H~6b+qSi`ZL?gF`F-d7`RD(=9hv9M_KPUmktA8RB99tS z0R2^jzxVE$lmpqeXqQ;zOZn zBOfn*mc!Hh*qh3!kk^fMBn5h7K!@06m3SZoV}<;M&aoMqGD-#iZ09MELJa}ZILId6 zJQ}CUAl0T~u>30wzJdD~K;Y&(|E55?0!%2%;x!@5U=>oR-So3Eh4aY7!V} zdq|PMJ3>#--iYW;J^ZB_2_f>AfC$|q4uwJKvG^bII4;FoJoWeg+GWe9VZjZxgRkdt zH)6I7-TWi(qQ|#V3d3vO3ihh)Y#D%3bkOx$RIdMg8eR)l5E6F>8S3Jtcl#;Nq*>x_ z{>698h%dwJrR}`-Gi3d%{F8_8?uZtKnHD~9;{Si;f-H>nV{Qug4mWi5>IRxq&^0Ty z!=*OvW4?a3+f)4+tcA3KquL{U;r#UyPcCen?0@lkr>m3MiTinvo_6<7x!XJ3u({<* zEh+CFwL0Ub&z$!x4+QW@zkc=fn_Yj|&hK|u@&hz(65GWVzOYfOVU+He%uCW3fv!@u zt#82ow`@&~0QY9oY8`TYx48PtD;P*$wlUw{+<`|nbky70Op8lCz#>8Y!a>D0@=0dN zyisk|sIg=at)l4Kl?gPTOYzdD~R-cG+u5fDk? zGnen)+!3EA{a#Y;^{cq&wVYp8z}-Jx+7&B9?5cr4sFNhm%sn6)2jKqFhCRMcFK=k& zXMdue`_B7H>oT12YGv& zpWM`=pSeBCZv8v^oA+UV#7;c=>aL|n%PX<-m!1N5D|5jZ6-q)DPyd@$`49B#L=g<=y|!m&Ot$f-&u`{B{Y|6j=m zoFIR_&!SbY%q*GBCV+d30)#4*3BdAOV9y_WrzC4K_s?EMfhyY8dP@Lf8ncOl(zMF5 zG+E-;l_M;4hKgjm41eWObPEdT7sML%Z}(eCi9GiQVvE9&g`Ouyx7fbjAOJzcdU55q|H~u# zV0?;tGD0j1V6~fzqt2i8{9iKzqU**aM<{aCg_J1jRp7)+cJ2QT7gmW`3~<+YeEI^l zU-y8(hTvm?oN&qJ`SkyWccz!WPEz~R?u2{LJ9h$VeJDlgl{(2A)60J3{fCnx`>_HL z0eC5{C4Hsw6K;OfAKKv&zcu03OWwO?$Vd*T@JI4DdvH9*eq!vPWq}FEA0QYmo!m7q zU~ggs@vek3(m7T)4hpA6hGis7(fdkRf))gO&HwHJK=p!EA)RA>csgckv>)&y zF*+`Y7_uaDo1pFD~{#Bm7gj*@KnLice#1otw{*y0|n@X zbVwWugvivBT>ppl5LiT8?!EGMMUeFDzl)yl>-O=yx*?V6f8h*Xn2ZX6;(9&^f^{|V z(%snT?&`98mk+C;r4v(BHV=dJ+FAeOsr!^eKQJ%Jqc`y^4GvH<3{a&10f3w1UypaQ zr~P;HT?>tarX+<;GO|aa+xSmX2zd;&B(^%6_uHp6o(D&3bMWWx{jb+n+yNfM(wY$J zfZfgj18U8(zCZw-f~fqbi6Tl=7&0Ms!VAN@r@!zYEC`_HT7ZGdYak==NA-V_s6RU4 AEC2ui literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index a5214605ff8eb15f16395da35c78cbc32c8d6ca7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6277 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 460dbd4f617aae647067edfd4176f26b781b3fde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5431 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 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..650e3b79b55246bbb27ac455038a560366bc8af3 GIT binary patch literal 3050 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%0DjK(xc~qF literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index d7ddf89ccbcaa96123ab76dd954038513673c678..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9265 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 Tm_A~j00000NkvXXu0mjf?lK6S diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..a2564f7e625b8dd2a16179cdbfb3165eeced4477 GIT binary patch literal 7206 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 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..eb7ef4836dd597cc5d89e40c1e4df77e9d68f6f4 GIT binary patch literal 5614 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 ImJ1~S08BaJZU6uP literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 79b8131e4f648a2a453cad97c78f228958eae728..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13045 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 E0-o{BBme*a literal 0 HcmV?d00001 diff --git a/app/src/main/res/values-cat/help.xml b/app/src/main/res/values-cat/help.xml index a72ebbdd..51298ddd 100644 --- a/app/src/main/res/values-cat/help.xml +++ b/app/src/main/res/values-cat/help.xml @@ -251,4 +251,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-cat/strings.xml b/app/src/main/res/values-cat/strings.xml index 88c33154..804d592d 100644 --- a/app/src/main/res/values-cat/strings.xml +++ b/app/src/main/res/values-cat/strings.xml @@ -447,4 +447,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-de/help.xml b/app/src/main/res/values-de/help.xml index 058272bc..c680b56c 100644 --- a/app/src/main/res/values-de/help.xml +++ b/app/src/main/res/values-de/help.xml @@ -311,4 +311,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 3868613c..78a6fb0a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -448,4 +448,17 @@ ANWENDEN Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-el/help.xml b/app/src/main/res/values-el/help.xml index 9683ba59..c1a7434c 100644 --- a/app/src/main/res/values-el/help.xml +++ b/app/src/main/res/values-el/help.xml @@ -293,4 +293,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 522ba7c7..855b3477 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -449,4 +449,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-eo/help.xml b/app/src/main/res/values-eo/help.xml index 601cd27e..1785c885 100644 --- a/app/src/main/res/values-eo/help.xml +++ b/app/src/main/res/values-eo/help.xml @@ -337,4 +337,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index f6d669f3..bd4b49a1 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -447,4 +447,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-es/help.xml b/app/src/main/res/values-es/help.xml index c398da6a..4fd451f1 100644 --- a/app/src/main/res/values-es/help.xml +++ b/app/src/main/res/values-es/help.xml @@ -138,15 +138,6 @@

Tasa de cambio

Debajo verás la cotización actual ofrecida por el exchange, junto con los montos mínimos y máximos. Por favor ten presente que en este paso esta cotización no está garantizada aún, va a confirarse en la siguiente pantalla, cuando tu transacción esté lista para aprobarse.

]]> - - Orden de cambio -

En esta pantalla estás viendo la orden coordinada con el exchange. Esta es válida por un tiempo limitado, quizás notes una cuenta regresiva en el botón de \"Enviar\". Esta cotización puede ser diferente a la indicada en los pasos anteriores, pero mientras autorices el envío antes de que se acabe el tiempo, quedará fija.

-

Número de orden

-

Como Monerujo solamente maneja el lado de XMR de la transacción, vas a necesitar este número de orden para rastrear el proceso de tu intercambio o recibir soporte en el sitio del exchange.

-

El tiempo se acabó

-

En caso de que la cuenta atrás llegue a cero, necesitarás conseguir una nueva cotización del exchange. Para eso debes volver a la pantalla anterior, y luego avanzar nuevamente a esta.

- ]]>
Crear Monedero con Ledger @@ -220,4 +211,37 @@

Nodo de Tor

Este nodo tiene una dirección .onion. Para usarlo, debes activar el modo Tor presionando el ícono junto al nodo en la pantalla principal.

]]>
+ + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index bf6bbe01..8f8de438 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -444,4 +444,17 @@ APLICAR Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-et/help.xml b/app/src/main/res/values-et/help.xml index cf7e47a0..dcd16fbb 100644 --- a/app/src/main/res/values-et/help.xml +++ b/app/src/main/res/values-et/help.xml @@ -313,4 +313,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index f0468de1..9100252f 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -447,4 +447,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-fa/help.xml b/app/src/main/res/values-fa/help.xml index e557243e..d6ea0761 100644 --- a/app/src/main/res/values-fa/help.xml +++ b/app/src/main/res/values-fa/help.xml @@ -422,4 +422,37 @@

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 2b34fa2f..96a5e719 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -687,4 +687,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-fr/help.xml b/app/src/main/res/values-fr/help.xml index 58aa78ad..c7b17caf 100644 --- a/app/src/main/res/values-fr/help.xml +++ b/app/src/main/res/values-fr/help.xml @@ -331,4 +331,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 860edf1f..905b6c86 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -460,4 +460,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-he/help.xml b/app/src/main/res/values-he/help.xml index 3bb802ec..e6800a48 100644 --- a/app/src/main/res/values-he/help.xml +++ b/app/src/main/res/values-he/help.xml @@ -310,4 +310,37 @@

זהו חוליית אוניון. כדי להשתמש בה, עליך להפעיל את מצב טור על-ידי לחיצה ב-סמל ליד החלק העליון של דף רשימת הארנקים.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index c05e5020..5bf905d9 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -447,4 +447,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-hu/help.xml b/app/src/main/res/values-hu/help.xml index af34a451..d3541c8b 100644 --- a/app/src/main/res/values-hu/help.xml +++ b/app/src/main/res/values-hu/help.xml @@ -315,4 +315,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 29d01b2d..9fb4ac1d 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -451,4 +451,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-it/help.xml b/app/src/main/res/values-it/help.xml index 84e772f4..a32e3d4b 100644 --- a/app/src/main/res/values-it/help.xml +++ b/app/src/main/res/values-it/help.xml @@ -316,4 +316,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9d915f3c..f4b88c5f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -452,4 +452,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-ja/help.xml b/app/src/main/res/values-ja/help.xml index cbc0d117..c58ba6d8 100644 --- a/app/src/main/res/values-ja/help.xml +++ b/app/src/main/res/values-ja/help.xml @@ -444,4 +444,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 196a1caf..c305f355 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -452,4 +452,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-nb/help.xml b/app/src/main/res/values-nb/help.xml index 70cdab6d..7644f541 100644 --- a/app/src/main/res/values-nb/help.xml +++ b/app/src/main/res/values-nb/help.xml @@ -313,4 +313,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 431d1d24..80453eac 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -449,4 +449,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-nl/help.xml b/app/src/main/res/values-nl/help.xml index 60ff8f6b..a150953c 100644 --- a/app/src/main/res/values-nl/help.xml +++ b/app/src/main/res/values-nl/help.xml @@ -251,4 +251,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 222f4557..59f0449c 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -449,4 +449,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-pt-rBR/help.xml b/app/src/main/res/values-pt-rBR/help.xml index f1ecf142..a4d386ad 100755 --- a/app/src/main/res/values-pt-rBR/help.xml +++ b/app/src/main/res/values-pt-rBR/help.xml @@ -312,4 +312,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 8218cb91..189354e1 100755 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -442,4 +442,17 @@ aqui. APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-pt/help.xml b/app/src/main/res/values-pt/help.xml index 8d2ada45..86fbd010 100644 --- a/app/src/main/res/values-pt/help.xml +++ b/app/src/main/res/values-pt/help.xml @@ -312,4 +312,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index e6ccbc0a..31e58f05 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -453,4 +453,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-ro/help.xml b/app/src/main/res/values-ro/help.xml index 5509965f..e2d42d9c 100644 --- a/app/src/main/res/values-ro/help.xml +++ b/app/src/main/res/values-ro/help.xml @@ -319,4 +319,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 9aa4201e..a98b2f61 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -449,4 +449,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-ru/help.xml b/app/src/main/res/values-ru/help.xml index 28fb0b0c..664b9daf 100644 --- a/app/src/main/res/values-ru/help.xml +++ b/app/src/main/res/values-ru/help.xml @@ -316,4 +316,37 @@

Это .onion узел. Для того, чтобы использовать его, вы должны включить Tor, нажав значок в верхней части страницы списка кошельков.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3b3967c0..af38c4bf 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -453,4 +453,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-sk/help.xml b/app/src/main/res/values-sk/help.xml index 93c46725..7d4910a7 100644 --- a/app/src/main/res/values-sk/help.xml +++ b/app/src/main/res/values-sk/help.xml @@ -280,4 +280,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 9b388474..bac5fd26 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -450,4 +450,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-sr/help.xml b/app/src/main/res/values-sr/help.xml index d8b92bbc..8625bb9a 100644 --- a/app/src/main/res/values-sr/help.xml +++ b/app/src/main/res/values-sr/help.xml @@ -311,4 +311,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 36daf710..e09c8291 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -448,4 +448,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-sv/help.xml b/app/src/main/res/values-sv/help.xml index e3f80d88..851c6462 100644 --- a/app/src/main/res/values-sv/help.xml +++ b/app/src/main/res/values-sv/help.xml @@ -300,4 +300,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index a0ab29dc..7fdb0e7b 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -441,4 +441,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-ta/help.xml b/app/src/main/res/values-ta/help.xml index a81a79bd..03ea448a 100644 --- a/app/src/main/res/values-ta/help.xml +++ b/app/src/main/res/values-ta/help.xml @@ -319,4 +319,37 @@

இது ஒரு .onion கணுவாகும். இதை பயன்படுத்த நீங்கள் பணப்பை பட்டியல் பக்கத்தின் மேல் உள்ள சின்னத்தை தட்டி TOR பயன்முறையை செயல்படுத்தவும்.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index e75e0ae6..6a0abb25 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -447,4 +447,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-uk/help.xml b/app/src/main/res/values-uk/help.xml index c968fea9..5216f575 100644 --- a/app/src/main/res/values-uk/help.xml +++ b/app/src/main/res/values-uk/help.xml @@ -311,4 +311,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 38c0e34b..64c482dd 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -453,4 +453,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-zh-rCN/help.xml b/app/src/main/res/values-zh-rCN/help.xml index 2d508d27..12d6493d 100644 --- a/app/src/main/res/values-zh-rCN/help.xml +++ b/app/src/main/res/values-zh-rCN/help.xml @@ -251,4 +251,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ac4e9127..3fdae218 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -374,4 +374,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values-zh-rTW/help.xml b/app/src/main/res/values-zh-rTW/help.xml index 6613b5e3..a1456905 100644 --- a/app/src/main/res/values-zh-rTW/help.xml +++ b/app/src/main/res/values-zh-rTW/help.xml @@ -252,4 +252,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]> + + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index df112e3a..37a6fa68 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -448,4 +448,17 @@ APPLY Lock Wallet in Background + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values/help.xml b/app/src/main/res/values/help.xml index d5e4a397..b25c7c3d 100644 --- a/app/src/main/res/values/help.xml +++ b/app/src/main/res/values/help.xml @@ -179,22 +179,10 @@ Converting XMR

Amounts

-

Enter on the \"Amount\" field the amount of cryptocurrency you want to send. By default, it will show you the equivalent amount of XMR to be sent from your wallet. You can pick a different currency from the dropdown if you want to fix your amount on it. Monerujo uses a fixed rate, so at the end of the process you should get the exact amount you specify here.

+

Enter on the \"Amount\" field the amount of cryptocurrency you want to send. By default, it will show you the equivalent amount of XMR to be sent from your wallet. You can pick a different currency from the dropdown if you want to fix your amount on it. Monerujo uses a fixed rate, so at the end of the process you should get the exact amount you specify here.

Exchange rate

Below you will see the current rate provided by the exchange, along with their lower and upper limits. Please note that this rate is not guaranteed at this point, it will be set the moment your transaction is ready to be approved on the next screen.

]]>
- - Exchange Order

-

On this screen you will see the actual exchange order. This order is valid for - a limited time - you may notice a countdown on the \"Spend\" button. This exchange rate may - be different to the one shown on previous screens, but as long as you accept before the time runs out, it\'ll be fixed.

-

Order Number

-

Since Monerujo only handles the XMR side of your transaction, your order number can be used to track or get support for your order on the exchange\'s website.

-

Countdown is over

-

Once the countdown reaches zero, you need to get a new quote from the exchange by going back to the - previous screen, and then coming back to this screen.

- ]]>
The Wallet @@ -300,4 +288,37 @@

This is an .onion node. In order to use it, you must enable Tor mode by touching the icon near the top of the Wallet List page.

]]>
+ + Using your sidekick wallet + Check that both phones are connected via Bluetooth and both apps are opened. + +

On sidekick:

+ Open your wallet of choice. + +

On monerujo:

+ Connect to sidekick. + + Open the wallet matching the one you have opened on the sidekick phone. + + Use it as you\'d normally use any wallet. Let it scan to see incoming transactions, share addresses, or send moneros. If the app needs to do something that involves your keys, it\'ll ask for permission from sidekick. + +

On sidekick:

+ If monerujo requested permission, you should see a dialog that shows the transaction details like amount and fees. You\'ll also see two options, to ACCEPT or DENY it. Choose wisely. + +

On monerujo:

+ The transaction should be signed and ready to be sent. Press to make it so. +
+ ]]>
+ + Create Wallet - Sidekick +

You want to recover your wallet from your awesome Sidekick device.

+

Et netus et malesuada fames. Mattis enim ut tellus elementum sagittis vitae. Eget duis at + tellus at. Id aliquet risus feugiat in. Donec et odio pellentesque diam. Dictum fusce ut + placerat orci nulla. Amet nisl suscipit adipiscing bibendum est ultricies integer quis.

+

Enter the block number of the first transaction used for this address in the + field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure, + enter an approximate date/blockheight before you first used this wallet address.

+ ]]>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 49b006cb..c31a5d72 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -27,7 +27,7 @@ Credits OK Cancel - Close Note + Close Detailed information Successfully sent @@ -197,6 +197,7 @@ Seed View Ledger + Sidekick Public Address View Key @@ -501,4 +502,32 @@ APPLY Lock Wallet in Background + + Sidekick Wallet + monerujo Sidekick Service + + Bluetooth not connected + Paired Devices + Other Devices + Scan + None paired + None found + Select Device + Bluetooth + Connect Sidekick + Select Sidekick device + Scanning for devices + + Bluetooth permissions are required to connect your Sidekick Wallet! + OK + App Settings + Cancel + + PIN: %1$s + [not connected] + Confirm Transaction + Network is on - this is a security risk. It is recommended + to turn on flight mode and restart the app with only Bluetooth enabled! + Please connect Sidekick device + Restore from Sidekick diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 4501c4dd..1a5256fa 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -265,6 +265,9 @@ 24sp +