Sidekick Support for Monerokon
|
@ -8,12 +8,8 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/app/build
|
/app/build
|
||||||
/app/release
|
/app/release
|
||||||
/app/alpha
|
/app/alpha*
|
||||||
/app/prod
|
/app/prod*
|
||||||
/app/alphaMainnet
|
|
||||||
/app/prodMainnet
|
|
||||||
/app/alphaStagenet
|
|
||||||
/app/prodStagenet
|
|
||||||
/app/.cxx
|
/app/.cxx
|
||||||
/monerujo.id
|
/monerujo.id
|
||||||
/external-libs/VERSION
|
/external-libs/VERSION
|
||||||
|
|
|
@ -8,8 +8,8 @@ android {
|
||||||
compileSdk 34
|
compileSdk 34
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 3311
|
versionCode 4005
|
||||||
versionName "3.3.11 'Argentina'"
|
versionName "4.0.5 'Sidekick'"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
|
@ -24,7 +24,7 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions 'type', 'net'
|
flavorDimensions = ['type', 'net']
|
||||||
productFlavors {
|
productFlavors {
|
||||||
mainnet {
|
mainnet {
|
||||||
dimension 'net'
|
dimension 'net'
|
||||||
|
@ -132,10 +132,10 @@ static def getId(name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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.core:core:1.13.1'
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
|
@ -143,7 +143,7 @@ dependencies {
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.preference:preference:1.2.1'
|
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 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||||
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
android:name="android.hardware.camera"
|
android:name="android.hardware.camera"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2017 m2049r
|
* Copyright (c) 2017-2024 m2049r
|
||||||
* <p>
|
* <p>
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,8 +18,6 @@
|
||||||
#include "monerujo.h"
|
#include "monerujo.h"
|
||||||
#include "wallet2_api.h"
|
#include "wallet2_api.h"
|
||||||
|
|
||||||
//TODO explicit casting jlong, jint, jboolean to avoid warnings
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
|
@ -34,7 +32,6 @@ extern "C"
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG,__VA_ARGS__)
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG,__VA_ARGS__)
|
||||||
|
|
||||||
static JavaVM *cachedJVM;
|
static JavaVM *cachedJVM;
|
||||||
static jclass class_String;
|
|
||||||
static jclass class_ArrayList;
|
static jclass class_ArrayList;
|
||||||
static jclass class_WalletListener;
|
static jclass class_WalletListener;
|
||||||
static jclass class_CoinsInfo;
|
static jclass class_CoinsInfo;
|
||||||
|
@ -42,17 +39,11 @@ static jclass class_TransactionInfo;
|
||||||
static jclass class_Transfer;
|
static jclass class_Transfer;
|
||||||
static jclass class_Ledger;
|
static jclass class_Ledger;
|
||||||
static jclass class_WalletStatus;
|
static jclass class_WalletStatus;
|
||||||
|
static jclass class_BluetoothService;
|
||||||
|
static jclass class_SidekickService;
|
||||||
|
|
||||||
std::mutex _listenerMutex;
|
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) {
|
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
|
||||||
cachedJVM = jvm;
|
cachedJVM = jvm;
|
||||||
LOGI("JNI_OnLoad");
|
LOGI("JNI_OnLoad");
|
||||||
|
@ -62,8 +53,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
|
||||||
}
|
}
|
||||||
//LOGI("JNI_OnLoad ok");
|
//LOGI("JNI_OnLoad ok");
|
||||||
|
|
||||||
class_String = static_cast<jclass>(jenv->NewGlobalRef(
|
|
||||||
jenv->FindClass("java/lang/String")));
|
|
||||||
class_ArrayList = static_cast<jclass>(jenv->NewGlobalRef(
|
class_ArrayList = static_cast<jclass>(jenv->NewGlobalRef(
|
||||||
jenv->FindClass("java/util/ArrayList")));
|
jenv->FindClass("java/util/ArrayList")));
|
||||||
class_CoinsInfo = static_cast<jclass>(jenv->NewGlobalRef(
|
class_CoinsInfo = static_cast<jclass>(jenv->NewGlobalRef(
|
||||||
|
@ -78,6 +67,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
|
||||||
jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger")));
|
jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger")));
|
||||||
class_WalletStatus = static_cast<jclass>(jenv->NewGlobalRef(
|
class_WalletStatus = static_cast<jclass>(jenv->NewGlobalRef(
|
||||||
jenv->FindClass("com/m2049r/xmrwallet/model/Wallet$Status")));
|
jenv->FindClass("com/m2049r/xmrwallet/model/Wallet$Status")));
|
||||||
|
class_BluetoothService = static_cast<jclass>(jenv->NewGlobalRef(
|
||||||
|
jenv->FindClass("com/m2049r/xmrwallet/service/BluetoothService")));
|
||||||
return JNI_VERSION_1_6;
|
return JNI_VERSION_1_6;
|
||||||
}
|
}
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -1686,6 +1677,79 @@ int LedgerFind(char *buffer, size_t len) {
|
||||||
return ret;
|
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<jsize>(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<int>(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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Copyright (c) 2017 m2049r
|
* Copyright (c) 2017-2024 m2049r
|
||||||
* <p>
|
* <p>
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -19,6 +19,8 @@
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
|
|
||||||
|
@ -28,6 +30,10 @@
|
||||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
|
#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") {
|
jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
||||||
jclass c = env->GetObjectClass(obj);
|
jclass c = env->GetObjectClass(obj);
|
||||||
return env->GetFieldID(c, fieldName, "J"); // of type long
|
return env->GetFieldID(c, fieldName, "J"); // of type long
|
||||||
|
@ -35,8 +41,16 @@ jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handl
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
T *getHandle(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
T *getHandle(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
||||||
|
return reinterpret_cast<T *>(env->GetLongField(obj, getHandleField(env, obj, fieldName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void destroyNativeObject(JNIEnv *env, T nativeObjectHandle, jobject obj, const char *fieldName = "handle") {
|
||||||
jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName));
|
jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName));
|
||||||
return reinterpret_cast<T *>(handle);
|
if (handle != 0) {
|
||||||
|
ThrowException(env, "java/lang/IllegalStateException", "invalid handle (destroy)");
|
||||||
|
}
|
||||||
|
delete reinterpret_cast<T *>(nativeObjectHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) {
|
void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) {
|
||||||
|
@ -54,7 +68,7 @@ extern "C"
|
||||||
{
|
{
|
||||||
#endif
|
#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
|
// from monero-core crypto/hash-ops.h - avoid #including monero code here
|
||||||
enum {
|
enum {
|
||||||
|
@ -62,18 +76,40 @@ enum {
|
||||||
HASH_DATA_AREA = 136
|
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) {
|
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*/);
|
cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void slow_hash_broken(const void *data, char *hash, int variant) {
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#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
|
#endif //XMRWALLET_WALLET_LIB_H
|
||||||
|
|
After Width: | Height: | Size: 13 KiB |
|
@ -16,7 +16,17 @@
|
||||||
|
|
||||||
package com.m2049r.xmrwallet;
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
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.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
@ -24,7 +34,8 @@ import android.os.PowerManager;
|
||||||
|
|
||||||
import androidx.activity.OnBackPressedCallback;
|
import androidx.activity.OnBackPressedCallback;
|
||||||
import androidx.annotation.CallSuper;
|
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.data.BarcodeData;
|
||||||
import com.m2049r.xmrwallet.dialog.ProgressDialog;
|
import com.m2049r.xmrwallet.dialog.ProgressDialog;
|
||||||
|
@ -159,4 +170,87 @@ public class BaseActivity extends SecureActivity
|
||||||
barcodeData = null;
|
barcodeData = null;
|
||||||
return popped;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,6 @@ package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
|
@ -67,8 +66,20 @@ public class GenerateFragment extends Fragment {
|
||||||
static final String TYPE_KEY = "key";
|
static final String TYPE_KEY = "key";
|
||||||
static final String TYPE_SEED = "seed";
|
static final String TYPE_SEED = "seed";
|
||||||
static final String TYPE_LEDGER = "ledger";
|
static final String TYPE_LEDGER = "ledger";
|
||||||
|
static final String TYPE_SIDEKICK = "sidekick";
|
||||||
static final String TYPE_VIEWONLY = "view";
|
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 TextInputLayout etWalletName;
|
||||||
private PasswordEntryView etWalletPassword;
|
private PasswordEntryView etWalletPassword;
|
||||||
private LinearLayout llFingerprintAuth;
|
private LinearLayout llFingerprintAuth;
|
||||||
|
@ -195,6 +206,7 @@ public class GenerateFragment extends Fragment {
|
||||||
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_UNSPECIFIED);
|
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_UNSPECIFIED);
|
||||||
break;
|
break;
|
||||||
case TYPE_LEDGER:
|
case TYPE_LEDGER:
|
||||||
|
case TYPE_SIDEKICK:
|
||||||
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||||
etWalletPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> {
|
etWalletPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
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() {
|
private boolean checkName() {
|
||||||
String name = etWalletName.getEditText().getText().toString();
|
String name = etWalletName.getEditText().getText().toString();
|
||||||
boolean ok = true;
|
boolean ok = true;
|
||||||
if (name.length() == 0) {
|
if (name.isEmpty()) {
|
||||||
etWalletName.setError(getString(R.string.generate_wallet_name));
|
etWalletName.setError(getString(R.string.generate_wallet_name));
|
||||||
ok = false;
|
ok = false;
|
||||||
} else if (name.charAt(0) == '.') {
|
} else if (name.charAt(0) == '.') {
|
||||||
|
@ -450,11 +462,12 @@ public class GenerateFragment extends Fragment {
|
||||||
activityCallback.onGenerate(name, crazyPass, seed, offset, height);
|
activityCallback.onGenerate(name, crazyPass, seed, offset, height);
|
||||||
break;
|
break;
|
||||||
case TYPE_LEDGER:
|
case TYPE_LEDGER:
|
||||||
|
case TYPE_SIDEKICK:
|
||||||
bGenerate.setEnabled(false);
|
bGenerate.setEnabled(false);
|
||||||
if (fingerprintAuthAllowed) {
|
if (fingerprintAuthAllowed) {
|
||||||
KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password);
|
KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password);
|
||||||
}
|
}
|
||||||
activityCallback.onGenerateLedger(name, crazyPass, height);
|
activityCallback.onGenerateDevice(getDeviceType(type), name, crazyPass, height);
|
||||||
break;
|
break;
|
||||||
case TYPE_KEY:
|
case TYPE_KEY:
|
||||||
case TYPE_VIEWONLY:
|
case TYPE_VIEWONLY:
|
||||||
|
@ -498,6 +511,8 @@ public class GenerateFragment extends Fragment {
|
||||||
return getString(R.string.generate_wallet_type_seed);
|
return getString(R.string.generate_wallet_type_seed);
|
||||||
case TYPE_LEDGER:
|
case TYPE_LEDGER:
|
||||||
return getString(R.string.generate_wallet_type_ledger);
|
return getString(R.string.generate_wallet_type_ledger);
|
||||||
|
case TYPE_SIDEKICK:
|
||||||
|
return getString(R.string.generate_wallet_type_sidekick);
|
||||||
case TYPE_VIEWONLY:
|
case TYPE_VIEWONLY:
|
||||||
return getString(R.string.generate_wallet_type_view);
|
return getString(R.string.generate_wallet_type_view);
|
||||||
default:
|
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 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);
|
void setTitle(String title);
|
||||||
|
|
||||||
|
@ -555,6 +570,9 @@ public class GenerateFragment extends Fragment {
|
||||||
case TYPE_LEDGER:
|
case TYPE_LEDGER:
|
||||||
inflater.inflate(R.menu.create_wallet_ledger, menu);
|
inflater.inflate(R.menu.create_wallet_ledger, menu);
|
||||||
break;
|
break;
|
||||||
|
case TYPE_SIDEKICK:
|
||||||
|
inflater.inflate(R.menu.create_wallet_sidekick, menu);
|
||||||
|
break;
|
||||||
case TYPE_VIEWONLY:
|
case TYPE_VIEWONLY:
|
||||||
inflater.inflate(R.menu.create_wallet_view, menu);
|
inflater.inflate(R.menu.create_wallet_view, menu);
|
||||||
break;
|
break;
|
||||||
|
@ -581,13 +599,11 @@ public class GenerateFragment extends Fragment {
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(getString(R.string.label_ok), null)
|
.setPositiveButton(getString(R.string.label_ok), null)
|
||||||
.setNegativeButton(getString(R.string.label_cancel),
|
.setNegativeButton(getString(R.string.label_cancel),
|
||||||
new DialogInterface.OnClickListener() {
|
(dialog, id) -> {
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
|
||||||
Helper.hideKeyboardAlways(activity);
|
Helper.hideKeyboardAlways(activity);
|
||||||
etWalletMnemonic.getEditText().getText().clear();
|
etWalletMnemonic.getEditText().getText().clear();
|
||||||
dialog.cancel();
|
dialog.cancel();
|
||||||
ledgerDialog = null;
|
ledgerDialog = null;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ledgerDialog = alertDialogBuilder.create();
|
ledgerDialog = alertDialogBuilder.create();
|
||||||
|
|
|
@ -254,7 +254,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||||
showProgress();
|
showProgress();
|
||||||
if ((walletPath != null)
|
if ((walletPath != null)
|
||||||
&& (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword())
|
&& (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword())
|
||||||
== Wallet.Device.Device_Ledger)
|
== Wallet.Device.Ledger)
|
||||||
&& (progressCallback != null)) {
|
&& (progressCallback != null)) {
|
||||||
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
|
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
|
||||||
dialogOpened = true;
|
dialogOpened = true;
|
||||||
|
@ -286,10 +286,11 @@ public class GenerateReviewFragment extends Fragment {
|
||||||
height = wallet.getRestoreHeight();
|
height = wallet.getRestoreHeight();
|
||||||
seed = wallet.getSeed(getSeedOffset());
|
seed = wallet.getSeed(getSeedOffset());
|
||||||
switch (wallet.getDeviceType()) {
|
switch (wallet.getDeviceType()) {
|
||||||
case Device_Ledger:
|
case Ledger:
|
||||||
viewKey = Ledger.Key();
|
viewKey = Ledger.Key();
|
||||||
break;
|
break;
|
||||||
case Device_Software:
|
case Software:
|
||||||
|
case Sidekick:
|
||||||
viewKey = wallet.getSecretViewKey();
|
viewKey = wallet.getSecretViewKey();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -55,6 +55,7 @@ import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
|
||||||
import com.m2049r.xmrwallet.model.NetworkType;
|
import com.m2049r.xmrwallet.model.NetworkType;
|
||||||
import com.m2049r.xmrwallet.model.Wallet;
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
import com.m2049r.xmrwallet.model.WalletManager;
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
|
import com.m2049r.xmrwallet.service.BluetoothService;
|
||||||
import com.m2049r.xmrwallet.service.WalletService;
|
import com.m2049r.xmrwallet.service.WalletService;
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||||
|
@ -76,13 +77,12 @@ import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class LoginActivity extends BaseActivity
|
public class LoginActivity extends BaseActivity
|
||||||
implements LoginFragment.Listener, GenerateFragment.Listener,
|
implements LoginFragment.Listener, GenerateFragment.Listener,
|
||||||
GenerateReviewFragment.Listener, GenerateReviewFragment.AcceptListener,
|
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 GENERATE_STACK = "gen";
|
||||||
|
|
||||||
private static final String NODES_PREFS_NAME = "nodes";
|
private static final String NODES_PREFS_NAME = "nodes";
|
||||||
|
@ -278,8 +278,15 @@ public class LoginActivity extends BaseActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasLedger() {
|
public boolean hasDevice(Wallet.Device type) {
|
||||||
|
switch (type) {
|
||||||
|
case Ledger:
|
||||||
return Ledger.isConnected();
|
return Ledger.isConnected();
|
||||||
|
case Sidekick:
|
||||||
|
return BluetoothService.IsConnected();
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -311,8 +318,8 @@ public class LoginActivity extends BaseActivity
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isNetworkAvailable())
|
||||||
loadFavouritesWithNetwork();
|
loadFavouritesWithNetwork();
|
||||||
|
|
||||||
LegacyStorageHelper.migrateWallets(this);
|
LegacyStorageHelper.migrateWallets(this);
|
||||||
|
|
||||||
if (savedInstanceState == null) startLoginFragment();
|
if (savedInstanceState == null) startLoginFragment();
|
||||||
|
@ -334,7 +341,7 @@ public class LoginActivity extends BaseActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onWalletSelected(String walletName, boolean streetmode) {
|
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();
|
Toast.makeText(this, getString(R.string.prompt_daemon_missing), Toast.LENGTH_SHORT).show();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -682,6 +689,7 @@ public class LoginActivity extends BaseActivity
|
||||||
dismissProgressDialog();
|
dismissProgressDialog();
|
||||||
unregisterDetachReceiver();
|
unregisterDetachReceiver();
|
||||||
Ledger.disconnect();
|
Ledger.disconnect();
|
||||||
|
BluetoothService.Stop();
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -695,6 +703,7 @@ public class LoginActivity extends BaseActivity
|
||||||
new AsyncWaitForService().execute();
|
new AsyncWaitForService().execute();
|
||||||
}
|
}
|
||||||
if (!Ledger.isConnected()) attachLedger();
|
if (!Ledger.isConnected()) attachLedger();
|
||||||
|
if (BluetoothService.IsConnected()) onLedgerAction(); //TODO sidekick & show sidekick fab
|
||||||
registerTor();
|
registerTor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -727,14 +736,14 @@ public class LoginActivity extends BaseActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void startWallet(String walletName, String walletPassword,
|
void startWallet(String walletName, String walletPassword, boolean fingerprintUsed, StartMode mode) {
|
||||||
boolean fingerprintUsed, boolean streetmode) {
|
|
||||||
Timber.d("startWallet()");
|
Timber.d("startWallet()");
|
||||||
|
|
||||||
Intent intent = new Intent(getApplicationContext(), WalletActivity.class);
|
Intent intent = new Intent(getApplicationContext(), WalletActivity.class);
|
||||||
intent.putExtra(WalletActivity.REQUEST_ID, walletName);
|
intent.putExtra(WalletActivity.REQUEST_ID, walletName);
|
||||||
intent.putExtra(WalletActivity.REQUEST_PW, walletPassword);
|
intent.putExtra(WalletActivity.REQUEST_PW, walletPassword);
|
||||||
intent.putExtra(WalletActivity.REQUEST_FINGERPRINT_USED, fingerprintUsed);
|
intent.putExtra(WalletActivity.REQUEST_FINGERPRINT_USED, fingerprintUsed);
|
||||||
intent.putExtra(WalletActivity.REQUEST_STREETMODE, streetmode);
|
intent.putExtra(WalletActivity.REQUEST_STREETMODE, mode == StartMode.Street);
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
intent.putExtra(WalletActivity.REQUEST_URI, uri);
|
intent.putExtra(WalletActivity.REQUEST_URI, uri);
|
||||||
uri = null; // use only once
|
uri = null; // use only once
|
||||||
|
@ -783,6 +792,11 @@ public class LoginActivity extends BaseActivity
|
||||||
Timber.d("SettingsFragment placed");
|
Timber.d("SettingsFragment placed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void startSidekickConnectFragment() {
|
||||||
|
replaceFragment(new SidekickConnectFragment(), null, null);
|
||||||
|
Timber.d("SidekickConnectFragment placed");
|
||||||
|
}
|
||||||
|
|
||||||
void replaceFragment(Fragment newFragment, String stackName, Bundle extras) {
|
void replaceFragment(Fragment newFragment, String stackName, Bundle extras) {
|
||||||
if (extras != null) {
|
if (extras != null) {
|
||||||
newFragment.setArguments(extras);
|
newFragment.setArguments(extras);
|
||||||
|
@ -821,7 +835,7 @@ public class LoginActivity extends BaseActivity
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
acquireWakeLock();
|
acquireWakeLock();
|
||||||
if (walletCreator.isLedger()) {
|
if (walletCreator.device() == Wallet.Device.Ledger) {
|
||||||
showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
|
showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
|
||||||
} else {
|
} else {
|
||||||
showProgressDialog(R.string.generate_wallet_creating);
|
showProgressDialog(R.string.generate_wallet_creating);
|
||||||
|
@ -890,8 +904,7 @@ public class LoginActivity extends BaseActivity
|
||||||
interface WalletCreator {
|
interface WalletCreator {
|
||||||
boolean createWallet(File aFile, String password);
|
boolean createWallet(File aFile, String password);
|
||||||
|
|
||||||
boolean isLedger();
|
Wallet.Device device();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean checkAndCloseWallet(Wallet aWallet) {
|
boolean checkAndCloseWallet(Wallet aWallet) {
|
||||||
|
@ -909,8 +922,8 @@ public class LoginActivity extends BaseActivity
|
||||||
createWallet(name, password,
|
createWallet(name, password,
|
||||||
new WalletCreator() {
|
new WalletCreator() {
|
||||||
@Override
|
@Override
|
||||||
public boolean isLedger() {
|
public Wallet.Device device() {
|
||||||
return false;
|
return Wallet.Device.Software;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -933,8 +946,8 @@ public class LoginActivity extends BaseActivity
|
||||||
createWallet(name, password,
|
createWallet(name, password,
|
||||||
new WalletCreator() {
|
new WalletCreator() {
|
||||||
@Override
|
@Override
|
||||||
public boolean isLedger() {
|
public Wallet.Device device() {
|
||||||
return false;
|
return Wallet.Device.Software;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -947,20 +960,19 @@ public class LoginActivity extends BaseActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onGenerateLedger(final String name, final String password,
|
public void onGenerateDevice(final Wallet.Device device, final String name, final String password, long restoreHeight) {
|
||||||
final long restoreHeight) {
|
|
||||||
createWallet(name, password,
|
createWallet(name, password,
|
||||||
new WalletCreator() {
|
new WalletCreator() {
|
||||||
@Override
|
@Override
|
||||||
public boolean isLedger() {
|
public Wallet.Device device() {
|
||||||
return true;
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean createWallet(File aFile, String password) {
|
public boolean createWallet(File aFile, String password) {
|
||||||
Wallet newWallet = WalletManager.getInstance()
|
Wallet newWallet = WalletManager.getInstance()
|
||||||
.createWalletFromDevice(aFile, password,
|
.createWalletFromDevice(aFile, password,
|
||||||
restoreHeight, "Ledger");
|
restoreHeight, device);
|
||||||
return checkAndCloseWallet(newWallet);
|
return checkAndCloseWallet(newWallet);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -973,8 +985,8 @@ public class LoginActivity extends BaseActivity
|
||||||
createWallet(name, password,
|
createWallet(name, password,
|
||||||
new WalletCreator() {
|
new WalletCreator() {
|
||||||
@Override
|
@Override
|
||||||
public boolean isLedger() {
|
public Wallet.Device device() {
|
||||||
return false;
|
return Wallet.Device.Software;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1105,6 +1117,9 @@ public class LoginActivity extends BaseActivity
|
||||||
} else if (id == R.id.action_create_help_ledger) {
|
} else if (id == R.id.action_create_help_ledger) {
|
||||||
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_ledger);
|
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_ledger);
|
||||||
return true;
|
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) {
|
} else if (id == R.id.action_details_help) {
|
||||||
HelpFragment.display(getSupportFragmentManager(), R.string.help_details);
|
HelpFragment.display(getSupportFragmentManager(), R.string.help_details);
|
||||||
return true;
|
return true;
|
||||||
|
@ -1117,6 +1132,9 @@ public class LoginActivity extends BaseActivity
|
||||||
} else if (id == R.id.action_help_node) {
|
} else if (id == R.id.action_help_node) {
|
||||||
HelpFragment.display(getSupportFragmentManager(), R.string.help_node);
|
HelpFragment.display(getSupportFragmentManager(), R.string.help_node);
|
||||||
return true;
|
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) {
|
} else if (id == R.id.action_default_nodes) {
|
||||||
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||||
if ((WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) &&
|
if ((WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) &&
|
||||||
|
@ -1124,6 +1142,9 @@ public class LoginActivity extends BaseActivity
|
||||||
((NodeFragment) f).restoreDefaultNodes();
|
((NodeFragment) f).restoreDefaultNodes();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
} else if (id == R.id.action_sidekick) {
|
||||||
|
checkBtPermissions();
|
||||||
|
return true;
|
||||||
} else if (id == R.id.action_ledger_seed) {
|
} else if (id == R.id.action_ledger_seed) {
|
||||||
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||||
if (f instanceof GenerateFragment) {
|
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
|
// an AsyncTask which tests the node before trying to open the wallet
|
||||||
private class AsyncOpenWallet extends AsyncTask<Void, Void, Boolean> {
|
private class AsyncOpenWallet extends AsyncTask<Void, Void, Boolean> {
|
||||||
final static int OK = 0;
|
final static int OK = 0;
|
||||||
|
@ -1172,7 +1198,7 @@ public class LoginActivity extends BaseActivity
|
||||||
if (result) {
|
if (result) {
|
||||||
Timber.d("selected wallet is .%s.", node.getName());
|
Timber.d("selected wallet is .%s.", node.getName());
|
||||||
// now it's getting real, onValidateFields if wallet exists
|
// now it's getting real, onValidateFields if wallet exists
|
||||||
promptAndStart(walletName, streetmode);
|
promptAndStart(walletName, streetmode ? StartMode.Street : StartMode.Normal);
|
||||||
} else {
|
} else {
|
||||||
if (node.getResponseCode() == 0) { // IOException
|
if (node.getResponseCode() == 0) { // IOException
|
||||||
Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_node_invalid), Toast.LENGTH_LONG).show();
|
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) {
|
private boolean checkDevice(String walletName, String password) {
|
||||||
String keyPath = new File(Helper.getWalletRoot(LoginActivity.this),
|
String keyPath = new File(Helper.getWalletRoot(this), walletName + ".keys").getAbsolutePath();
|
||||||
walletName + ".keys").getAbsolutePath();
|
|
||||||
// check if we need connected hardware
|
// check if we need connected hardware
|
||||||
Wallet.Device device = WalletManager.getInstance().queryWalletDevice(keyPath, password);
|
final Wallet.Device device = WalletManager.getInstance().queryWalletDevice(keyPath, password);
|
||||||
if (device == Wallet.Device.Device_Ledger) {
|
if (!hasDevice(device)) {
|
||||||
if (!hasLedger()) {
|
if (device == Wallet.Device.Ledger) {
|
||||||
toast(R.string.open_wallet_ledger_missing);
|
toast(R.string.open_wallet_ledger_missing);
|
||||||
} else {
|
} else if (device == Wallet.Device.Sidekick) {
|
||||||
return true;
|
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);
|
File walletFile = Helper.getWalletFile(this, walletName);
|
||||||
if (WalletManager.getInstance().walletExists(walletFile)) {
|
if (WalletManager.getInstance().walletExists(walletFile)) {
|
||||||
Helper.promptPassword(LoginActivity.this, walletName, false,
|
Helper.promptPassword(LoginActivity.this, walletName, false,
|
||||||
|
@ -1210,7 +1236,7 @@ public class LoginActivity extends BaseActivity
|
||||||
@Override
|
@Override
|
||||||
public void act(String walletName, String password, boolean fingerprintUsed) {
|
public void act(String walletName, String password, boolean fingerprintUsed) {
|
||||||
if (checkDevice(walletName, password))
|
if (checkDevice(walletName, password))
|
||||||
startWallet(walletName, password, fingerprintUsed, streetmode);
|
startWallet(walletName, password, fingerprintUsed, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1378,6 +1404,28 @@ public class LoginActivity extends BaseActivity
|
||||||
return usbManager;
|
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
|
// Tor (Orbot) stuff
|
||||||
//
|
//
|
||||||
|
|
|
@ -46,6 +46,7 @@ import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||||
import com.m2049r.xmrwallet.data.NodeInfo;
|
import com.m2049r.xmrwallet.data.NodeInfo;
|
||||||
import com.m2049r.xmrwallet.dialog.HelpFragment;
|
import com.m2049r.xmrwallet.dialog.HelpFragment;
|
||||||
import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
|
import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
import com.m2049r.xmrwallet.model.WalletManager;
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||||
|
@ -61,6 +62,7 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInteractionListener,
|
public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInteractionListener,
|
||||||
|
@ -115,7 +117,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||||
|
|
||||||
Set<NodeInfo> getOrPopulateFavourites();
|
Set<NodeInfo> getOrPopulateFavourites();
|
||||||
|
|
||||||
boolean hasLedger();
|
boolean hasDevice(Wallet.Device type);
|
||||||
|
|
||||||
|
boolean isNetworkAvailable();
|
||||||
|
|
||||||
void runOnNetCipher(Runnable runnable);
|
void runOnNetCipher(Runnable runnable);
|
||||||
}
|
}
|
||||||
|
@ -146,7 +150,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||||
activityCallback.setToolbarButton(Toolbar.BUTTON_SETTINGS);
|
activityCallback.setToolbarButton(Toolbar.BUTTON_SETTINGS);
|
||||||
activityCallback.showNet();
|
activityCallback.showNet();
|
||||||
showNetwork();
|
showNetwork();
|
||||||
//activityCallback.runOnNetCipher(this::pingSelectedNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
|
private OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
|
||||||
|
@ -172,6 +175,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||||
fabSeed = view.findViewById(R.id.fabSeed);
|
fabSeed = view.findViewById(R.id.fabSeed);
|
||||||
fabImport = view.findViewById(R.id.fabImport);
|
fabImport = view.findViewById(R.id.fabImport);
|
||||||
fabLedger = view.findViewById(R.id.fabLedger);
|
fabLedger = view.findViewById(R.id.fabLedger);
|
||||||
|
fabSidekick = view.findViewById(R.id.fabSidekick);
|
||||||
|
|
||||||
fabNewL = view.findViewById(R.id.fabNewL);
|
fabNewL = view.findViewById(R.id.fabNewL);
|
||||||
fabViewL = view.findViewById(R.id.fabViewL);
|
fabViewL = view.findViewById(R.id.fabViewL);
|
||||||
|
@ -179,6 +183,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||||
fabSeedL = view.findViewById(R.id.fabSeedL);
|
fabSeedL = view.findViewById(R.id.fabSeedL);
|
||||||
fabImportL = view.findViewById(R.id.fabImportL);
|
fabImportL = view.findViewById(R.id.fabImportL);
|
||||||
fabLedgerL = view.findViewById(R.id.fabLedgerL);
|
fabLedgerL = view.findViewById(R.id.fabLedgerL);
|
||||||
|
fabSidekickL = view.findViewById(R.id.fabSidekickL);
|
||||||
|
|
||||||
fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse);
|
fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse);
|
||||||
fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen);
|
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);
|
fabSeed.setOnClickListener(this);
|
||||||
fabImport.setOnClickListener(this);
|
fabImport.setOnClickListener(this);
|
||||||
fabLedger.setOnClickListener(this);
|
fabLedger.setOnClickListener(this);
|
||||||
|
fabSidekick.setOnClickListener(this);
|
||||||
fabScreen.setOnClickListener(this);
|
fabScreen.setOnClickListener(this);
|
||||||
|
|
||||||
RecyclerView recyclerView = view.findViewById(R.id.list);
|
RecyclerView recyclerView = view.findViewById(R.id.list);
|
||||||
|
@ -202,6 +208,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
ViewGroup llNotice = view.findViewById(R.id.llNotice);
|
ViewGroup llNotice = view.findViewById(R.id.llNotice);
|
||||||
|
|
||||||
Notice.showAll(llNotice, ".*_login");
|
Notice.showAll(llNotice, ".*_login");
|
||||||
|
|
||||||
view.findViewById(R.id.llNode).setOnClickListener(v -> startNodePrefs());
|
view.findViewById(R.id.llNode).setOnClickListener(v -> startNodePrefs());
|
||||||
|
@ -304,17 +311,14 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
private boolean fabOpen = false;
|
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 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_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen;
|
||||||
private Animation fab_pulse;
|
private Animation fab_pulse;
|
||||||
|
|
||||||
public boolean isFabOpen() {
|
|
||||||
return fabOpen;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setFabOpen(boolean value) {
|
private void setFabOpen(boolean value) {
|
||||||
fabOpen = value;
|
fabOpen = value;
|
||||||
onBackPressedCallback.setEnabled(value);
|
onBackPressedCallback.setEnabled(value);
|
||||||
|
@ -328,6 +332,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||||
if (fabLedgerL.getVisibility() == View.VISIBLE) {
|
if (fabLedgerL.getVisibility() == View.VISIBLE) {
|
||||||
fabLedgerL.startAnimation(fab_close);
|
fabLedgerL.startAnimation(fab_close);
|
||||||
fabLedger.setClickable(false);
|
fabLedger.setClickable(false);
|
||||||
|
} else if (fabSidekickL.getVisibility() == View.VISIBLE) {
|
||||||
|
fabSidekickL.startAnimation(fab_close);
|
||||||
|
fabSidekick.setClickable(false);
|
||||||
} else {
|
} else {
|
||||||
fabNewL.startAnimation(fab_close);
|
fabNewL.startAnimation(fab_close);
|
||||||
fabNew.setClickable(false);
|
fabNew.setClickable(false);
|
||||||
|
@ -345,17 +352,25 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||||
fabScreen.setClickable(true);
|
fabScreen.setClickable(true);
|
||||||
fabScreen.startAnimation(fab_open_screen);
|
fabScreen.startAnimation(fab_open_screen);
|
||||||
fab.startAnimation(rotate_forward);
|
fab.startAnimation(rotate_forward);
|
||||||
if (activityCallback.hasLedger()) {
|
if ((activityCallback.hasDevice(Wallet.Device.Ledger)
|
||||||
fabLedgerL.setVisibility(View.VISIBLE);
|
|| activityCallback.hasDevice(Wallet.Device.Sidekick))) {
|
||||||
fabNewL.setVisibility(View.GONE);
|
fabNewL.setVisibility(View.GONE);
|
||||||
fabViewL.setVisibility(View.GONE);
|
fabViewL.setVisibility(View.GONE);
|
||||||
fabKeyL.setVisibility(View.GONE);
|
fabKeyL.setVisibility(View.GONE);
|
||||||
fabSeedL.setVisibility(View.GONE);
|
fabSeedL.setVisibility(View.GONE);
|
||||||
fabImportL.setVisibility(View.GONE);
|
fabImportL.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
if (activityCallback.hasDevice(Wallet.Device.Ledger)) {
|
||||||
|
fabLedgerL.setVisibility(View.VISIBLE);
|
||||||
fabLedgerL.startAnimation(fab_open);
|
fabLedgerL.startAnimation(fab_open);
|
||||||
fabLedger.setClickable(true);
|
fabLedger.setClickable(true);
|
||||||
|
} else { // Sidekick
|
||||||
|
fabSidekickL.setVisibility(View.VISIBLE);
|
||||||
|
fabSidekickL.startAnimation(fab_open);
|
||||||
|
fabSidekick.setClickable(true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
fabSidekickL.setVisibility(View.GONE);
|
||||||
fabLedgerL.setVisibility(View.GONE);
|
fabLedgerL.setVisibility(View.GONE);
|
||||||
fabNewL.setVisibility(View.VISIBLE);
|
fabNewL.setVisibility(View.VISIBLE);
|
||||||
fabViewL.setVisibility(View.VISIBLE);
|
fabViewL.setVisibility(View.VISIBLE);
|
||||||
|
@ -404,6 +419,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||||
Timber.d("FAB_LEDGER");
|
Timber.d("FAB_LEDGER");
|
||||||
animateFAB();
|
animateFAB();
|
||||||
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
|
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) {
|
} else if (id == R.id.fabScreen) {
|
||||||
animateFAB();
|
animateFAB();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<BluetoothInfo> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -195,7 +195,7 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
super.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);
|
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS);
|
||||||
dialogOpened = true;
|
dialogOpened = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2017 m2049r
|
* Copyright (c) 2017-2024 m2049r
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.drawerlayout.widget.DrawerLayout;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
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_FINGERPRINT_USED = "fingerprint";
|
||||||
public static final String REQUEST_STREETMODE = "streetmode";
|
public static final String REQUEST_STREETMODE = "streetmode";
|
||||||
public static final String REQUEST_URI = "uri";
|
public static final String REQUEST_URI = "uri";
|
||||||
|
public static final String REQUEST_SIDEKICK = "sidekick";
|
||||||
|
|
||||||
private NavigationView accountsView;
|
private NavigationView accountsView;
|
||||||
private DrawerLayout drawer;
|
private DrawerLayout drawer;
|
||||||
|
@ -202,19 +204,9 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||||
return getWallet().getSubaddress(major, minor);
|
return getWallet().getSubaddress(major, minor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startWalletService() {
|
private void startWalletService(String walletId) {
|
||||||
Bundle extras = getIntent().getExtras();
|
|
||||||
if (extras != null) {
|
|
||||||
acquireWakeLock();
|
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);
|
connectWalletService(walletId, password);
|
||||||
} else {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopWalletService() {
|
private void stopWalletService() {
|
||||||
|
@ -330,7 +322,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void onWalletChangePassword() {
|
public void onWalletChangePassword() {
|
||||||
try {
|
try {
|
||||||
GenerateReviewFragment detailsFragment = (GenerateReviewFragment) getCurrentFragment();
|
GenerateReviewFragment detailsFragment = (GenerateReviewFragment) getCurrentFragment();
|
||||||
|
@ -356,14 +347,21 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||||
return;
|
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);
|
setContentView(R.layout.activity_wallet);
|
||||||
toolbar = findViewById(R.id.toolbar);
|
toolbar = findViewById(R.id.toolbar);
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||||
|
|
||||||
toolbar.setOnButtonListener(new Toolbar.OnButtonListener() {
|
toolbar.setOnButtonListener(type -> {
|
||||||
@Override
|
|
||||||
public void onButton(int type) {
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Toolbar.BUTTON_BACK:
|
case Toolbar.BUTTON_BACK:
|
||||||
onDisposeRequest();
|
onDisposeRequest();
|
||||||
|
@ -383,7 +381,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||||
default:
|
default:
|
||||||
Timber.e("Button " + type + "pressed - how can this be?");
|
Timber.e("Button " + type + "pressed - how can this be?");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
drawer = findViewById(R.id.drawer_layout);
|
drawer = findViewById(R.id.drawer_layout);
|
||||||
|
@ -398,11 +395,16 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||||
showNet();
|
showNet();
|
||||||
|
|
||||||
Fragment walletFragment = new WalletFragment();
|
Fragment walletFragment = new WalletFragment();
|
||||||
|
final FragmentTransaction tx =
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.add(R.id.fragment_container, walletFragment, WalletFragment.class.getName()).commit();
|
.add(R.id.fragment_container, walletFragment, WalletFragment.class.getName());
|
||||||
Timber.d("fragment added");
|
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.");
|
Timber.d("onCreate() done.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -616,7 +618,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWalletOpen(final Wallet.Device device) {
|
public void onWalletOpen(final Wallet.Device device) {
|
||||||
if (device == Wallet.Device.Device_Ledger) {
|
if (device == Wallet.Device.Ledger) {
|
||||||
runOnUiThread(() -> showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE));
|
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);
|
intent.putExtra(WalletService.REQUEST_CMD_TX_TAG, tag);
|
||||||
startService(intent);
|
startService(intent);
|
||||||
Timber.d("CREATE TX request sent");
|
Timber.d("CREATE TX request sent");
|
||||||
if (getWallet().getDeviceType() == Wallet.Device.Device_Ledger)
|
if (getWallet().getDeviceType() == Wallet.Device.Ledger)
|
||||||
showLedgerProgressDialog(LedgerProgressDialog.TYPE_SEND);
|
showLedgerProgressDialog(LedgerProgressDialog.TYPE_SEND);
|
||||||
} else {
|
} else {
|
||||||
Timber.e("Service not bound");
|
Timber.e("Service not bound");
|
||||||
|
@ -1132,11 +1134,12 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
switch (getWallet().getDeviceType()) {
|
switch (getWallet().getDeviceType()) {
|
||||||
case Device_Ledger:
|
case Ledger:
|
||||||
showLedgerProgressDialog(LedgerProgressDialog.TYPE_ACCOUNT);
|
showLedgerProgressDialog(LedgerProgressDialog.TYPE_ACCOUNT);
|
||||||
dialogOpened = true;
|
dialogOpened = true;
|
||||||
break;
|
break;
|
||||||
case Device_Software:
|
case Software:
|
||||||
|
case Sidekick:
|
||||||
showProgressDialog(R.string.accounts_progress_new);
|
showProgressDialog(R.string.accounts_progress_new);
|
||||||
dialogOpened = true;
|
dialogOpened = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -22,27 +22,20 @@ import android.content.res.Configuration;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
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.model.NetworkType;
|
||||||
import com.m2049r.xmrwallet.util.LocaleHelper;
|
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||||
import com.m2049r.xmrwallet.util.NetCipherHelper;
|
import com.m2049r.xmrwallet.util.NetCipherHelper;
|
||||||
import com.m2049r.xmrwallet.util.NightmodeHelper;
|
import com.m2049r.xmrwallet.util.NightmodeHelper;
|
||||||
import com.m2049r.xmrwallet.util.ServiceHelper;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class XmrWalletApplication extends Application {
|
public class XmrWalletApplication extends Application {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@OptIn(markerClass = FragmentStateManagerControl.class)
|
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
FragmentManager.enableNewStateManager(false);
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Timber.plant(new Timber.DebugTree());
|
Timber.plant(new Timber.DebugTree());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<BluetoothInfo> NameComparator = (o1, o2) -> o1.name.compareTo(o2.name);
|
||||||
|
}
|
|
@ -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<BluetoothInfoAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
public interface OnInteractionListener {
|
||||||
|
void onInteraction(View view, BluetoothInfo item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<BluetoothInfo> items = new ArrayList<>();
|
||||||
|
private final OnInteractionListener listener;
|
||||||
|
|
||||||
|
public BluetoothInfoAdapter(OnInteractionListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BluetoothInfoDiff extends DiffCallback<BluetoothInfo> {
|
||||||
|
|
||||||
|
public BluetoothInfoDiff(List<BluetoothInfo> oldList, List<BluetoothInfo> 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<BluetoothInfo> newItems = new ArrayList<>(items);
|
||||||
|
if (!items.contains(item))
|
||||||
|
newItems.add(item);
|
||||||
|
setItems(newItems); // in case the nodeinfo has changed
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItems(Collection<BluetoothInfo> newItemsCollection) {
|
||||||
|
List<BluetoothInfo> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean needsTransactionUpdateOnNewBlock() {
|
public boolean needsTransactionUpdateOnNewBlock() {
|
||||||
return (infoItems.size() > 0) && !infoItems.get(0).isConfirmed();
|
return (!infoItems.isEmpty()) && !infoItems.get(0).isConfirmed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TransactionInfoDiff extends DiffCallback<TransactionInfo> {
|
private static class TransactionInfoDiff extends DiffCallback<TransactionInfo> {
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
package com.m2049r.xmrwallet.ledger;
|
||||||
|
|
||||||
|
public interface Hardware {
|
||||||
|
}
|
|
@ -35,12 +35,11 @@ import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class Ledger {
|
public class Ledger implements Hardware {
|
||||||
static final public boolean ENABLED = true;
|
static final public boolean ENABLED = true;
|
||||||
// 5:20 is same as wallet2.cpp::restore()
|
// 5:20 is same as wallet2.cpp::restore()
|
||||||
static public final int LOOKAHEAD_ACCOUNTS = 5;
|
static public final int LOOKAHEAD_ACCOUNTS = 5;
|
||||||
static public final int LOOKAHEAD_SUBADDRESSES = 20;
|
static public final int LOOKAHEAD_SUBADDRESSES = 20;
|
||||||
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
|
|
||||||
|
|
||||||
private static final byte PROTOCOL_VERSION = 0x03;
|
private static final byte PROTOCOL_VERSION = 0x03;
|
||||||
public static final int SW_OK = 0x9000;
|
public static final int SW_OK = 0x9000;
|
||||||
|
|
|
@ -111,7 +111,11 @@ public class Wallet {
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
public enum Device {
|
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 accountLookahead;
|
||||||
private final int subaddressLookahead;
|
private final int subaddressLookahead;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ package com.m2049r.xmrwallet.model;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.XmrWalletApplication;
|
import com.m2049r.xmrwallet.XmrWalletApplication;
|
||||||
import com.m2049r.xmrwallet.data.Node;
|
import com.m2049r.xmrwallet.data.Node;
|
||||||
import com.m2049r.xmrwallet.ledger.Ledger;
|
|
||||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -161,10 +160,12 @@ public class WalletManager {
|
||||||
String spendKeyString);
|
String spendKeyString);
|
||||||
|
|
||||||
public Wallet createWalletFromDevice(File aFile, String password, long restoreHeight,
|
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,
|
long walletHandle = createWalletFromDeviceJ(aFile.getAbsolutePath(), password,
|
||||||
getNetworkType().getValue(), deviceName, restoreHeight,
|
getNetworkType().getValue(), device.name(), restoreHeight,
|
||||||
Ledger.SUBADDRESS_LOOKAHEAD);
|
lookahead);
|
||||||
Wallet wallet = new Wallet(walletHandle);
|
Wallet wallet = new Wallet(walletHandle);
|
||||||
manageWallet(wallet);
|
manageWallet(wallet);
|
||||||
return wallet;
|
return wallet;
|
||||||
|
|
|
@ -19,6 +19,8 @@ package com.m2049r.xmrwallet.onboarding;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
|
@ -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.
|
||||||
|
* <p>
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ import android.os.Process;
|
||||||
/**
|
/**
|
||||||
* Handy class for starting a new thread that has a looper. The looper can then be
|
* 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.
|
* 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 {
|
public class MoneroHandlerThread extends Thread {
|
||||||
// from src/cryptonote_config.h
|
// from src/cryptonote_config.h
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<group android:scaleX="0.9961111"
|
||||||
|
android:scaleY="0.9961111"
|
||||||
|
android:translateX="0.21"
|
||||||
|
android:translateY="0.21">
|
||||||
|
<path
|
||||||
|
android:pathData="M0,0h108v108h-108z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:startX="54"
|
||||||
|
android:startY="41.11"
|
||||||
|
android:endX="54"
|
||||||
|
android:endY="85.99"
|
||||||
|
android:type="linear">
|
||||||
|
<item android:offset="0" android:color="#FFF26222"/>
|
||||||
|
<item android:offset="1" android:color="#FFED1A5B"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:pathData="M62.76,39.37a11.13,11.13 0,0 0,-8.86 4.39,11.13 11.13,0 0,0 -15.42,-2.25V40H33.89V67.87h4.59V50.53a6.57,6.57 0,1 1,13.13 0V67.87H56.2V50.53a6.57,6.57 0,1 1,13.13 0V67.87a6.57,6.57 0,0 1,-6.57 6.56V79A11.17,11.17 0,0 0,73.92 67.87V49.28h0A11.24,11.24 0,0 0,62.76 39.37Z"
|
||||||
|
android:fillColor="#fff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M71.62,33.07m-2.38,0a2.38,2.38 0,1 1,4.76 0a2.38,2.38 0,1 1,-4.76 0"
|
||||||
|
android:fillColor="#fff"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M7,12l-2,-2 -2,2 2,2 2,-2zM17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41L11,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59L13,5.83zM14.88,16.29L13,18.17v-3.76l1.88,1.88zM19,10l-2,2 2,2 2,-2 -2,-2z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M13,5.83l1.88,1.88 -1.6,1.6 1.41,1.41 3.02,-3.02L12,2h-1v5.03l2,2v-3.2zM5.41,4L4,5.41 10.59,12 5,17.59 6.41,19 11,14.41V22h1l4.29,-4.29 2.3,2.29L20,18.59 5.41,4zM13,18.17v-3.76l1.88,1.88L13,18.17z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:autoMirrored="true">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M14.24,12.01l2.32,2.32c0.28,-0.72 0.44,-1.51 0.44,-2.33 0,-0.82 -0.16,-1.59 -0.43,-2.31l-2.33,2.32zM19.53,6.71l-1.26,1.26c0.63,1.21 0.98,2.57 0.98,4.02s-0.36,2.82 -0.98,4.02l1.2,1.2c0.97,-1.54 1.54,-3.36 1.54,-5.31 -0.01,-1.89 -0.55,-3.67 -1.48,-5.19zM15.71,7.71L10,2L9,2v7.59L4.41,5 3,6.41 8.59,12 3,17.59 4.41,19 9,14.41L9,22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM11,5.83l1.88,1.88L11,9.59L11,5.83zM12.88,16.29L11,18.17v-3.76l1.88,1.88z"/>
|
||||||
|
</vector>
|
|
@ -1,29 +1,74 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
<vector
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
|
android:width="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path
|
<path android:fillColor="#3DDC84"
|
||||||
android:fillColor="#fff"
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
android:pathData="M0,0h108v108h-108z"
|
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||||
android:strokeWidth="0.3"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#000" />
|
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||||
<path android:pathData="M0,0h108v108h-108z">
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<aapt:attr name="android:fillColor">
|
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||||
<gradient
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:endX="54"
|
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||||
android:endY="85.99"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:startX="54"
|
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||||
android:startY="41.11"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:type="linear">
|
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||||
<item
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:color="#FFF26222"
|
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||||
android:offset="0" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<item
|
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||||
android:color="#FFED1A5B"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:offset="1" />
|
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||||
</gradient>
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
</aapt:attr>
|
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||||
</path>
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
|
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#fff"
|
|
||||||
android:pathData="M62.76,39.37a11.13,11.13 0,0 0,-8.86 4.39,11.13 11.13,0 0,0 -15.42,-2.25V40H33.89V67.87h4.59V50.53a6.57,6.57 0,1 1,13.13 0V67.87H56.2V50.53a6.57,6.57 0,1 1,13.13 0V67.87a6.57,6.57 0,0 1,-6.57 6.56V79A11.17,11.17 0,0 0,73.92 67.87V49.28h-0.07A11.17,11.17 0,0 0,62.76 39.37Z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#fff"
|
|
||||||
android:pathData="M71.62,33.07m-2.38,0a2.38,2.38 0,1 1,4.76 0a2.38,2.38 0,1 1,-4.76 0" />
|
|
||||||
</vector>
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="320"
|
||||||
|
android:viewportHeight="320">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:pathData="M159.56,35.7c-68.48,0 -124,55.52 -124,124s55.52,124 124,124 124,-55.52 124,-124 -55.52,-124 -124,-124ZM97.94,224c-4.25,0 -7.69,-3.44 -7.69,-7.69s3.44,-7.69 7.69,-7.69 7.69,3.44 7.69,7.69 -3.44,7.69 -7.69,7.69ZM190.11,223.74h-70.41v-14.86h70.41c11.7,0 21.23,-9.52 21.23,-21.23s-9.52,-21.23 -21.23,-21.23h-56.09c-19.9,0 -36.08,-16.19 -36.08,-36.08s16.19,-36.08 36.08,-36.08h70.41v14.86h-70.41c-11.7,0 -21.23,9.52 -21.23,21.23s9.52,21.23 21.23,21.23h56.09c19.9,0 36.08,16.19 36.08,36.08s-16.19,36.08 -36.08,36.08ZM226.19,109.38c-4.25,0 -7.69,-3.44 -7.69,-7.69s3.44,-7.69 7.69,-7.69 7.69,3.44 7.69,7.69 -3.44,7.69 -7.69,7.69Z"
|
||||||
|
android:strokeWidth="0" />
|
||||||
|
</vector>
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.m2049r.xmrwallet.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
style="@style/MonerujoToolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?android:attr/actionBarSize"
|
||||||
|
android:background="@drawable/backgound_toolbar_mainnet"
|
||||||
|
android:minHeight="?android:attr/actionBarSize" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/fragment_bluetooth"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/fragment_sidekick"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -20,6 +20,11 @@
|
||||||
android:background="@drawable/backgound_toolbar_mainnet"
|
android:background="@drawable/backgound_toolbar_mainnet"
|
||||||
android:minHeight="?android:attr/actionBarSize" />
|
android:minHeight="?android:attr/actionBarSize" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/fragment_bluetooth"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/fragment_container"
|
android:id="@+id/fragment_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- 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.
|
||||||
|
-->
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
style="@style/MoneroText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/btGroup"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerVertical="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/btIcon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_bluetooth_disabled_24" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/pbConnecting"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="invisible" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btName"
|
||||||
|
style="@style/MoneroText.Large"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_toEndOf="@id/btGroup"
|
||||||
|
tools:text="My Librem 5" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btCode"
|
||||||
|
style="@style/MoneroText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
tools:text="PIN: 0000" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
|
@ -19,6 +19,12 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical" />
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llNetInfo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
style="@style/MoneroLabel.Heading"
|
style="@style/MoneroLabel.Heading"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -58,10 +64,10 @@
|
||||||
android:layout_alignParentStart="true"
|
android:layout_alignParentStart="true"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/tor_enable"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
android:src="@drawable/ic_network_clearnet"
|
android:src="@drawable/ic_network_clearnet" />
|
||||||
android:contentDescription="@string/tor_enable" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -102,11 +108,12 @@
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/node_refresh_hint"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
android:src="@drawable/ic_renew"
|
android:src="@drawable/ic_renew" />
|
||||||
android:contentDescription="@string/node_refresh_hint" />
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/etDummy"
|
android:id="@+id/etDummy"
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<GridLayout
|
||||||
|
android:id="@+id/lights"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:columnCount="5" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/confirmation"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_above="@+id/buttons">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/MoneroText.Large.Accent"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sidekick_confirm_tx" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvFee"
|
||||||
|
style="@style/MoneroText.Unconfirmed"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="Fee 3.827222 XMR" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTransfers"
|
||||||
|
style="@style/MoneroText.Info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
tools:text="1.324243 XMR\n888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/buttons"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/buttonDeny"
|
||||||
|
style="@style/MoneroButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Deny" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/buttonAccept"
|
||||||
|
style="@style/MoneroButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Accept" />
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
</FrameLayout>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/bt_fragment"
|
||||||
|
android:name="com.m2049r.xmrwallet.BluetoothFragment"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:layout="@layout/fragment_bluetooth" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/MoneroLabel.Heading"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/bluetooth_select_label" />
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/pullToRefresh"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/item_bluetooth" />
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:background="@drawable/selector_login">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:src="@drawable/ic_bluetooth_24" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toEndOf="@id/icon"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvName"
|
||||||
|
style="@style/MoneroText.PosAmount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="My Librem 5" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAddress"
|
||||||
|
style="@style/MoneroText.Small"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="AB:CD:EF:01:23:45" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
|
@ -29,8 +29,8 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_marginEnd="8dp"
|
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" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fabNew"
|
android:id="@+id/fabNew"
|
||||||
|
@ -63,8 +63,8 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:text="@string/fab_restore_viewonly"
|
android:contentDescription="@string/fab_restore_viewonly"
|
||||||
android:contentDescription="@string/fab_restore_viewonly" />
|
android:text="@string/fab_restore_viewonly" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fabView"
|
android:id="@+id/fabView"
|
||||||
|
@ -94,8 +94,8 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:text="@string/fab_restore_key"
|
android:contentDescription="@string/fab_restore_key"
|
||||||
android:contentDescription="@string/fab_restore_key" />
|
android:text="@string/fab_restore_key" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fabKey"
|
android:id="@+id/fabKey"
|
||||||
|
@ -125,8 +125,8 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:text="@string/fab_restore_seed"
|
android:contentDescription="@string/fab_restore_seed"
|
||||||
android:contentDescription="@string/fab_restore_seed" />
|
android:text="@string/fab_restore_seed" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fabSeed"
|
android:id="@+id/fabSeed"
|
||||||
|
@ -156,8 +156,8 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerVertical="true"
|
android:layout_centerVertical="true"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:text="@string/menu_restore"
|
android:contentDescription="@string/menu_restore"
|
||||||
android:contentDescription="@string/menu_restore" />
|
android:text="@string/menu_restore" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/fabImport"
|
android:id="@+id/fabImport"
|
||||||
|
@ -207,6 +207,37 @@
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/fabSidekickL"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fabSidekickT"
|
||||||
|
style="@style/MoneroFab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="@string/fab_restore_sidekick" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fabSidekick"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:layout_toEndOf="@+id/fabSidekickT"
|
||||||
|
android:elevation="6dp"
|
||||||
|
android:src="@drawable/ic_sidekick_restore"
|
||||||
|
app:borderWidth="0dp"
|
||||||
|
app:fabSize="mini"
|
||||||
|
app:pressedTranslationZ="12dp" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -227,13 +258,13 @@
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:backgroundTint="@android:color/transparent"
|
android:backgroundTint="@android:color/transparent"
|
||||||
android:backgroundTintMode="src_in"
|
android:backgroundTintMode="src_in"
|
||||||
|
android:contentDescription="@string/fab_create_new"
|
||||||
android:src="@drawable/ic_add"
|
android:src="@drawable/ic_add"
|
||||||
app:borderWidth="0dp"
|
app:borderWidth="0dp"
|
||||||
app:elevation="0dp"
|
app:elevation="0dp"
|
||||||
app:fabSize="normal"
|
app:fabSize="normal"
|
||||||
app:pressedTranslationZ="0dp"
|
app:pressedTranslationZ="0dp"
|
||||||
app:tint="?attr/toolbarTextColor"
|
app:tint="?attr/toolbarTextColor" />
|
||||||
android:contentDescription="@string/fab_create_new" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_create_help_ledger"
|
android:id="@+id/action_create_help_sidekick"
|
||||||
android:icon="@drawable/ic_help_white_24dp"
|
android:icon="@drawable/ic_help_white_24dp"
|
||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
android:title="@string/menu_help"
|
android:title="@string/menu_help"
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_create_help_ledger"
|
||||||
|
android:icon="@drawable/ic_help_white_24dp"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:title="@string/menu_help"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
</menu>
|
|
@ -8,4 +8,10 @@
|
||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
android:title="@string/menu_help"
|
android:title="@string/menu_help"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_sidekick"
|
||||||
|
android:icon="@drawable/ic_sidekick_restore"
|
||||||
|
android:orderInCategory="200"
|
||||||
|
android:title="@string/menu_bluetooth"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
</menu>
|
</menu>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_help_sidekick"
|
||||||
|
android:orderInCategory="300"
|
||||||
|
android:title="@string/menu_help"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
</menu>
|
Before Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 9.5 KiB |
|
@ -251,4 +251,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -447,4 +447,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -311,4 +311,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -448,4 +448,17 @@
|
||||||
<string name="label_apply">ANWENDEN</string>
|
<string name="label_apply">ANWENDEN</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -293,4 +293,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -449,4 +449,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -337,4 +337,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -447,4 +447,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -139,15 +139,6 @@
|
||||||
<p>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.</p>
|
<p>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.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
<string name="help_confirm"><![CDATA[
|
|
||||||
<h1>Orden de cambio</h1>
|
|
||||||
<p>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.</p>
|
|
||||||
<h2>Número de orden</h2>
|
|
||||||
<p>Como Monerujo solamente maneja el lado de XMR de la transacción, vas a necesitar este <em>número de orden</em> para rastrear el proceso de tu intercambio o recibir soporte en el sitio del exchange.</p>
|
|
||||||
<h2>El tiempo se acabó</h2>
|
|
||||||
<p>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.</p>
|
|
||||||
]]></string>
|
|
||||||
|
|
||||||
<string name="help_create_ledger"><![CDATA[
|
<string name="help_create_ledger"><![CDATA[
|
||||||
<h1>Crear Monedero con Ledger</h1>
|
<h1>Crear Monedero con Ledger</h1>
|
||||||
<p>En esta pantalla puedes restaurar un monedero desde tu dispositivo Ledger Nano S.</p>
|
<p>En esta pantalla puedes restaurar un monedero desde tu dispositivo Ledger Nano S.</p>
|
||||||
|
@ -220,4 +211,37 @@
|
||||||
<h1>Nodo de Tor</h1>
|
<h1>Nodo de Tor</h1>
|
||||||
<p>Este nodo tiene una dirección <em>.onion</em>. Para usarlo, debes activar el modo Tor presionando el ícono <img src="ic_network_clearnet"/> junto al nodo en la pantalla principal.</p>
|
<p>Este nodo tiene una dirección <em>.onion</em>. Para usarlo, debes activar el modo Tor presionando el ícono <img src="ic_network_clearnet"/> junto al nodo en la pantalla principal.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -444,4 +444,17 @@
|
||||||
<string name="label_apply">APLICAR</string>
|
<string name="label_apply">APLICAR</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -313,4 +313,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -447,4 +447,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -422,4 +422,37 @@
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -687,4 +687,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -331,4 +331,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -460,4 +460,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -310,4 +310,37 @@
|
||||||
<p>זהו חוליית אוניון. כדי להשתמש בה, עליך להפעיל את מצב טור על-ידי לחיצה
|
<p>זהו חוליית אוניון. כדי להשתמש בה, עליך להפעיל את מצב טור על-ידי לחיצה
|
||||||
<img src="ic_network_clearnet"/> ב-סמל ליד החלק העליון של דף רשימת הארנקים.</p>
|
<img src="ic_network_clearnet"/> ב-סמל ליד החלק העליון של דף רשימת הארנקים.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -447,4 +447,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -315,4 +315,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -451,4 +451,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -316,4 +316,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -452,4 +452,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -444,4 +444,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -452,4 +452,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -313,4 +313,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -449,4 +449,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -251,4 +251,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -449,4 +449,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -312,4 +312,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -442,4 +442,17 @@ aqui.</string>
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -312,4 +312,37 @@
|
||||||
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
|
||||||
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
|
||||||
]]></string>
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_sidekick"><![CDATA[
|
||||||
|
<h2>Using your sidekick wallet</h2>
|
||||||
|
Check that both phones are connected via Bluetooth and both apps are opened.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
Open your wallet of choice.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On sidekick:</h3>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<h3>On monerujo:</h3>
|
||||||
|
The transaction should be signed and ready to be sent. Press to make it so.
|
||||||
|
<br/>
|
||||||
|
]]></string>
|
||||||
|
|
||||||
|
<string name="help_create_sidekick" translatable="false"><![CDATA[
|
||||||
|
<h1>Create Wallet - Sidekick</h1>
|
||||||
|
<p>You want to recover your wallet from your awesome Sidekick device.</p>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>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 <em>before</em> you first used this wallet address.</p>
|
||||||
|
]]></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -453,4 +453,17 @@
|
||||||
<string name="label_apply">APPLY</string>
|
<string name="label_apply">APPLY</string>
|
||||||
|
|
||||||
<string name="setting_lock">Lock Wallet in Background</string>
|
<string name="setting_lock">Lock Wallet in Background</string>
|
||||||
|
|
||||||
|
<string name="bluetooth_permissions">Bluetooth permissions are required to connect your Sidekick Wallet!</string>
|
||||||
|
<string name="bluetooth_permissions_ok">OK</string>
|
||||||
|
<string name="bluetooth_permissions_settings">App Settings</string>
|
||||||
|
<string name="bluetooth_permissions_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<string name="sidekick_pin">PIN: %1$s</string>
|
||||||
|
<string name="sidekick_not_connected">[not connected]</string>
|
||||||
|
<string name="sidekick_confirm_tx">Confirm Transaction</string>
|
||||||
|
<string name="sidekick_network_warning">Network is on - this is a security risk. It is recommended
|
||||||
|
to turn on flight mode and restart the app with only Bluetooth enabled!</string>
|
||||||
|
<string name="open_wallet_sidekick_missing">Please connect Sidekick device</string>
|
||||||
|
<string name="fab_restore_sidekick">Restore from Sidekick</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|