Monerujo for Ledger Nano S (#377)

This commit is contained in:
m2049r 2018-08-04 01:03:26 +02:00 committed by GitHub
parent 679bae5f42
commit bf5ed793b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 2639 additions and 252 deletions

View File

@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 25 targetSdkVersion 25
versionCode 102 versionCode 111
versionName "1.5.12 'Maximum Nachos'" versionName "1.6.1 'Nano S'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {

View File

@ -10,11 +10,11 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT" /> <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<application <application
android:name=".XmrWalletApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:name=".XmrWalletApplication"
android:theme="@style/MyMaterialTheme"> android:theme="@style/MyMaterialTheme">
<activity <activity
@ -28,13 +28,39 @@
android:name=".LoginActivity" android:name=".LoginActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="portrait"> android:screenOrientation="portrait">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter" />
</activity> </activity>
<!--activity
android:name=".util.UsbEventReceiverActivity"
android:excludeFromRecents="true"
android:exported="false"
android:label="@string/app_name"
android:noHistory="true"
android:process=":UsbEventReceiverActivityProcess"
android:taskAffinity="com.m2049r.xmrwallet.taskAffinityUsbEventReceiver"
android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter" />
</activity-->
<service <service
android:name=".service.WalletService" android:name=".service.WalletService"
android:description="@string/service_description" android:description="@string/service_description"

View File

@ -38,6 +38,7 @@ static jclass class_ArrayList;
static jclass class_WalletListener; static jclass class_WalletListener;
static jclass class_TransactionInfo; static jclass class_TransactionInfo;
static jclass class_Transfer; static jclass class_Transfer;
static jclass class_Ledger;
std::mutex _listenerMutex; std::mutex _listenerMutex;
@ -58,6 +59,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
jenv->FindClass("com/m2049r/xmrwallet/model/Transfer"))); jenv->FindClass("com/m2049r/xmrwallet/model/Transfer")));
class_WalletListener = static_cast<jclass>(jenv->NewGlobalRef( class_WalletListener = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/model/WalletListener"))); jenv->FindClass("com/m2049r/xmrwallet/model/WalletListener")));
class_Ledger = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger")));
return JNI_VERSION_1_6; return JNI_VERSION_1_6;
} }
#ifdef __cplusplus #ifdef __cplusplus
@ -353,6 +356,39 @@ Java_com_m2049r_xmrwallet_model_WalletManager_createWalletFromKeysJ(JNIEnv *env,
return reinterpret_cast<jlong>(wallet); return reinterpret_cast<jlong>(wallet);
} }
// virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) = 0;
JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_createWalletFromDeviceJ(JNIEnv *env, jobject instance,
jstring path,
jstring password,
jint networkType,
jstring deviceName,
jlong restoreHeight,
jstring subaddressLookahead) {
const char *_path = env->GetStringUTFChars(path, NULL);
const char *_password = env->GetStringUTFChars(password, NULL);
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
const char *_deviceName = env->GetStringUTFChars(deviceName, NULL);
const char *_subaddressLookahead = env->GetStringUTFChars(subaddressLookahead, NULL);
Bitmonero::Wallet *wallet =
Bitmonero::WalletManagerFactory::getWalletManager()->createWalletFromDevice(
std::string(_path),
std::string(_password),
_networkType,
std::string(_deviceName),
(uint64_t) restoreHeight,
std::string(_subaddressLookahead));
env->ReleaseStringUTFChars(path, _path);
env->ReleaseStringUTFChars(password, _password);
env->ReleaseStringUTFChars(deviceName, _deviceName);
env->ReleaseStringUTFChars(subaddressLookahead, _subaddressLookahead);
return reinterpret_cast<jlong>(wallet);
}
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_walletExists(JNIEnv *env, jobject instance, Java_com_m2049r_xmrwallet_model_WalletManager_walletExists(JNIEnv *env, jobject instance,
jstring path) { jstring path) {
@ -378,6 +414,20 @@ Java_com_m2049r_xmrwallet_model_WalletManager_verifyWalletPassword(JNIEnv *env,
return static_cast<jboolean>(passwordOk); return static_cast<jboolean>(passwordOk);
} }
//virtual int queryWalletHardware(const std::string &keys_file_name, const std::string &password) const = 0;
JNIEXPORT jint JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_queryWalletHardware(JNIEnv *env, jobject instance,
jstring keys_file_name,
jstring password) {
const char *_keys_file_name = env->GetStringUTFChars(keys_file_name, NULL);
const char *_password = env->GetStringUTFChars(password, NULL);
int hardwareId =
Bitmonero::WalletManagerFactory::getWalletManager()->
queryWalletHardware(std::string(_keys_file_name), std::string(_password));
env->ReleaseStringUTFChars(keys_file_name, _keys_file_name);
env->ReleaseStringUTFChars(password, _password);
return static_cast<jint>(hardwareId);
}
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_findWallets(JNIEnv *env, jobject instance, Java_com_m2049r_xmrwallet_model_WalletManager_findWallets(JNIEnv *env, jobject instance,
@ -712,6 +762,13 @@ Java_com_m2049r_xmrwallet_model_Wallet_isSynchronized(JNIEnv *env, jobject insta
return static_cast<jboolean>(wallet->synchronized()); return static_cast<jboolean>(wallet->synchronized());
} }
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_isKeyOnDevice(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
bool key_on_device = wallet->isKeyOnDevice();
return static_cast<jboolean>(key_on_device);
}
//void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h //void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h
JNIEXPORT jbyteArray JNICALL JNIEXPORT jbyteArray JNICALL
Java_com_m2049r_xmrwallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jobject clazz, Java_com_m2049r_xmrwallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jobject clazz,
@ -1309,6 +1366,96 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jobject i
Bitmonero::WalletManagerFactory::setLogLevel(level); Bitmonero::WalletManagerFactory::setLogLevel(level);
} }
//
// Ledger Stuff
//
#include "monerujo_ledger.h"
/**
* @brief LedgerExchange - exchange data with Ledger Device
* @param pbSendBuffer - buffer for data to send
* @param cbSendLength - length of send buffer
* @param pbRecvBuffer - buffer for received data
* @param pcbRecvLength - pointer to size of receive buffer
* gets set with length of received data on successful return
* @return SCARD_S_SUCCESS - success
* SCARD_E_NO_READERS_AVAILABLE - no device connected / found
* SCARD_E_INSUFFICIENT_BUFFER - pbRecvBuffer is too small for the response
*/
LONG LedgerExchange(
LPCBYTE pbSendBuffer,
DWORD cbSendLength,
LPBYTE pbRecvBuffer,
LPDWORD pcbRecvLength) {
LOGD("LedgerExchange");
JNIEnv *jenv;
int envStat = attachJVM(&jenv);
if (envStat == JNI_ERR) return -1;
jmethodID exchangeMethod = jenv->GetStaticMethodID(class_Ledger, "Exchange", "([B)[B");
jsize sendLen = static_cast<jsize>(cbSendLength);
jbyteArray dataSend = jenv->NewByteArray(sendLen);
jenv->SetByteArrayRegion(dataSend, 0, sendLen, (jbyte *) pbSendBuffer);
jbyteArray dataRecv = (jbyteArray) jenv->CallStaticObjectMethod(class_Ledger, exchangeMethod,
dataSend);
jenv->DeleteLocalRef(dataSend);
if (dataRecv == nullptr) {
detachJVM(jenv, envStat);
LOGD("LedgerExchange SCARD_E_NO_READERS_AVAILABLE");
return SCARD_E_NO_READERS_AVAILABLE;
}
jsize len = jenv->GetArrayLength(dataRecv);
LOGD("LedgerExchange SCARD_S_SUCCESS %ld/%d", cbSendLength, len);
if (len <= *pcbRecvLength) {
*pcbRecvLength = static_cast<DWORD>(len);
jenv->GetByteArrayRegion(dataRecv, 0, len, (jbyte *) pbRecvBuffer);
jenv->DeleteLocalRef(dataRecv);
detachJVM(jenv, envStat);
return SCARD_S_SUCCESS;
} else {
jenv->DeleteLocalRef(dataRecv);
detachJVM(jenv, envStat);
LOGE("LedgerExchange SCARD_E_INSUFFICIENT_BUFFER");
return SCARD_E_INSUFFICIENT_BUFFER;
}
}
/**
* @brief LedgerFind - find Ledger Device and return it's name
* @param buffer - buffer for name of found device
* @param len - length of buffer
* @return 0 - success
* -1 - no device connected / found
* -2 - JVM not found
*/
int LedgerFind(char *buffer, size_t len) {
LOGD("LedgerName");
JNIEnv *jenv;
int envStat = attachJVM(&jenv);
if (envStat == JNI_ERR) return -2;
jmethodID nameMethod = jenv->GetStaticMethodID(class_Ledger, "Name", "()Ljava/lang/String;");
jstring name = (jstring) jenv->CallStaticObjectMethod(class_Ledger, nameMethod);
int ret;
if (name != nullptr) {
const char *_name = jenv->GetStringUTFChars(name, NULL);
strncpy(buffer, _name, len);
jenv->ReleaseStringUTFChars(name, _name);
buffer[len - 1] = 0; // terminate in case _name is bigger
ret = 0;
LOGD("LedgerName is %s", buffer);
} else {
buffer[0] = 0;
ret = -1;
}
detachJVM(jenv, envStat);
return ret;
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2018 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef XMRWALLET_LEDGER_H
#define XMRWALLET_LEDGER_H
#ifdef __cplusplus
extern "C"
{
#endif
#define SCARD_S_SUCCESS ((LONG)0x00000000) /**< No error was encountered. */
#define SCARD_E_INSUFFICIENT_BUFFER ((LONG)0x80100008) /**< The data buffer to receive returned data is too small for the returned data. */
#define SCARD_E_NO_READERS_AVAILABLE ((LONG)0x8010002E) /**< Cannot find a smart card reader. */
typedef long LONG;
typedef unsigned long DWORD;
typedef DWORD *LPDWORD;
typedef unsigned char BYTE;
typedef BYTE *LPBYTE;
typedef const BYTE *LPCBYTE;
typedef char CHAR;
typedef CHAR *LPSTR;
int LedgerFind(char *buffer, size_t len);
LONG LedgerExchange(LPCBYTE pbSendBuffer, DWORD cbSendLength, LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength);
#ifdef __cplusplus
}
#endif
#endif //XMRWALLET_LEDGER_H

View File

@ -0,0 +1,53 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
*
* 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.btchip;
public class BTChipException extends Exception {
private static final long serialVersionUID = 5512803003827126405L;
public BTChipException(String reason) {
super(reason);
}
public BTChipException(String reason, Throwable cause) {
super(reason, cause);
}
public BTChipException(String reason, int sw) {
super(reason);
this.sw = sw;
}
public int getSW() {
return sw;
}
public String toString() {
if (sw == 0) {
return "BTChip Exception : " + getMessage();
} else {
return "BTChip Exception : " + getMessage() + " " + Integer.toHexString(sw);
}
}
private int sw;
}

View File

@ -0,0 +1,31 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
* (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.btchip.comm;
import com.btchip.BTChipException;
public interface BTChipTransport {
public byte[] exchange(byte[] command);
public void close();
public void setDebug(boolean debugFlag);
}

View File

@ -0,0 +1,126 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
* (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.btchip.comm;
import java.io.ByteArrayOutputStream;
public class LedgerHelper {
private static final int TAG_APDU = 0x05;
public static byte[] wrapCommandAPDU(int channel, byte[] command, int packetSize) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
if (packetSize < 3) {
throw new IllegalArgumentException("Can't handle Ledger framing with less than 3 bytes for the report");
}
int sequenceIdx = 0;
int offset = 0;
output.write(channel >> 8);
output.write(channel);
output.write(TAG_APDU);
output.write(sequenceIdx >> 8);
output.write(sequenceIdx);
sequenceIdx++;
output.write(command.length >> 8);
output.write(command.length);
int blockSize = (command.length > packetSize - 7 ? packetSize - 7 : command.length);
output.write(command, offset, blockSize);
offset += blockSize;
while (offset != command.length) {
output.write(channel >> 8);
output.write(channel);
output.write(TAG_APDU);
output.write(sequenceIdx >> 8);
output.write(sequenceIdx);
sequenceIdx++;
blockSize = (command.length - offset > packetSize - 5 ? packetSize - 5 : command.length - offset);
output.write(command, offset, blockSize);
offset += blockSize;
}
if ((output.size() % packetSize) != 0) {
byte[] padding = new byte[packetSize - (output.size() % packetSize)];
output.write(padding, 0, padding.length);
}
return output.toByteArray();
}
public static byte[] unwrapResponseAPDU(int channel, byte[] data, int packetSize) {
ByteArrayOutputStream response = new ByteArrayOutputStream();
int offset = 0;
int responseLength;
int sequenceIdx = 0;
if ((data == null) || (data.length < 7 + 5)) {
return null;
}
if (data[offset++] != (channel >> 8)) {
throw new IllegalArgumentException("Invalid channel");
}
if (data[offset++] != (channel & 0xff)) {
throw new IllegalArgumentException("Invalid channel");
}
if (data[offset++] != TAG_APDU) {
throw new IllegalArgumentException("Invalid tag");
}
if (data[offset++] != 0x00) {
throw new IllegalArgumentException("Invalid sequence");
}
if (data[offset++] != 0x00) {
throw new IllegalArgumentException("Invalid sequence");
}
responseLength = ((data[offset++] & 0xff) << 8);
responseLength |= (data[offset++] & 0xff);
if (data.length < 7 + responseLength) {
return null;
}
int blockSize = (responseLength > packetSize - 7 ? packetSize - 7 : responseLength);
response.write(data, offset, blockSize);
offset += blockSize;
while (response.size() != responseLength) {
sequenceIdx++;
if (offset == data.length) {
return null;
}
if (data[offset++] != (channel >> 8)) {
throw new IllegalArgumentException("Invalid channel");
}
if (data[offset++] != (channel & 0xff)) {
throw new IllegalArgumentException("Invalid channel");
}
if (data[offset++] != TAG_APDU) {
throw new IllegalArgumentException("Invalid tag");
}
if (data[offset++] != (sequenceIdx >> 8)) {
throw new IllegalArgumentException("Invalid sequence");
}
if (data[offset++] != (sequenceIdx & 0xff)) {
throw new IllegalArgumentException("Invalid sequence");
}
blockSize = (responseLength - response.size() > packetSize - 5 ? packetSize - 5 : responseLength - response.size());
if (blockSize > data.length - offset) {
return null;
}
response.write(data, offset, blockSize);
offset += blockSize;
}
return response.toByteArray();
}
}

View File

@ -0,0 +1,145 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
* (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.btchip.comm.android;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbRequest;
import com.btchip.BTChipException;
import com.btchip.comm.BTChipTransport;
import com.btchip.comm.LedgerHelper;
import com.btchip.utils.Dump;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import timber.log.Timber;
public class BTChipTransportAndroidHID implements BTChipTransport {
public static UsbDevice getDevice(UsbManager manager) {
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
for (UsbDevice device : deviceList.values()) {
Timber.d("%04X:%04X %s, %s", device.getVendorId(), device.getProductId(), device.getManufacturerName(), device.getProductName());
if ((device.getVendorId() == VID) && (device.getProductId() == PID_HID)) {
return device;
}
}
return null;
}
public static BTChipTransport open(UsbManager manager, UsbDevice device) throws IOException {
UsbDeviceConnection connection = manager.openDevice(device);
if (connection == null) throw new IOException("Device not connected");
// Must only be called once permission is granted (see http://developer.android.com/reference/android/hardware/usb/UsbManager.html)
// Important if enumerating, rather than being awaken by the intent notification
UsbInterface dongleInterface = device.getInterface(0);
UsbEndpoint in = null;
UsbEndpoint out = null;
for (int i = 0; i < dongleInterface.getEndpointCount(); i++) {
UsbEndpoint tmpEndpoint = dongleInterface.getEndpoint(i);
if (tmpEndpoint.getDirection() == UsbConstants.USB_DIR_IN) {
in = tmpEndpoint;
} else {
out = tmpEndpoint;
}
}
connection.claimInterface(dongleInterface, true);
return new BTChipTransportAndroidHID(connection, dongleInterface, in, out);
}
private static final int VID = 0x2C97;
private static final int PID_HID = 0x0001;
private UsbDeviceConnection connection;
private UsbInterface dongleInterface;
private UsbEndpoint in;
private UsbEndpoint out;
private byte transferBuffer[];
private boolean debug;
public BTChipTransportAndroidHID(UsbDeviceConnection connection, UsbInterface dongleInterface, UsbEndpoint in, UsbEndpoint out) {
this.connection = connection;
this.dongleInterface = dongleInterface;
this.in = in;
this.out = out;
transferBuffer = new byte[HID_BUFFER_SIZE];
}
@Override
public byte[] exchange(byte[] command) {
ByteArrayOutputStream response = new ByteArrayOutputStream();
byte[] responseData = null;
int offset = 0;
if (debug) {
Timber.d("=> %s", Dump.dump(command));
}
command = LedgerHelper.wrapCommandAPDU(LEDGER_DEFAULT_CHANNEL, command, HID_BUFFER_SIZE);
UsbRequest requestOut = new UsbRequest();
requestOut.initialize(connection, out);
while (offset != command.length) {
int blockSize = (command.length - offset > HID_BUFFER_SIZE ? HID_BUFFER_SIZE : command.length - offset);
System.arraycopy(command, offset, transferBuffer, 0, blockSize);
requestOut.queue(ByteBuffer.wrap(transferBuffer), HID_BUFFER_SIZE);
connection.requestWait();
offset += blockSize;
}
requestOut.close();
ByteBuffer responseBuffer = ByteBuffer.allocate(HID_BUFFER_SIZE);
UsbRequest requestIn = new UsbRequest();
requestIn.initialize(connection, in);
while ((responseData = LedgerHelper.unwrapResponseAPDU(LEDGER_DEFAULT_CHANNEL, response.toByteArray(), HID_BUFFER_SIZE)) == null) {
responseBuffer.clear();
requestIn.queue(responseBuffer, HID_BUFFER_SIZE);
connection.requestWait();
responseBuffer.rewind();
responseBuffer.get(transferBuffer, 0, HID_BUFFER_SIZE);
response.write(transferBuffer, 0, HID_BUFFER_SIZE);
}
requestIn.close();
if (debug) {
Timber.d("<= %s", Dump.dump(responseData));
}
return responseData;
}
@Override
public void close() {
connection.releaseInterface(dongleInterface);
connection.close();
}
@Override
public void setDebug(boolean debugFlag) {
this.debug = debugFlag;
}
private static final int HID_BUFFER_SIZE = 64;
private static final int LEDGER_DEFAULT_CHANNEL = 1;
private static final int SW1_DATA_AVAILABLE = 0x61;
}

View File

@ -0,0 +1,62 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
*
* 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.btchip.utils;
import java.io.ByteArrayOutputStream;
public class Dump {
public static String dump(byte[] buffer, int offset, int length) {
String result = "";
for (int i = 0; i < length; i++) {
String temp = Integer.toHexString((buffer[offset + i]) & 0xff);
if (temp.length() < 2) {
temp = "0" + temp;
}
result += temp;
}
return result;
}
public static String dump(byte[] buffer) {
return dump(buffer, 0, buffer.length);
}
public static byte[] hexToBin(String src) {
ByteArrayOutputStream result = new ByteArrayOutputStream();
int i = 0;
while (i < src.length()) {
char x = src.charAt(i);
if (!((x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f'))) {
i++;
continue;
}
try {
result.write(Integer.valueOf("" + src.charAt(i) + src.charAt(i + 1), 16));
i += 2;
} catch (Exception e) {
return null;
}
}
return result.toByteArray();
}
}

View File

@ -0,0 +1,105 @@
package com.m2049r.xmrwallet;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import com.m2049r.xmrwallet.dialog.ProgressDialog;
import com.m2049r.xmrwallet.ledger.Ledger;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import timber.log.Timber;
public class BaseActivity extends SecureActivity implements GenerateReviewFragment.ProgressListener {
ProgressDialog progressDialog = null;
private class SimpleProgressDialog extends ProgressDialog {
SimpleProgressDialog(Context context, int msgId) {
super(context);
setCancelable(false);
setMessage(context.getString(msgId));
}
@Override
public void onBackPressed() {
// prevent back button
}
}
@Override
public void showProgressDialog(int msgId) {
showProgressDialog(msgId, 0);
}
public void showProgressDialog(int msgId, long delay) {
dismissProgressDialog(); // just in case
progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
if (delay > 0) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
if (progressDialog != null) progressDialog.show();
}
}, delay);
} else {
progressDialog.show();
}
}
@Override
public void showLedgerProgressDialog(int mode) {
dismissProgressDialog(); // just in case
progressDialog = new LedgerProgressDialog(BaseActivity.this, mode);
Ledger.setListener((Ledger.Listener) progressDialog);
progressDialog.show();
}
@Override
public void dismissProgressDialog() {
if (progressDialog == null) return; // nothing to do
if (progressDialog instanceof Ledger.Listener) {
Ledger.unsetListener((Ledger.Listener) progressDialog);
}
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
progressDialog = null;
}
static final int RELEASE_WAKE_LOCK_DELAY = 5000; // millisconds
private PowerManager.WakeLock wl = null;
void acquireWakeLock() {
if ((wl != null) && wl.isHeld()) return;
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
this.wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name));
try {
wl.acquire();
Timber.d("WakeLock acquired");
} catch (SecurityException ex) {
Timber.w("WakeLock NOT acquired: %s", ex.getLocalizedMessage());
wl = null;
}
}
void releaseWakeLock(int delayMillis) {
Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
releaseWakeLock();
}
}, delayMillis);
}
void releaseWakeLock() {
if ((wl == null) || !wl.isHeld()) return;
wl.release();
wl = null;
Timber.d("WakeLock released");
}
}

View File

@ -61,6 +61,7 @@ public class GenerateFragment extends Fragment {
static final String TYPE_NEW = "new"; static final String TYPE_NEW = "new";
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_VIEWONLY = "view"; static final String TYPE_VIEWONLY = "view";
private TextInputLayout etWalletName; private TextInputLayout etWalletName;
@ -190,6 +191,17 @@ public class GenerateFragment extends Fragment {
return false; return false;
} }
}); });
} else if (type.equals(TYPE_LEDGER)) {
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
etWalletRestoreHeight.requestFocus();
return true;
}
return false;
}
});
} else if (type.equals(TYPE_SEED)) { } else if (type.equals(TYPE_SEED)) {
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
@ -485,6 +497,12 @@ public class GenerateFragment extends Fragment {
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password); KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
} }
activityCallback.onGenerate(name, crazyPass, seed, height); activityCallback.onGenerate(name, crazyPass, seed, height);
} else if (type.equals(TYPE_LEDGER)) {
bGenerate.setEnabled(false);
if (fingerprintAuthAllowed) {
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
}
activityCallback.onGenerateLedger(name, crazyPass, height);
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) { } else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
if (checkAddress() && checkViewKey() && checkSpendKey()) { if (checkAddress() && checkViewKey() && checkSpendKey()) {
bGenerate.setEnabled(false); bGenerate.setEnabled(false);
@ -523,6 +541,8 @@ public class GenerateFragment extends Fragment {
return getString(R.string.generate_wallet_type_new); return getString(R.string.generate_wallet_type_new);
case TYPE_SEED: case TYPE_SEED:
return getString(R.string.generate_wallet_type_seed); return getString(R.string.generate_wallet_type_seed);
case TYPE_LEDGER:
return getString(R.string.generate_wallet_type_ledger);
case TYPE_VIEWONLY: case TYPE_VIEWONLY:
return getString(R.string.generate_wallet_type_view); return getString(R.string.generate_wallet_type_view);
default: default:
@ -540,6 +560,8 @@ 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 setTitle(String title); void setTitle(String title);
void setToolbarButton(int type); void setToolbarButton(int type);
@ -575,6 +597,9 @@ public class GenerateFragment extends Fragment {
case TYPE_SEED: case TYPE_SEED:
inflater.inflate(R.menu.create_wallet_seed, menu); inflater.inflate(R.menu.create_wallet_seed, menu);
break; break;
case TYPE_LEDGER:
inflater.inflate(R.menu.create_wallet_ledger, 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;

View File

@ -43,6 +43,8 @@ import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.ledger.Ledger;
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;
@ -52,8 +54,6 @@ import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import java.io.File;
import timber.log.Timber; import timber.log.Timber;
public class GenerateReviewFragment extends Fragment { public class GenerateReviewFragment extends Fragment {
@ -76,6 +76,9 @@ public class GenerateReviewFragment extends Fragment {
private ImageButton bCopyAddress; private ImageButton bCopyAddress;
private LinearLayout llAdvancedInfo; private LinearLayout llAdvancedInfo;
private LinearLayout llPassword; private LinearLayout llPassword;
private LinearLayout llMnemonic;
private LinearLayout llSpendKey;
private LinearLayout llViewKey;
private Button bAdvancedInfo; private Button bAdvancedInfo;
private Button bAccept; private Button bAccept;
@ -99,6 +102,9 @@ public class GenerateReviewFragment extends Fragment {
bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo); bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo); llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
llPassword = (LinearLayout) view.findViewById(R.id.llPassword); llPassword = (LinearLayout) view.findViewById(R.id.llPassword);
llMnemonic = (LinearLayout) view.findViewById(R.id.llMnemonic);
llSpendKey = (LinearLayout) view.findViewById(R.id.llSpendKey);
llViewKey = (LinearLayout) view.findViewById(R.id.llViewKey);
bAccept = (Button) view.findViewById(R.id.bAccept); bAccept = (Button) view.findViewById(R.id.bAccept);
@ -142,7 +148,6 @@ public class GenerateReviewFragment extends Fragment {
} }
void showDetails() { void showDetails() {
showProgress();
tvWalletPassword.setText(null); tvWalletPassword.setText(null);
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath); new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath);
} }
@ -188,6 +193,20 @@ public class GenerateReviewFragment extends Fragment {
boolean isWatchOnly; boolean isWatchOnly;
Wallet.Status status; Wallet.Status status;
boolean dialogOpened = false;
@Override
protected void onPreExecute() {
super.onPreExecute();
showProgress();
if ((walletPath != null)
&& (WalletManager.getInstance().queryWalletHardware(walletPath + ".keys", getPassword()) == 1)
&& (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
dialogOpened = true;
}
}
@Override @Override
protected Boolean doInBackground(String... params) { protected Boolean doInBackground(String... params) {
if (params.length != 1) return false; if (params.length != 1) return false;
@ -212,7 +231,11 @@ public class GenerateReviewFragment extends Fragment {
address = wallet.getAddress(); address = wallet.getAddress();
seed = wallet.getSeed(); seed = wallet.getSeed();
viewKey = wallet.getSecretViewKey(); if (wallet.isKeyOnDevice()) {
viewKey = Ledger.Key();
} else {
viewKey = wallet.getSecretViewKey();
}
spendKey = isWatchOnly ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey(); spendKey = isWatchOnly ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey();
isWatchOnly = wallet.isWatchOnly(); isWatchOnly = wallet.isWatchOnly();
if (closeWallet) wallet.close(); if (closeWallet) wallet.close();
@ -222,6 +245,8 @@ public class GenerateReviewFragment extends Fragment {
@Override @Override
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
super.onPostExecute(result); super.onPostExecute(result);
if (dialogOpened)
progressCallback.dismissProgressDialog();
if (!isAdded()) return; // never mind if (!isAdded()) return; // never mind
walletName = name; walletName = name;
if (result) { if (result) {
@ -232,10 +257,22 @@ public class GenerateReviewFragment extends Fragment {
llPassword.setVisibility(View.VISIBLE); llPassword.setVisibility(View.VISIBLE);
tvWalletPassword.setText(getPassword()); tvWalletPassword.setText(getPassword());
tvWalletAddress.setText(address); tvWalletAddress.setText(address);
tvWalletMnemonic.setText(seed); if (!seed.isEmpty()) {
tvWalletViewKey.setText(viewKey); llMnemonic.setVisibility(View.VISIBLE);
tvWalletSpendKey.setText(spendKey); tvWalletMnemonic.setText(seed);
bAdvancedInfo.setVisibility(View.VISIBLE); }
boolean showAdvanced = false;
if (isKeyValid(viewKey)) {
llViewKey.setVisibility(View.VISIBLE);
tvWalletViewKey.setText(viewKey);
showAdvanced = true;
}
if (isKeyValid(spendKey)) {
llSpendKey.setVisibility(View.VISIBLE);
tvWalletSpendKey.setText(spendKey);
showAdvanced = true;
}
if (showAdvanced) bAdvancedInfo.setVisibility(View.VISIBLE);
bCopyAddress.setClickable(true); bCopyAddress.setClickable(true);
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp); bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
activityCallback.setTitle(name, getString(R.string.details_title)); activityCallback.setTitle(name, getString(R.string.details_title));
@ -267,6 +304,8 @@ public class GenerateReviewFragment extends Fragment {
public interface ProgressListener { public interface ProgressListener {
void showProgressDialog(int msgId); void showProgressDialog(int msgId);
void showLedgerProgressDialog(int mode);
void dismissProgressDialog(); void dismissProgressDialog();
} }
@ -577,4 +616,10 @@ public class GenerateReviewFragment extends Fragment {
return openDialog; return openDialog;
} }
private boolean isKeyValid(String key) {
return (key != null) && (key.length() == 64)
&& !key.equals("0000000000000000000000000000000000000000000000000000000000000000")
&& !key.toLowerCase().equals("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
// ledger implmenetation returns the spend key as f's
}
} }

View File

@ -16,18 +16,20 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.ProgressDialog; import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.media.MediaScannerConnection; import android.media.MediaScannerConnection;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
@ -46,6 +48,8 @@ import com.m2049r.xmrwallet.dialog.AboutFragment;
import com.m2049r.xmrwallet.dialog.CreditsFragment; import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.dialog.PrivacyFragment; import com.m2049r.xmrwallet.dialog.PrivacyFragment;
import com.m2049r.xmrwallet.ledger.Ledger;
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;
@ -72,10 +76,10 @@ import java.util.Locale;
import timber.log.Timber; import timber.log.Timber;
public class LoginActivity extends SecureActivity public class LoginActivity extends BaseActivity
implements LoginFragment.Listener, GenerateFragment.Listener, implements LoginFragment.Listener, GenerateFragment.Listener,
GenerateReviewFragment.Listener, GenerateReviewFragment.AcceptListener, GenerateReviewFragment.Listener, GenerateReviewFragment.AcceptListener,
GenerateReviewFragment.ProgressListener, ReceiveFragment.Listener { ReceiveFragment.Listener {
private static final String GENERATE_STACK = "gen"; private static final String GENERATE_STACK = "gen";
static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms
@ -102,6 +106,11 @@ public class LoginActivity extends SecureActivity
toolbar.setTitle(title, subtitle); toolbar.setTitle(title, subtitle);
} }
@Override
public boolean hasLedger() {
return Ledger.isConnected();
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
Timber.d("onCreate()"); Timber.d("onCreate()");
@ -140,6 +149,8 @@ public class LoginActivity extends SecureActivity
} else { } else {
Timber.i("Waiting for permissions"); Timber.i("Waiting for permissions");
} }
processIntent(getIntent());
} }
boolean checkServiceRunning() { boolean checkServiceRunning() {
@ -517,39 +528,12 @@ public class LoginActivity extends SecureActivity
super.onPause(); super.onPause();
} }
ProgressDialog progressDialog = null;
@Override
public void showProgressDialog(int msgId) {
showProgressDialog(msgId, 0);
}
private void showProgressDialog(int msgId, long delay) {
dismissProgressDialog(); // just in case
progressDialog = new MyProgressDialog(LoginActivity.this, msgId);
if (delay > 0) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
if (progressDialog != null) progressDialog.show();
}
}, delay);
} else {
progressDialog.show();
}
}
@Override
public void dismissProgressDialog() {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
progressDialog = null;
}
@Override @Override
protected void onDestroy() { protected void onDestroy() {
Timber.d("onDestroy");
dismissProgressDialog(); dismissProgressDialog();
unregisterDetachReceiver();
Ledger.disconnect();
super.onDestroy(); super.onDestroy();
} }
@ -562,25 +546,9 @@ public class LoginActivity extends SecureActivity
// and show a progress dialog, but only if there isn't one already // and show a progress dialog, but only if there isn't one already
new AsyncWaitForService().execute(); new AsyncWaitForService().execute();
} }
if (!Ledger.isConnected()) attachLedger();
} }
private class MyProgressDialog extends ProgressDialog {
Activity activity;
MyProgressDialog(Activity activity, int msgId) {
super(activity);
this.activity = activity;
setCancelable(false);
setMessage(activity.getString(msgId));
}
@Override
public void onBackPressed() {
// prevent back button
}
}
private class AsyncWaitForService extends AsyncTask<Void, Void, Void> { private class AsyncWaitForService extends AsyncTask<Void, Void, Void> {
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
@ -730,7 +698,12 @@ public class LoginActivity extends SecureActivity
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
showProgressDialog(R.string.generate_wallet_creating); acquireWakeLock();
if (walletCreator.isLedger()) {
showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
} else {
showProgressDialog(R.string.generate_wallet_creating);
}
} }
@Override @Override
@ -763,6 +736,7 @@ public class LoginActivity extends SecureActivity
@Override @Override
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
super.onPostExecute(result); super.onPostExecute(result);
releaseWakeLock(RELEASE_WAKE_LOCK_DELAY);
if (isDestroyed()) { if (isDestroyed()) {
return; return;
} }
@ -794,12 +768,20 @@ public class LoginActivity extends SecureActivity
interface WalletCreator { interface WalletCreator {
boolean createWallet(File aFile, String password); boolean createWallet(File aFile, String password);
boolean isLedger();
} }
@Override @Override
public void onGenerate(final String name, final String password) { public void onGenerate(final String name, final String password) {
createWallet(name, password, createWallet(name, password,
new WalletCreator() { new WalletCreator() {
@Override
public boolean isLedger() {
return false;
}
@Override
public boolean createWallet(File aFile, String password) { public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance() Wallet newWallet = WalletManager.getInstance()
.createWallet(aFile, password, MNEMONIC_LANGUAGE); .createWallet(aFile, password, MNEMONIC_LANGUAGE);
@ -819,9 +801,40 @@ public class LoginActivity extends SecureActivity
final long restoreHeight) { final long restoreHeight) {
createWallet(name, password, createWallet(name, password,
new WalletCreator() { new WalletCreator() {
@Override
public boolean isLedger() {
return false;
}
@Override
public boolean createWallet(File aFile, String password) { public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance(). Wallet newWallet = WalletManager.getInstance()
recoveryWallet(aFile, password, seed, restoreHeight); .recoveryWallet(aFile, password, seed, restoreHeight);
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) {
Timber.e(newWallet.getErrorString());
toast(newWallet.getErrorString());
}
newWallet.close();
return success;
}
});
}
@Override
public void onGenerateLedger(final String name, final String password, final long restoreHeight) {
createWallet(name, password,
new WalletCreator() {
@Override
public boolean isLedger() {
return true;
}
@Override
public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance()
.createWalletFromDevice(aFile, password,
restoreHeight, "Ledger");
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok); boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) { if (!success) {
Timber.e(newWallet.getErrorString()); Timber.e(newWallet.getErrorString());
@ -839,6 +852,12 @@ public class LoginActivity extends SecureActivity
final long restoreHeight) { final long restoreHeight) {
createWallet(name, password, createWallet(name, password,
new WalletCreator() { new WalletCreator() {
@Override
public boolean isLedger() {
return false;
}
@Override
public boolean createWallet(File aFile, String password) { public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance() Wallet newWallet = WalletManager.getInstance()
.createWalletWithKeys(aFile, password, MNEMONIC_LANGUAGE, restoreHeight, .createWalletWithKeys(aFile, password, MNEMONIC_LANGUAGE, restoreHeight,
@ -863,6 +882,15 @@ public class LoginActivity extends SecureActivity
}); });
} }
void toast(final int msgId) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this, getString(msgId), Toast.LENGTH_LONG).show();
}
});
}
@Override @Override
public void onAccept(final String name, final String password) { public void onAccept(final String name, final String password) {
File walletFolder = getStorageRoot(); File walletFolder = getStorageRoot();
@ -870,26 +898,9 @@ public class LoginActivity extends SecureActivity
Timber.d("New Wallet %s", walletFile.getAbsolutePath()); Timber.d("New Wallet %s", walletFile.getAbsolutePath());
walletFile.delete(); // when recovering wallets, the cache seems corrupt walletFile.delete(); // when recovering wallets, the cache seems corrupt
boolean rc = testWallet(walletFile.getAbsolutePath(), password) == Wallet.Status.Status_Ok; popFragmentStack(GENERATE_STACK);
Toast.makeText(LoginActivity.this,
if (rc) { getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show();
popFragmentStack(GENERATE_STACK);
Toast.makeText(LoginActivity.this,
getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show();
} else {
Timber.e("Wallet store failed to %s", walletFile.getAbsolutePath());
Toast.makeText(LoginActivity.this, getString(R.string.generate_wallet_create_failed), Toast.LENGTH_LONG).show();
}
}
Wallet.Status testWallet(String path, String password) {
Timber.d("testing wallet %s", path);
Wallet aWallet = WalletManager.getInstance().openWallet(path, password);
if (aWallet == null) return Wallet.Status.Status_Error; // does this ever happen?
Wallet.Status status = aWallet.getStatus();
Timber.d("wallet tested %s", aWallet.getStatus());
aWallet.close();
return status;
} }
boolean walletExists(File walletFile, boolean any) { boolean walletExists(File walletFile, boolean any) {
@ -1040,6 +1051,9 @@ public class LoginActivity extends SecureActivity
case R.id.action_create_help_seed: case R.id.action_create_help_seed:
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_seed); HelpFragment.display(getSupportFragmentManager(), R.string.help_create_seed);
return true; return true;
case R.id.action_create_help_ledger:
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_ledger);
return true;
case R.id.action_details_help: case R.id.action_details_help:
HelpFragment.display(getSupportFragmentManager(), R.string.help_details); HelpFragment.display(getSupportFragmentManager(), R.string.help_details);
return true; return true;
@ -1150,14 +1164,166 @@ public class LoginActivity extends SecureActivity
File walletFile = Helper.getWalletFile(this, walletNode.getName()); File walletFile = Helper.getWalletFile(this, walletNode.getName());
if (WalletManager.getInstance().walletExists(walletFile)) { if (WalletManager.getInstance().walletExists(walletFile)) {
WalletManager.getInstance().setDaemon(walletNode); WalletManager.getInstance().setDaemon(walletNode);
Helper.promptPassword(LoginActivity.this, walletNode.getName(), false, new Helper.PasswordAction() { Helper.promptPassword(LoginActivity.this, walletNode.getName(), false,
@Override new Helper.PasswordAction() {
public void action(String walletName, String password, boolean fingerprintUsed) { @Override
startWallet(walletName, password, fingerprintUsed); public void action(String walletName, String password, boolean fingerprintUsed) {
} String keyPath = new File(Helper.getWalletRoot(LoginActivity.this),
}); walletName + ".keys").getAbsolutePath();
// check if we need connected hardware
int hw = WalletManager.getInstance().queryWalletHardware(keyPath, password);
if ((hw == 1) && (!hasLedger())) {
toast(R.string.open_wallet_ledger_missing);
} else {
// hw could be < 0 meaning the password is wrong - this gets dealt with later
startWallet(walletName, password, fingerprintUsed);
}
}
});
} else { // this cannot really happen as we prefilter choices } else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show(); Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
} }
} }
// USB Stuff - (Ledger)
private static final String ACTION_USB_PERMISSION = "com.m2049r.xmrwallet.USB_PERMISSION";
void attachLedger() {
final UsbManager usbManager = getUsbManager();
UsbDevice device = Ledger.findDevice(usbManager);
if (device != null) {
if (usbManager.hasPermission(device)) {
connectLedger(usbManager, device);
} else {
registerReceiver(usbPermissionReceiver, new IntentFilter(ACTION_USB_PERMISSION));
usbManager.requestPermission(device,
PendingIntent.getBroadcast(this, 0,
new Intent(ACTION_USB_PERMISSION), 0));
}
} else {
Timber.d("no ledger device found");
}
}
private final BroadcastReceiver usbPermissionReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
unregisterReceiver(usbPermissionReceiver);
synchronized (this) {
UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
if (device != null) {
connectLedger(getUsbManager(), device);
}
} else {
Timber.w("User denied permission for device %s", device.getProductName());
}
}
}
}
};
private void connectLedger(UsbManager usbManager, final UsbDevice usbDevice) {
try {
Ledger.connect(usbManager, usbDevice);
registerDetachReceiver();
onLedgerAction();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
Toast.LENGTH_SHORT)
.show();
}
});
} catch (IOException ex) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.open_wallet_ledger_missing),
Toast.LENGTH_SHORT)
.show();
}
});
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
processIntent(intent);
}
private void processIntent(Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
synchronized (this) {
final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null) {
final UsbManager usbManager = getUsbManager();
if (usbManager.hasPermission(device)) {
Timber.d("Ledger attached by intent");
connectLedger(usbManager, device);
}
}
}
}
}
BroadcastReceiver detachReceiver;
private void unregisterDetachReceiver() {
if (detachReceiver != null) unregisterReceiver(detachReceiver);
}
private void registerDetachReceiver() {
detachReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
unregisterDetachReceiver();
final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Timber.i("Ledger detached!");
if (device != null)
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_detached, device.getProductName()),
Toast.LENGTH_SHORT)
.show();
}
});
Ledger.disconnect();
onLedgerAction();
}
}
};
registerReceiver(detachReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));
}
public void onLedgerAction() {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (f instanceof GenerateFragment) {
onBackPressed();
} else if (f instanceof LoginFragment) {
if (((LoginFragment) f).isFabOpen()) {
((LoginFragment) f).animateFAB();
}
}
}
// get UsbManager or die trying
@NonNull
private UsbManager getUsbManager() {
final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
if (usbManager == null) {
throw new IllegalStateException("no USB_SERVICE");
}
return usbManager;
}
} }

View File

@ -103,6 +103,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void setNetworkType(NetworkType networkType); void setNetworkType(NetworkType networkType);
boolean hasLedger();
} }
@Override @Override
@ -145,11 +146,13 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabView = (FloatingActionButton) view.findViewById(R.id.fabView); fabView = (FloatingActionButton) view.findViewById(R.id.fabView);
fabKey = (FloatingActionButton) view.findViewById(R.id.fabKey); fabKey = (FloatingActionButton) view.findViewById(R.id.fabKey);
fabSeed = (FloatingActionButton) view.findViewById(R.id.fabSeed); fabSeed = (FloatingActionButton) view.findViewById(R.id.fabSeed);
fabLedger = (FloatingActionButton) view.findViewById(R.id.fabLedger);
fabNewL = (RelativeLayout) view.findViewById(R.id.fabNewL); fabNewL = (RelativeLayout) view.findViewById(R.id.fabNewL);
fabViewL = (RelativeLayout) view.findViewById(R.id.fabViewL); fabViewL = (RelativeLayout) view.findViewById(R.id.fabViewL);
fabKeyL = (RelativeLayout) view.findViewById(R.id.fabKeyL); fabKeyL = (RelativeLayout) view.findViewById(R.id.fabKeyL);
fabSeedL = (RelativeLayout) view.findViewById(R.id.fabSeedL); fabSeedL = (RelativeLayout) view.findViewById(R.id.fabSeedL);
fabLedgerL = (RelativeLayout) view.findViewById(R.id.fabLedgerL);
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);
@ -163,6 +166,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabView.setOnClickListener(this); fabView.setOnClickListener(this);
fabKey.setOnClickListener(this); fabKey.setOnClickListener(this);
fabSeed.setOnClickListener(this); fabSeed.setOnClickListener(this);
fabLedger.setOnClickListener(this);
fabScreen.setOnClickListener(this); fabScreen.setOnClickListener(this);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list); RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
@ -173,7 +177,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
etDummy = (EditText) view.findViewById(R.id.etDummy); etDummy = (EditText) view.findViewById(R.id.etDummy);
ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice); ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
Notice.showAll(llNotice,".*_login"); Notice.showAll(llNotice, ".*_login");
etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress); etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress);
nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line); nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line);
@ -426,9 +430,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
} }
private boolean isFabOpen = false; private boolean isFabOpen = false;
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed; private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabLedger;
private FrameLayout fabScreen; private FrameLayout fabScreen;
private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL; private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabLedgerL;
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;
@ -437,32 +441,53 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
} }
public void animateFAB() { public void animateFAB() {
if (isFabOpen) { if (isFabOpen) { // close the fab
fabScreen.setVisibility(View.INVISIBLE);
fabScreen.setClickable(false); fabScreen.setClickable(false);
fabScreen.startAnimation(fab_close_screen); fabScreen.startAnimation(fab_close_screen);
fab.startAnimation(rotate_backward); fab.startAnimation(rotate_backward);
fabNewL.startAnimation(fab_close); if (fabLedgerL.getVisibility() == View.VISIBLE) {
fabNew.setClickable(false); fabLedgerL.startAnimation(fab_close);
fabViewL.startAnimation(fab_close); fabLedger.setClickable(false);
fabView.setClickable(false); } else {
fabKeyL.startAnimation(fab_close); fabNewL.startAnimation(fab_close);
fabKey.setClickable(false); fabNew.setClickable(false);
fabSeedL.startAnimation(fab_close); fabViewL.startAnimation(fab_close);
fabSeed.setClickable(false); fabView.setClickable(false);
fabKeyL.startAnimation(fab_close);
fabKey.setClickable(false);
fabSeedL.startAnimation(fab_close);
fabSeed.setClickable(false);
}
isFabOpen = false; isFabOpen = false;
} else { } else { // open the fab
fabScreen.setClickable(true); fabScreen.setClickable(true);
fabScreen.startAnimation(fab_open_screen); fabScreen.startAnimation(fab_open_screen);
fab.startAnimation(rotate_forward); fab.startAnimation(rotate_forward);
fabNewL.startAnimation(fab_open); if (activityCallback.hasLedger()) {
fabNew.setClickable(true); fabLedgerL.setVisibility(View.VISIBLE);
fabViewL.startAnimation(fab_open); fabNewL.setVisibility(View.GONE);
fabView.setClickable(true); fabViewL.setVisibility(View.GONE);
fabKeyL.startAnimation(fab_open); fabKeyL.setVisibility(View.GONE);
fabKey.setClickable(true); fabSeedL.setVisibility(View.GONE);
fabSeedL.startAnimation(fab_open);
fabSeed.setClickable(true); fabLedgerL.startAnimation(fab_open);
fabLedger.setClickable(true);
} else {
fabLedgerL.setVisibility(View.GONE);
fabNewL.setVisibility(View.VISIBLE);
fabViewL.setVisibility(View.VISIBLE);
fabKeyL.setVisibility(View.VISIBLE);
fabSeedL.setVisibility(View.VISIBLE);
fabNewL.startAnimation(fab_open);
fabNew.setClickable(true);
fabViewL.startAnimation(fab_open);
fabView.setClickable(true);
fabKeyL.startAnimation(fab_open);
fabKey.setClickable(true);
fabSeedL.startAnimation(fab_open);
fabSeed.setClickable(true);
}
isFabOpen = true; isFabOpen = true;
} }
} }
@ -470,6 +495,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override @Override
public void onClick(View v) { public void onClick(View v) {
int id = v.getId(); int id = v.getId();
Timber.d("onClick %d/%d", id, R.id.fabLedger);
switch (id) { switch (id) {
case R.id.fab: case R.id.fab:
animateFAB(); animateFAB();
@ -491,6 +517,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
animateFAB(); animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED); activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
break; break;
case R.id.fabLedger:
Timber.d("FAB_LEDGER");
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
break;
case R.id.fabScreen: case R.id.fabScreen:
animateFAB(); animateFAB();
break; break;

View File

@ -47,6 +47,7 @@ import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
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.util.Helper; import com.m2049r.xmrwallet.util.Helper;
@ -62,7 +63,6 @@ import timber.log.Timber;
public class ReceiveFragment extends Fragment { public class ReceiveFragment extends Fragment {
private ProgressBar pbProgress; private ProgressBar pbProgress;
private View llAddress;
private TextView tvAddressLabel; private TextView tvAddressLabel;
private TextView tvAddress; private TextView tvAddress;
private TextInputLayout etPaymentId; private TextInputLayout etPaymentId;
@ -93,7 +93,6 @@ public class ReceiveFragment extends Fragment {
View view = inflater.inflate(R.layout.fragment_receive, container, false); View view = inflater.inflate(R.layout.fragment_receive, container, false);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
llAddress = view.findViewById(R.id.llAddress);
tvAddressLabel = (TextView) view.findViewById(R.id.tvAddressLabel); tvAddressLabel = (TextView) view.findViewById(R.id.tvAddressLabel);
tvAddress = (TextView) view.findViewById(R.id.tvAddress); tvAddress = (TextView) view.findViewById(R.id.tvAddress);
etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId); etPaymentId = (TextInputLayout) view.findViewById(R.id.etPaymentId);
@ -177,23 +176,9 @@ public class ReceiveFragment extends Fragment {
enableSubaddressButton(false); enableSubaddressButton(false);
enableCopyAddress(false); enableCopyAddress(false);
final Runnable resetSize = new Runnable() {
public void run() {
tvAddress.animate().setDuration(125).scaleX(1).scaleY(1).start();
}
};
final Runnable newAddress = new Runnable() { final Runnable newAddress = new Runnable() {
public void run() { public void run() {
tvAddress.setText(wallet.getNewSubaddress()); getNewSubaddress();
tvAddressLabel.setText(getString(R.string.generate_address_label_sub,
wallet.getNumSubaddresses() - 1));
storeWallet();
generateQr();
enableCopyAddress(true);
tvAddress.animate().alpha(1).setDuration(125)
.scaleX(1.2f).scaleY(1.2f)
.withEndAction(resetSize).start();
} }
}; };
@ -315,18 +300,39 @@ public class ReceiveFragment extends Fragment {
} }
private void loadAndShow(String walletPath, String password) { private void loadAndShow(String walletPath, String password) {
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, new AsyncShow(walletPath, password).executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR);
walletPath, password);
} }
private class AsyncShow extends AsyncTask<String, Void, Boolean> { GenerateReviewFragment.ProgressListener progressCallback = null;
String password;
private class AsyncShow extends AsyncTask<Void, Void, Boolean> {
final private String walletPath;
final private String password;
AsyncShow(String walletPath, String passsword) {
super();
this.walletPath = walletPath;
this.password = passsword;
}
boolean dialogOpened = false;
@Override @Override
protected Boolean doInBackground(String... params) { protected void onPreExecute() {
if (params.length != 2) return false; super.onPreExecute();
String walletPath = params[0]; showProgress();
password = params[1]; if ((walletPath != null)
&& (WalletManager.getInstance().queryWalletHardware(walletPath + ".keys", password) == 1)
&& (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
dialogOpened = true;
}
}
@Override
protected Boolean doInBackground(Void... params) {
if (params.length != 0) return false;
wallet = WalletManager.getInstance().openWallet(walletPath, password); wallet = WalletManager.getInstance().openWallet(walletPath, password);
isMyWallet = true; isMyWallet = true;
return true; return true;
@ -335,6 +341,8 @@ public class ReceiveFragment extends Fragment {
@Override @Override
protected void onPostExecute(Boolean result) { protected void onPostExecute(Boolean result) {
super.onPostExecute(result); super.onPostExecute(result);
if (dialogOpened)
progressCallback.dismissProgressDialog();
if (!isAdded()) return; // never mind if (!isAdded()) return; // never mind
if (result) { if (result) {
show(); show();
@ -495,6 +503,10 @@ public class ReceiveFragment extends Fragment {
throw new ClassCastException(context.toString() throw new ClassCastException(context.toString()
+ " must implement Listener"); + " must implement Listener");
} }
if (context instanceof GenerateReviewFragment.ProgressListener) {
this.progressCallback = (GenerateReviewFragment.ProgressListener) context;
}
} }
@Override @Override
@ -513,4 +525,51 @@ public class ReceiveFragment extends Fragment {
} }
super.onDetach(); super.onDetach();
} }
private void getNewSubaddress() {
new AsyncSubaddress().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR);
}
private class AsyncSubaddress extends AsyncTask<Void, Void, Boolean> {
private String newSubaddress;
boolean dialogOpened = false;
@Override
protected void onPreExecute() {
super.onPreExecute();
if (wallet.isKeyOnDevice() && (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS);
dialogOpened = true;
}
}
@Override
protected Boolean doInBackground(Void... params) {
if (params.length != 0) return false;
newSubaddress = wallet.getNewSubaddress();
storeWallet();
return true;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (dialogOpened)
progressCallback.dismissProgressDialog();
tvAddress.setText(newSubaddress);
tvAddressLabel.setText(getString(R.string.generate_address_label_sub,
wallet.getNumSubaddresses() - 1));
generateQr();
enableCopyAddress(true);
final Runnable resetSize = new Runnable() {
public void run() {
tvAddress.animate().setDuration(125).scaleX(1).scaleY(1).start();
}
};
tvAddress.animate().alpha(1).setDuration(125)
.scaleX(1.2f).scaleY(1.2f)
.withEndAction(resetSize).start();
}
}
} }

View File

@ -24,6 +24,7 @@ import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
@ -51,12 +52,14 @@ import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment; import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
import com.m2049r.xmrwallet.fragment.send.SendFragment; import com.m2049r.xmrwallet.fragment.send.SendFragment;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
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.WalletService; import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.util.UserNotes; import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
@ -65,7 +68,7 @@ import java.util.List;
import timber.log.Timber; import timber.log.Timber;
public class WalletActivity extends SecureActivity implements WalletFragment.Listener, public class WalletActivity extends BaseActivity implements WalletFragment.Listener,
WalletService.Observer, SendFragment.Listener, TxFragment.Listener, WalletService.Observer, SendFragment.Listener, TxFragment.Listener,
GenerateReviewFragment.ListenerWithWallet, GenerateReviewFragment.ListenerWithWallet,
GenerateReviewFragment.Listener, GenerateReviewFragment.Listener,
@ -397,28 +400,6 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
Timber.d("onResume()"); Timber.d("onResume()");
} }
private PowerManager.WakeLock wl = null;
void acquireWakeLock() {
if ((wl != null) && wl.isHeld()) return;
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
this.wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getString(R.string.app_name));
try {
wl.acquire();
Timber.d("WakeLock acquired");
} catch (SecurityException ex) {
Timber.w("WakeLock NOT acquired: %s", ex.getLocalizedMessage());
wl = null;
}
}
public void releaseWakeLock() {
if ((wl == null) || !wl.isHeld()) return;
wl.release();
wl = null;
Timber.d("WakeLock released");
}
public void saveWallet() { public void saveWallet() {
if (mIsBound) { // no point in talking to unbound service if (mIsBound) { // no point in talking to unbound service
Intent intent = new Intent(getApplicationContext(), WalletService.class); Intent intent = new Intent(getApplicationContext(), WalletService.class);
@ -503,7 +484,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName()); getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName());
if (wallet.isSynchronized()) { if (wallet.isSynchronized()) {
Timber.d("onRefreshed() synced"); Timber.d("onRefreshed() synced");
releaseWakeLock(); // the idea is to stay awake until synced releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced
if (!synced) { // first sync if (!synced) { // first sync
onProgress(-1); onProgress(-1);
saveWallet(); // save on first sync saveWallet(); // save on first sync
@ -544,10 +525,21 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
boolean haveWallet = false; boolean haveWallet = false;
@Override
public void onWalletOpen(final int hardware) {
if (hardware > 0)
runOnUiThread(new Runnable() {
public void run() {
showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
}
});
}
@Override @Override
public void onWalletStarted(final boolean success) { public void onWalletStarted(final boolean success) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
dismissProgressDialog();
if (!success) { if (!success) {
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show(); Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show();
} }
@ -578,6 +570,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
getSupportFragmentManager().findFragmentById(R.id.fragment_container); getSupportFragmentManager().findFragmentById(R.id.fragment_container);
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
dismissProgressDialog();
PendingTransaction.Status status = pendingTransaction.getStatus(); PendingTransaction.Status status = pendingTransaction.getStatus();
if (status != PendingTransaction.Status.Status_Ok) { if (status != PendingTransaction.Status.Status_Ok) {
String errorText = pendingTransaction.getErrorString(); String errorText = pendingTransaction.getErrorString();
@ -733,6 +726,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
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().isKeyOnDevice())
showLedgerProgressDialog(LedgerProgressDialog.TYPE_SEND);
} else { } else {
Timber.e("Service not bound"); Timber.e("Service not bound");
} }
@ -1049,12 +1044,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
final int id = item.getItemId(); final int id = item.getItemId();
switch (id) { switch (id) {
case R.id.account_new: case R.id.account_new:
getWallet().addAccount(); addAccount();
int newIdx = getWallet().getNumAccounts() - 1;
getWallet().setAccountIndex(newIdx);
Toast.makeText(this,
getString(R.string.accounts_new, newIdx),
Toast.LENGTH_SHORT).show();
break; break;
default: default:
Timber.d("NavigationDrawer ID=%d", id); Timber.d("NavigationDrawer ID=%d", id);
@ -1063,9 +1053,49 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
Timber.d("found @%d", accountIdx); Timber.d("found @%d", accountIdx);
getWallet().setAccountIndex(accountIdx); getWallet().setAccountIndex(accountIdx);
} }
forceUpdate();
drawer.closeDrawer(GravityCompat.START);
} }
forceUpdate();
drawer.closeDrawer(GravityCompat.START);
return true; return true;
} }
private void addAccount() {
new AsyncAddAccount().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR);
}
private class AsyncAddAccount extends AsyncTask<Void, Void, Boolean> {
boolean dialogOpened = false;
@Override
protected void onPreExecute() {
super.onPreExecute();
if (getWallet().isKeyOnDevice()) {
showLedgerProgressDialog(LedgerProgressDialog.TYPE_ACCOUNT);
dialogOpened = true;
} else {
showProgressDialog(R.string.accounts_progress_new);
dialogOpened = true;
}
}
@Override
protected Boolean doInBackground(Void... params) {
if (params.length != 0) return false;
getWallet().addAccount();
getWallet().setAccountIndex(getWallet().getNumAccounts() - 1);
return true;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
forceUpdate();
drawer.closeDrawer(GravityCompat.START);
if (dialogOpened)
dismissProgressDialog();
Toast.makeText(WalletActivity.this,
getString(R.string.accounts_new, getWallet().getNumAccounts() - 1),
Toast.LENGTH_SHORT).show();
}
}
} }

View File

@ -0,0 +1,130 @@
package com.m2049r.xmrwallet.dialog;
/*
* Copyright (C) 2007 The Android Open Source Project
* 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.
*/
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
import java.util.Locale;
import timber.log.Timber;
public class ProgressDialog extends AlertDialog {
private ProgressBar pbBar;
private TextView tvMessage;
private TextView tvProgress;
private View rlProgressBar, pbCircle;
static private final String PROGRESS_FORMAT = "%1d/%2d";
private CharSequence message;
private int maxValue, progressValue;
private boolean indeterminate = true;
public ProgressDialog(Context context) {
super(context);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ledger_progress, null);
pbCircle = view.findViewById(R.id.pbCircle);
tvMessage = (TextView) view.findViewById(R.id.tvMessage);
rlProgressBar = view.findViewById(R.id.rlProgressBar);
pbBar = (ProgressBar) view.findViewById(R.id.pbBar);
tvProgress = (TextView) view.findViewById(R.id.tvProgress);
setView(view);
//setTitle("blabla");
//super.setMessage("bubbu");
// view.invalidate();
setIndeterminate(indeterminate);
if (maxValue > 0) {
setMax(maxValue);
}
if (progressValue > 0) {
setProgress(progressValue);
}
if (message != null) {
Timber.d("msg=%s", message);
setMessage(message);
}
super.onCreate(savedInstanceState);
}
public void setProgress(int value, int max) {
progressValue = value;
maxValue = max;
if (pbBar != null) {
pbBar.setProgress(value);
pbBar.setMax(max);
tvProgress.setText(String.format(Locale.getDefault(), PROGRESS_FORMAT, value, maxValue));
}
}
public void setProgress(int value) {
progressValue = value;
if (pbBar != null) {
pbBar.setProgress(value);
tvProgress.setText(String.format(Locale.getDefault(), PROGRESS_FORMAT, value, maxValue));
}
}
public void setMax(int max) {
maxValue = max;
if (pbBar != null) {
pbBar.setMax(max);
}
}
public void setIndeterminate(boolean indeterminate) {
if (this.indeterminate != indeterminate) {
if (rlProgressBar != null) {
if (indeterminate) {
pbCircle.setVisibility(View.VISIBLE);
rlProgressBar.setVisibility(View.GONE);
} else {
pbCircle.setVisibility(View.GONE);
rlProgressBar.setVisibility(View.VISIBLE);
}
}
this.indeterminate = indeterminate;
}
}
@Override
public void setMessage(CharSequence message) {
this.message = message;
if (tvMessage != null) {
tvMessage.setText(message);
}
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.ledger;
public enum Instruction {
INS_NONE(0x00),
INS_RESET(0x02),
INS_GET_KEY(0x20),
INS_PUT_KEY(0x22),
INS_GET_CHACHA8_PREKEY(0x24),
INS_VERIFY_KEY(0x26),
INS_SECRET_KEY_TO_PUBLIC_KEY(0x30),
INS_GEN_KEY_DERIVATION(0x32),
INS_DERIVATION_TO_SCALAR(0x34),
INS_DERIVE_PUBLIC_KEY(0x36),
INS_DERIVE_SECRET_KEY(0x38),
INS_GEN_KEY_IMAGE(0x3A),
INS_SECRET_KEY_ADD(0x3C),
INS_SECRET_KEY_SUB(0x3E),
INS_GENERATE_KEYPAIR(0x40),
INS_SECRET_SCAL_MUL_KEY(0x42),
INS_SECRET_SCAL_MUL_BASE(0x44),
INS_DERIVE_SUBADDRESS_PUBLIC_KEY(0x46),
INS_GET_SUBADDRESS(0x48),
INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY(0x4A),
INS_GET_SUBADDRESS_SECRET_KEY(0x4C),
INS_OPEN_TX(0x70),
INS_SET_SIGNATURE_MODE(0x72),
INS_GET_ADDITIONAL_KEY(0x74),
INS_STEALTH(0x76),
INS_BLIND(0x78),
INS_UNBLIND(0x7A),
INS_VALIDATE(0x7C),
INS_MLSAG(0x7E),
INS_CLOSE_TX(0x80),
INS_GET_RESPONSE(0xc0),
INS_UNDEFINED(0xff);;
public static Instruction fromByte(byte n) {
switch (n & 0xFF) {
case 0x00:
return INS_NONE;
case 0x02:
return INS_RESET;
case 0x20:
return INS_GET_KEY;
case 0x22:
return INS_PUT_KEY;
case 0x24:
return INS_GET_CHACHA8_PREKEY;
case 0x26:
return INS_VERIFY_KEY;
case 0x30:
return INS_SECRET_KEY_TO_PUBLIC_KEY;
case 0x32:
return INS_GEN_KEY_DERIVATION;
case 0x34:
return INS_DERIVATION_TO_SCALAR;
case 0x36:
return INS_DERIVE_PUBLIC_KEY;
case 0x38:
return INS_DERIVE_SECRET_KEY;
case 0x3A:
return INS_GEN_KEY_IMAGE;
case 0x3C:
return INS_SECRET_KEY_ADD;
case 0x3E:
return INS_SECRET_KEY_SUB;
case 0x40:
return INS_GENERATE_KEYPAIR;
case 0x42:
return INS_SECRET_SCAL_MUL_KEY;
case 0x44:
return INS_SECRET_SCAL_MUL_BASE;
case 0x46:
return INS_DERIVE_SUBADDRESS_PUBLIC_KEY;
case 0x48:
return INS_GET_SUBADDRESS;
case 0x4A:
return INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY;
case 0x4C:
return INS_GET_SUBADDRESS_SECRET_KEY;
case 0x70:
return INS_OPEN_TX;
case 0x72:
return INS_SET_SIGNATURE_MODE;
case 0x74:
return INS_GET_ADDITIONAL_KEY;
case 0x76:
return INS_STEALTH;
case 0x78:
return INS_BLIND;
case 0x7A:
return INS_UNBLIND;
case 0x7C:
return INS_VALIDATE;
case 0x7E:
return INS_MLSAG;
case 0x80:
return INS_CLOSE_TX;
case 0xc0:
return INS_GET_RESPONSE;
default:
return INS_UNDEFINED;
}
}
public int getValue() {
return value;
}
private int value;
Instruction(int value) {
this.value = value;
}
}

View File

@ -0,0 +1,272 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
* (c) 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.ledger;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import com.btchip.BTChipException;
import com.btchip.comm.BTChipTransport;
import com.btchip.comm.android.BTChipTransportAndroidHID;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.util.Helper;
import java.io.IOException;
import timber.log.Timber;
public class Ledger {
// lookahead parameters as suggest on
// https://monero.stackexchange.com/a/9902/8977 (Step 8)
// by dEBRUYNE
static public final int LOOKAHEAD_ACCOUNTS = 3;
static public final int LOOKAHEAD_SUBADDRESSES = 100;
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
public static final int SW_OK = 0x9000;
public static final int SW_INS_NOT_SUPPORTED = 0x6D00;
public static final int OK[] = {SW_OK};
public static UsbDevice findDevice(UsbManager usbManager) {
return BTChipTransportAndroidHID.getDevice(usbManager);
}
static private Ledger Instance = null;
static public String connect(UsbManager usbManager, UsbDevice usbDevice) throws IOException {
if (Instance != null) {
disconnect();
}
Instance = new Ledger(usbManager, usbDevice);
return Name();
}
static public void disconnect() {
// this is not synchronized so as to close immediately
if (Instance != null) {
Instance.close();
Instance = null;
}
}
static public boolean isConnected() {
//TODO synchronize with connect/disconnect?
return Instance != null;
}
static public String Name() {
if (Instance != null) {
return Instance.name;
} else {
return null;
}
}
static public byte[] Exchange(byte[] apdu) {
if (Instance != null) {
Timber.d("INS: %s", Instruction.fromByte(apdu[1]));
return Instance.exchangeRaw(apdu);
} else {
return null;
}
}
final private BTChipTransport transport;
final private String name;
private int lastSW = 0;
private Ledger(UsbManager usbManager, UsbDevice usbDevice) throws IOException {
final BTChipTransport transport = BTChipTransportAndroidHID.open(usbManager, usbDevice);
Timber.d("transport opened = %s", transport.toString());
transport.setDebug(BuildConfig.DEBUG);
this.transport = transport;
this.name = usbDevice.getManufacturerName() + " " + usbDevice.getProductName();
initKey();
}
synchronized private void close() {
initKey(); // don't leak key after we disconnect
transport.close();
Timber.d("transport closed");
lastSW = 0;
}
synchronized private byte[] exchangeRaw(byte[] apdu) {
if (transport == null)
throw new IllegalStateException("No transport (probably closed previously)");
Timber.i("exchangeRaw %02x", apdu[1]);
Instruction ins = Instruction.fromByte(apdu[1]);
if (listener != null) listener.onInstructionSend(ins, apdu);
sniffOut(ins, apdu);
byte[] data = transport.exchange(apdu);
if (listener != null) listener.onInstructionReceive(ins, data);
sniffIn(data);
return data;
}
private byte[] exchange(byte[] apdu) throws BTChipException {
byte[] response = exchangeRaw(apdu);
if (response.length < 2) {
throw new BTChipException("Truncated response");
}
lastSW = ((int) (response[response.length - 2] & 0xff) << 8) |
(int) (response[response.length - 1] & 0xff);
byte[] result = new byte[response.length - 2];
System.arraycopy(response, 0, result, 0, response.length - 2);
return result;
}
private byte[] exchangeCheck(byte[] apdu, int acceptedSW[]) throws BTChipException {
byte[] response = exchange(apdu);
if (acceptedSW == null) {
return response;
}
for (int SW : acceptedSW) {
if (lastSW == SW) {
return response;
}
}
throw new BTChipException("Invalid status", lastSW);
}
private byte[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
byte[] apdu = new byte[data.length + 5];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte) (data.length);
System.arraycopy(data, 0, apdu, 5, data.length);
return exchangeCheck(apdu, acceptedSW);
}
private byte[] exchangeApdu(byte cla, byte ins, byte p1, byte p2, int length, int acceptedSW[]) throws BTChipException {
byte[] apdu = new byte[5];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte) (length);
return exchangeCheck(apdu, acceptedSW);
}
private byte[] exchangeApduSplit(byte cla, byte ins, byte p1, byte p2, byte[] data, int acceptedSW[]) throws BTChipException {
int offset = 0;
byte[] result = null;
while (offset < data.length) {
int blockLength = ((data.length - offset) > 255 ? 255 : data.length - offset);
byte[] apdu = new byte[blockLength + 5];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte) (blockLength);
System.arraycopy(data, offset, apdu, 5, blockLength);
result = exchangeCheck(apdu, acceptedSW);
offset += blockLength;
}
return result;
}
private byte[] exchangeApduSplit2(byte cla, byte ins, byte p1, byte p2, byte[] data, byte[] data2, int acceptedSW[]) throws BTChipException {
int offset = 0;
byte[] result = null;
int maxBlockSize = 255 - data2.length;
while (offset < data.length) {
int blockLength = ((data.length - offset) > maxBlockSize ? maxBlockSize : data.length - offset);
boolean lastBlock = ((offset + blockLength) == data.length);
byte[] apdu = new byte[blockLength + 5 + (lastBlock ? data2.length : 0)];
apdu[0] = cla;
apdu[1] = ins;
apdu[2] = p1;
apdu[3] = p2;
apdu[4] = (byte) (blockLength + (lastBlock ? data2.length : 0));
System.arraycopy(data, offset, apdu, 5, blockLength);
if (lastBlock) {
System.arraycopy(data2, 0, apdu, 5 + blockLength, data2.length);
}
result = exchangeCheck(apdu, acceptedSW);
offset += blockLength;
}
return result;
}
public interface Listener {
void onInstructionSend(Instruction ins, byte[] apdu);
void onInstructionReceive(Instruction ins, byte[] data);
}
Listener listener;
static public void setListener(Listener listener) {
if (Instance != null) {
Instance.listener = listener;
}
}
static public void unsetListener(Listener listener) {
if ((Instance != null) && (Instance.listener == listener))
Instance.listener = null;
}
// very stupid hack to extract the view key
// without messing around with monero core code
// NB: as all the ledger comm can be sniffed off the USB cable - there is no security issue here
private boolean snoopKey = false;
private byte[] key;
private void initKey() {
key = Helper.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
}
static public String Key() {
if (Instance != null) {
return Helper.bytesToHex(Instance.key).toLowerCase();
} else {
return null;
}
}
private void sniffOut(Instruction ins, byte[] apdu) {
if (ins == Instruction.INS_GET_KEY) {
snoopKey = (apdu[2] == 2);
}
}
private void sniffIn(byte[] data) {
// stupid hack to extract the view key
// without messing around with monero core code
if (snoopKey) {
if (data.length == 34) { // 32 key + result code 9000
long sw = ((data[data.length - 2] & 0xff) << 8) |
(data[data.length - 1] & 0xff);
Timber.e("WS %d", sw);
if (sw == SW_OK) {
System.arraycopy(data, 0, key, 0, 32);
}
}
snoopKey = false;
}
}
}

View File

@ -0,0 +1,162 @@
package com.m2049r.xmrwallet.ledger;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.dialog.ProgressDialog;
import com.m2049r.xmrwallet.model.WalletManager;
import timber.log.Timber;
public class LedgerProgressDialog extends ProgressDialog implements Ledger.Listener {
static public final int TYPE_DEBUG = 0;
static public final int TYPE_RESTORE = 1;
static public final int TYPE_SUBADDRESS = 2;
static public final int TYPE_ACCOUNT = 3;
static public final int TYPE_SEND = 4;
private final int type;
private Handler uiHandler = new Handler(Looper.getMainLooper());
public LedgerProgressDialog(Context context, int type) {
super(context);
this.type = type;
setCancelable(false);
if (type == TYPE_SEND)
setMessage(context.getString(R.string.info_prepare_tx));
else
setMessage(context.getString(R.string.progress_ledger_progress));
}
@Override
public void onBackPressed() {
// prevent back button
}
private int firstSubaddress = Integer.MAX_VALUE;
private boolean validate = false;
private boolean validated = false;
@Override
public void onInstructionSend(final Instruction ins, final byte[] apdu) {
Timber.d("LedgerProgressDialog SEND %s", ins);
uiHandler.post(new Runnable() {
@Override
public void run() {
if (type > TYPE_DEBUG) {
validate = false;
switch (ins) {
case INS_RESET: // ledger may ask for confirmation - maybe a bug?
case INS_GET_KEY: // ledger asks for confirmation to send keys
setIndeterminate(true);
setMessage(getContext().getString(R.string.progress_ledger_confirm));
break;
case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: // lookahead
//00 4a 00 00 09 00 01000000 30000000
// 0 1 2 3 4 5 6 7 8 9 a b c d
int account = bytesToInteger(apdu, 6);
int subaddress = bytesToInteger(apdu, 10);
Timber.d("fetching subaddress (%d, %d)", account, subaddress);
switch (type) {
case TYPE_RESTORE:
setProgress(account * Ledger.LOOKAHEAD_SUBADDRESSES + subaddress + 1,
Ledger.LOOKAHEAD_ACCOUNTS * Ledger.LOOKAHEAD_SUBADDRESSES);
setIndeterminate(false);
break;
case TYPE_ACCOUNT:
final int requestedSubaddress = account * Ledger.LOOKAHEAD_SUBADDRESSES + subaddress;
if (firstSubaddress > requestedSubaddress) {
firstSubaddress = requestedSubaddress;
}
setProgress(requestedSubaddress - firstSubaddress + 1,
Ledger.LOOKAHEAD_ACCOUNTS * Ledger.LOOKAHEAD_SUBADDRESSES);
setIndeterminate(false);
break;
case TYPE_SUBADDRESS:
if (firstSubaddress > subaddress) {
firstSubaddress = subaddress;
}
setProgress(subaddress - firstSubaddress + 1, Ledger.LOOKAHEAD_SUBADDRESSES);
setIndeterminate(false);
break;
default:
setIndeterminate(true);
break;
}
setMessage(getContext().getString(R.string.progress_ledger_lookahead));
break;
case INS_VERIFY_KEY:
setIndeterminate(true);
setMessage(getContext().getString(R.string.progress_ledger_verify));
break;
case INS_OPEN_TX:
setIndeterminate(true);
setMessage(getContext().getString(R.string.progress_ledger_opentx));
break;
case INS_MLSAG:
if (validated) {
setIndeterminate(true);
setMessage(getContext().getString(R.string.progress_ledger_mlsag));
}
break;
case INS_VALIDATE:
if ((apdu[2] != 1) || (apdu[3] != 1)) break;
validate = true;
uiHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (validate) {
setIndeterminate(true);
setMessage(getContext().getString(R.string.progress_ledger_confirm));
validated = true;
}
}
}, 250);
break;
default:
// ignore others and maintain state
}
} else {
setMessage(ins.name());
}
}
});
}
@Override
public void onInstructionReceive(final Instruction ins, final byte[] data) {
Timber.d("LedgerProgressDialog RECV %s", ins);
uiHandler.post(new Runnable() {
@Override
public void run() {
if (type > TYPE_DEBUG) {
switch (ins) {
case INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY: // lookahead
case INS_VERIFY_KEY:
case INS_GET_CHACHA8_PREKEY:
break;
default:
if (type != TYPE_SEND)
setMessage(getContext().getString(R.string.progress_ledger_progress));
}
} else {
setMessage("Returned from " + ins.name());
}
}
});
}
// TODO: we use ints in Java but the are signed; accounts & subaddresses are unsigned ...
private int bytesToInteger(byte[] bytes, int offset) {
int result = 0;
for (int i = 3; i >= 0; i--) {
result <<= 8;
result |= (bytes[offset + i] & 0xFF);
}
return result;
}
}

View File

@ -389,4 +389,5 @@ public class Wallet {
return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1); return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1);
} }
public native boolean isKeyOnDevice();
} }

View File

@ -17,6 +17,7 @@
package com.m2049r.xmrwallet.model; package com.m2049r.xmrwallet.model;
import com.m2049r.xmrwallet.data.WalletNode; import com.m2049r.xmrwallet.data.WalletNode;
import com.m2049r.xmrwallet.ledger.Ledger;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@ -129,6 +130,23 @@ public class WalletManager {
String viewKeyString, String viewKeyString,
String spendKeyString); String spendKeyString);
public Wallet createWalletFromDevice(File aFile, String password, long restoreHeight,
String deviceName) {
long walletHandle = createWalletFromDeviceJ(aFile.getAbsolutePath(), password,
getNetworkType().getValue(), deviceName, restoreHeight,
Ledger.SUBADDRESS_LOOKAHEAD);
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet);
return wallet;
}
private native long createWalletFromDeviceJ(String path, String password,
int networkType,
String deviceName,
long restoreHeight,
String subaddressLookahead);
public native boolean closeJ(Wallet wallet); public native boolean closeJ(Wallet wallet);
public boolean close(Wallet wallet) { public boolean close(Wallet wallet) {
@ -150,6 +168,12 @@ public class WalletManager {
public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only); public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
public boolean verifyWalletPasswordOnly(String keys_file_name, String password) {
return queryWalletHardware(keys_file_name, password) >= 0;
}
public native int queryWalletHardware(String keys_file_name, String password);
//public native List<String> findWallets(String path); // this does not work - some error in boost //public native List<String> findWallets(String path); // this does not work - some error in boost
public class WalletInfo implements Comparable<WalletInfo> { public class WalletInfo implements Comparable<WalletInfo> {

View File

@ -221,6 +221,8 @@ public class WalletService extends Service {
void onSetNotes(boolean success); void onSetNotes(boolean success);
void onWalletStarted(boolean success); void onWalletStarted(boolean success);
void onWalletOpen(int hardware);
} }
String progressText = null; String progressText = null;
@ -535,6 +537,8 @@ public class WalletService extends Service {
showProgress(30); showProgress(30);
if (walletMgr.walletExists(path)) { if (walletMgr.walletExists(path)) {
Timber.d("open wallet %s", path); Timber.d("open wallet %s", path);
int hw = WalletManager.getInstance().queryWalletHardware(path + ".keys", walletPassword);
if (observer != null) observer.onWalletOpen(hw);
wallet = walletMgr.openWallet(path, walletPassword); wallet = walletMgr.openWallet(path, walletPassword);
showProgress(60); showProgress(60);
Timber.d("wallet opened"); Timber.d("wallet opened");

View File

@ -349,33 +349,33 @@ public class Helper {
String walletPath = new File(getWalletRoot(context), walletName + ".keys").getAbsolutePath(); String walletPath = new File(getWalletRoot(context), walletName + ".keys").getAbsolutePath();
// try with entered password (which could be a legacy password or a CrAzYpass) // try with entered password (which could be a legacy password or a CrAzYpass)
if (WalletManager.getInstance().verifyWalletPassword(walletPath, password, true)) { if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, password)) {
return password; return password;
} }
// maybe this is a malformed CrAzYpass? // maybe this is a malformed CrAzYpass?
String possibleCrazyPass = CrazyPassEncoder.reformat(password); String possibleCrazyPass = CrazyPassEncoder.reformat(password);
if (possibleCrazyPass != null) { // looks like a CrAzYpass if (possibleCrazyPass != null) { // looks like a CrAzYpass
if (WalletManager.getInstance().verifyWalletPassword(walletPath, possibleCrazyPass, true)) { if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, possibleCrazyPass)) {
return possibleCrazyPass; return possibleCrazyPass;
} }
} }
// generate & try with CrAzYpass // generate & try with CrAzYpass
String crazyPass = KeyStoreHelper.getCrazyPass(context, password); String crazyPass = KeyStoreHelper.getCrazyPass(context, password);
if (WalletManager.getInstance().verifyWalletPassword(walletPath, crazyPass, true)) { if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, crazyPass)) {
return crazyPass; return crazyPass;
} }
// or maybe it is a broken CrAzYpass? (of which we have two variants) // or maybe it is a broken CrAzYpass? (of which we have two variants)
String brokenCrazyPass2 = KeyStoreHelper.getBrokenCrazyPass(context, password, 2); String brokenCrazyPass2 = KeyStoreHelper.getBrokenCrazyPass(context, password, 2);
if ((brokenCrazyPass2 != null) if ((brokenCrazyPass2 != null)
&& WalletManager.getInstance().verifyWalletPassword(walletPath, brokenCrazyPass2, true)) { && WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, brokenCrazyPass2)) {
return brokenCrazyPass2; return brokenCrazyPass2;
} }
String brokenCrazyPass1 = KeyStoreHelper.getBrokenCrazyPass(context, password, 1); String brokenCrazyPass1 = KeyStoreHelper.getBrokenCrazyPass(context, password, 1);
if ((brokenCrazyPass1 != null) if ((brokenCrazyPass1 != null)
&& WalletManager.getInstance().verifyWalletPassword(walletPath, brokenCrazyPass1, true)) { && WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, brokenCrazyPass1)) {
return brokenCrazyPass1; return brokenCrazyPass1;
} }
@ -407,6 +407,7 @@ public class Helper {
final CancellationSignal cancelSignal = new CancellationSignal(); final CancellationSignal cancelSignal = new CancellationSignal();
final AtomicBoolean incorrectSavedPass = new AtomicBoolean(false); final AtomicBoolean incorrectSavedPass = new AtomicBoolean(false);
class LoginWalletTask extends AsyncTask<Void, Void, Boolean> { class LoginWalletTask extends AsyncTask<Void, Void, Boolean> {
private String pass; private String pass;
private boolean fingerprintUsed; private boolean fingerprintUsed;
@ -594,6 +595,5 @@ public class Helper {
static public ExchangeApi getExchangeApi() { static public ExchangeApi getExchangeApi() {
return new com.m2049r.xmrwallet.service.exchange.coinmarketcap.ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient()); return new com.m2049r.xmrwallet.service.exchange.coinmarketcap.ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient());
} }
} }

View File

@ -33,6 +33,8 @@ import com.m2049r.xmrwallet.dialog.HelpFragment;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import timber.log.Timber;
public class Notice { public class Notice {
private static final String PREFS_NAME = "notice"; private static final String PREFS_NAME = "notice";
private static List<Notice> notices = null; private static List<Notice> notices = null;
@ -40,6 +42,7 @@ public class Notice {
private static final String NOTICE_SHOW_XMRTO_ENABLED_LOGIN = "notice_xmrto_enabled_login"; private static final String NOTICE_SHOW_XMRTO_ENABLED_LOGIN = "notice_xmrto_enabled_login";
private static final String NOTICE_SHOW_XMRTO_ENABLED_SEND = "notice_xmrto_enabled_send"; private static final String NOTICE_SHOW_XMRTO_ENABLED_SEND = "notice_xmrto_enabled_send";
private static final String NOTICE_SHOW_CRAZYPASS = "notice_crazypass_enabled_login"; private static final String NOTICE_SHOW_CRAZYPASS = "notice_crazypass_enabled_login";
private static final String NOTICE_SHOW_LEDGER = "notice_ledger_enabled_login";
private static void init() { private static void init() {
synchronized (Notice.class) { synchronized (Notice.class) {
@ -63,6 +66,12 @@ public class Notice {
R.string.help_details, R.string.help_details,
2) 2)
); );
notices.add(
new Notice(NOTICE_SHOW_LEDGER,
R.string.info_ledger_enabled,
R.string.help_create_ledger,
1)
);
} }
} }

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" <set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"> android:fillAfter="false">
<scale <scale
android:duration="300" android:duration="300"
android:fromXScale="1.0" android:fromXScale="1.0"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" <set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"> android:fillAfter="false">
<scale <scale
android:duration="300" android:duration="300"
android:fromXScale="0.0" android:fromXScale="0.0"

View File

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="8.36dp"
android:viewportHeight="184.59"
android:viewportWidth="529.82">
<path
android:fillColor="#fff"
android:pathData="M517.82,0h-48a99.43,99.43 0,0 1,60 63.84L529.82,12A12,12 0,0 0,517.82 0Z" />
<path
android:fillColor="#fff"
android:pathData="M471.85,184.59h46a12,12 0,0 0,12 -12v-51.07A99.44,99.44 0,0 1,471.85 184.59Z" />
<path
android:fillColor="#fff"
android:pathData="M438.12,0.04v0L12,0.04a12,12 0,0 0,-12 12v160.59a12,12 0,0 0,12 12h435.25a92.69,92.69 0,0 0,-9.13 -184.54ZM435.12,142.33a49.64,49.64 0,1 1,49.64 -49.64A49.64,49.64 0,0 1,435.12 142.33Z" />
</vector>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="4dp"
android:paddingTop="4dp">
<ProgressBar
android:id="@+id/pbCircle"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:visibility="visible" />
<TextView
android:id="@+id/tvMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
tools:text="A progress text" />
</LinearLayout>
<RelativeLayout
android:id="@+id/rlProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<ProgressBar
android:id="@+id/pbBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="1dp" />
<TextView
android:id="@+id/tvProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@+id/pbBar"
android:layout_below="@id/pbBar"
android:paddingBottom="4dp"
tools:text="123/600" />
</RelativeLayout>
</LinearLayout>

View File

@ -51,10 +51,13 @@
android:textAlignment="center" android:textAlignment="center"
tools:text="49RBjxQ2zgf7t17w7So9ngcEY9obKzsrr6Dsah24MNSMiMBEeiYPP5CCTBq4GpZcEYN5Zf3upsLiwd5PezePE1i4Tf3rryY" /> tools:text="49RBjxQ2zgf7t17w7So9ngcEY9obKzsrr6Dsah24MNSMiMBEeiYPP5CCTBq4GpZcEYN5Zf3upsLiwd5PezePE1i4Tf3rryY" />
<FrameLayout <LinearLayout
android:id="@+id/llMnemonic"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/header_top"> android:layout_marginTop="@dimen/header_top"
android:orientation="vertical"
android:visibility="gone">
<TextView <TextView
style="@style/MoneroLabel.Heading" style="@style/MoneroLabel.Heading"
@ -62,17 +65,17 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:text="@string/generate_mnemonic_label" /> android:text="@string/generate_mnemonic_label" />
</FrameLayout>
<TextView <TextView
android:id="@+id/tvWalletMnemonic" android:id="@+id/tvWalletMnemonic"
style="@style/MoneroText.Monospace.Mnemonic" style="@style/MoneroText.Monospace.Mnemonic"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/data_top" android:layout_marginTop="@dimen/data_top"
android:background="@drawable/backgound_seed" android:background="@drawable/backgound_seed"
android:textAlignment="center" android:textAlignment="center"
tools:text="tucks slackens vehicle doctor oaks aloof balding knife rays wise haggled cuisine navy ladder suitcase dusted last thorn pixels karate ticket nibs violin zapped slackens" /> tools:text="tucks slackens vehicle doctor oaks aloof balding knife rays wise haggled cuisine navy ladder suitcase dusted last thorn pixels karate ticket nibs violin zapped slackens" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/llPassword" android:id="@+id/llPassword"
@ -126,57 +129,73 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"
android:layout_marginTop="@dimen/section_top" android:layout_marginTop="@dimen/data_top"
android:orientation="vertical" android:orientation="vertical"
android:visibility="gone"> android:visibility="gone">
<FrameLayout <LinearLayout
android:id="@+id/llViewKey"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:layout_marginTop="@dimen/header_top"
android:orientation="vertical"
android:visibility="gone">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
style="@style/MoneroLabel.Heading"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:text="@string/generate_viewkey_label" />
<ImageButton
android:id="@+id/bCopyViewKey"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginEnd="80dp"
android:background="?android:selectableItemBackground"
android:src="@drawable/ic_content_copy_black_24dp" />
</FrameLayout>
<TextView
android:id="@+id/tvWalletViewKey"
style="@style/MoneroText.Monospace"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/data_top"
android:textAlignment="center"
tools:text="e4aba454d78799dbd8d576bf70e7f15a06e91f1ecfd404053f91519a48df2a0e" />
</LinearLayout>
<LinearLayout
android:id="@+id/llSpendKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/header_top"
android:orientation="vertical"
android:visibility="gone">
<TextView <TextView
style="@style/MoneroLabel.Heading" style="@style/MoneroLabel.Heading"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center" android:layout_gravity="center"
android:text="@string/generate_viewkey_label" /> android:text="@string/generate_spendkey_label" />
<ImageButton <TextView
android:id="@+id/bCopyViewKey" android:id="@+id/tvWalletSpendKey"
android:layout_width="wrap_content" style="@style/MoneroText.Monospace"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_marginTop="@dimen/data_top"
android:layout_marginEnd="80dp" android:textAlignment="center"
android:background="?android:selectableItemBackground" tools:text="2bb6afd03f1a4b91e619236b9d342aeb6d232fa50e5b9b91a00db26d1cafaa08" />
android:src="@drawable/ic_content_copy_black_24dp" /> </LinearLayout>
</FrameLayout>
<TextView
android:id="@+id/tvWalletViewKey"
style="@style/MoneroText.Monospace"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/data_top"
android:textAlignment="center"
tools:text="e4aba454d78799dbd8d576bf70e7f15a06e91f1ecfd404053f91519a48df2a0e" />
<TextView
style="@style/MoneroLabel.Heading"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginTop="@dimen/header_top"
android:text="@string/generate_spendkey_label" />
<TextView
android:id="@+id/tvWalletSpendKey"
style="@style/MoneroText.Monospace"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/data_top"
android:textAlignment="center"
tools:text="4925a6254d2dff71df53f13b8334f9ceda6cdd5821aa895f7de1d2640500740d" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -139,6 +139,39 @@
</RelativeLayout> </RelativeLayout>
<RelativeLayout
android:id="@+id/fabLedgerL"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginBottom="88dp"
android:layout_marginEnd="16dp"
android:visibility="gone">
<TextView
android:id="@+id/fabLedgerT"
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_ledger" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabLedger"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_toEndOf="@+id/fabLedgerT"
android:backgroundTint="@color/gradientPink"
android:elevation="6dp"
android:src="@drawable/ic_ledger_restore"
app:borderWidth="0dp"
app:fabSize="mini"
app:pressedTranslationZ="12dp" />
</RelativeLayout>
</FrameLayout> </FrameLayout>
<LinearLayout <LinearLayout

View File

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

View File

@ -204,7 +204,7 @@
<string name="help_xmrto"><![CDATA[ <string name="help_xmrto"><![CDATA[
<h1>BTC senden</h1> <h1>BTC senden</h1>
<h2>XMR.TO</h2> <h2>XMR.TO</h2>
<p>XXMR.TO ist ein Drittanbieter-Service, der als Wechselservice von Monero zu Bitcoin fungiert. <p>XMR.TO ist ein Drittanbieter-Service, der als Wechselservice von Monero zu Bitcoin fungiert.
Wir verwenden die XMR.TO Schnittstelle, um Bitcoin-Zahlungen in Monerujo zu integrieren. Bitte sieh dir Wir verwenden die XMR.TO Schnittstelle, um Bitcoin-Zahlungen in Monerujo zu integrieren. Bitte sieh dir
https://xmr.to an und entscheide selbst, ob es etwas ist, was du verwenden möchtest. Das Monerujo https://xmr.to an und entscheide selbst, ob es etwas ist, was du verwenden möchtest. Das Monerujo
Team ist nicht mit XMR.TO verbunden und kann dir bei deren Service nicht helfen.</p> Team ist nicht mit XMR.TO verbunden und kann dir bei deren Service nicht helfen.</p>
@ -227,4 +227,15 @@
indem du zum vorherigen Schritt zurückgehst und dann zum Bildschirm "Bestätigen" zurückkehrst.</p> indem du zum vorherigen Schritt zurückgehst und dann zum Bildschirm "Bestätigen" zurückkehrst.</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -34,6 +34,7 @@
<string name="info_xmrto_enabled">BTC Zahlung aktiviert - Tippe für mehr Infos.</string> <string name="info_xmrto_enabled">BTC Zahlung aktiviert - Tippe für mehr Infos.</string>
<string name="info_crazypass_enabled">CrAzYpass aktiviert - Tippe für mehr Infos.</string> <string name="info_crazypass_enabled">CrAzYpass aktiviert - Tippe für mehr Infos.</string>
<string name="info_ledger_enabled">Ledger aktiviert - Tippe für mehr Infos.</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>Du hast eine BTC Adresse eingegeben.</b><br/> <b>Du hast eine BTC Adresse eingegeben.</b><br/>
@ -321,4 +322,20 @@
<string name="menu_language">Sprache</string> <string name="menu_language">Sprache</string>
<string name="language_system_default">Benutze Systemsprache</string> <string name="language_system_default">Benutze Systemsprache</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -213,4 +213,15 @@
πίσω στο προηγούμενο βήμα και μετά επιστρέφοντας στην οθόνη \"Επιβεβαίωση\".</p> πίσω στο προηγούμενο βήμα και μετά επιστρέφοντας στην οθόνη \"Επιβεβαίωση\".</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -32,6 +32,8 @@
<string name="info_send_prio_fees">Υψηλότερη προτεραιότητα = Υψηλότερα Κόμιστρα</string> <string name="info_send_prio_fees">Υψηλότερη προτεραιότητα = Υψηλότερα Κόμιστρα</string>
<string name="info_xmrto_enabled">Συναλλαγή BTC ενεργοποιήθηκε, πάτα για περισσότερες πληροφορείες.</string> <string name="info_xmrto_enabled">Συναλλαγή BTC ενεργοποιήθηκε, πάτα για περισσότερες πληροφορείες.</string>
<string name="info_crazypass_enabled">CrAzYpass ενεργοποιήθηκε, πάτα για περισσότερες πληροφορείες.</string>
<string name="info_ledger_enabled">Ledger ενεργοποιήθηκε, πάτα για περισσότερες πληροφορείες.</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>Έβαλες μια διεύθυνση bitcoin.</b><br/> <b>Έβαλες μια διεύθυνση bitcoin.</b><br/>
@ -284,7 +286,6 @@
<string name="fab_restore_seed">Επαναφορά πορτοφολιού από σπόρο 25-λέξεων</string> <string name="fab_restore_seed">Επαναφορά πορτοφολιού από σπόρο 25-λέξεων</string>
<string name="menu_changepw">Change Passphrase</string> <string name="menu_changepw">Change Passphrase</string>
<string name="info_crazypass_enabled">CrAzYpass enabled, tap for more info.</string>
<string name="changepw_progress">Change Password in progress</string> <string name="changepw_progress">Change Password in progress</string>
<string name="changepw_failed">Change Password failed!</string> <string name="changepw_failed">Change Password failed!</string>
<string name="changepw_success">Password changed</string> <string name="changepw_success">Password changed</string>
@ -320,4 +321,20 @@
<string name="menu_language">Language</string> <string name="menu_language">Language</string>
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -251,4 +251,16 @@
parte de XMR.TO, esto se logra dando un paso atrás y luego volviendo a la pantalla de parte de XMR.TO, esto se logra dando un paso atrás y luego volviendo a la pantalla de
\"Confirmar\".</p> \"Confirmar\".</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -292,8 +292,10 @@
<string name="tx_amount_btc">Monto\n(BTC)</string> <string name="tx_amount_btc">Monto\n(BTC)</string>
<string name="about_whoami">Soy monerujo</string> <string name="about_whoami">Soy monerujo</string>
<string name="info_send_xmrto_success_order_label">Orden XMR.TO</string> <string name="info_send_xmrto_success_order_label">Orden XMR.TO</string>
<string name="info_xmrto_enabled">Pago en BTC activado, toca para más info.</string> <string name="info_xmrto_enabled">Pago en BTC activado, toca para más info.</string>
<string name="info_crazypass_enabled">CrAzYpass activado, toca para más info.</string> <string name="info_crazypass_enabled">CrAzYpass activado, toca para más info.</string>
<string name="info_ledger_enabled">Ledger activado, toca para más info.</string>
<string name="accounts_drawer_new">Crear Cuenta</string> <string name="accounts_drawer_new">Crear Cuenta</string>
<string name="accounts_drawer_title">Cuentas</string> <string name="accounts_drawer_title">Cuentas</string>
@ -306,4 +308,20 @@
<string name="menu_language">Language</string> <string name="menu_language">Language</string>
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -244,4 +244,15 @@
XMR.TO en retournant à létape précédente puis en revenant à lécran \"Confirmation\".</p> XMR.TO en retournant à létape précédente puis en revenant à lécran \"Confirmation\".</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -33,8 +33,8 @@
<string name="info_send_prio_fees">Plus Prioritaire = Plus de Frais</string> <string name="info_send_prio_fees">Plus Prioritaire = Plus de Frais</string>
<string name="info_xmrto_enabled">Paiement BTC activé, tapez pour plus d\'infos.</string> <string name="info_xmrto_enabled">Paiement BTC activé, tapez pour plus d\'infos.</string>
<string name="info_crazypass_enabled">CrAzYpass activé, tapez pour plus d\'infos.</string> <string name="info_crazypass_enabled">CrAzYpass activé, tapez pour plus d\'infos.</string>
<string name="info_ledger_enabled">Ledger activé, tapez pour plus d\'infos.</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>Vous avez entré une adresse Bitcoin.</b><br/> <b>Vous avez entré une adresse Bitcoin.</b><br/>
@ -324,4 +324,20 @@
<string name="menu_language">Language</string> <string name="menu_language">Language</string>
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -234,4 +234,15 @@
visszamész az előző lépésre, majd visszajössz a Megerősítés oldalra.</p> visszamész az előző lépésre, majd visszajössz a Megerősítés oldalra.</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -34,6 +34,7 @@
<string name="info_xmrto_enabled">BTC fizetés engedélyezve, koppints ide a részletekért.</string> <string name="info_xmrto_enabled">BTC fizetés engedélyezve, koppints ide a részletekért.</string>
<string name="info_crazypass_enabled">CrAzYpass engedélyezve, koppints ide a részletekért</string> <string name="info_crazypass_enabled">CrAzYpass engedélyezve, koppints ide a részletekért</string>
<string name="info_ledger_enabled">Ledger engedélyezve, koppints ide a részletekért</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>Bitcoin-címet adtál meg.</b><br/> <b>Bitcoin-címet adtál meg.</b><br/>
@ -44,7 +45,6 @@
<string name="info_send_xmrto_success_btc">%1$s BTC</string> <string name="info_send_xmrto_success_btc">%1$s BTC</string>
<string name="info_send_xmrto_paid">Megerősítés folyamatban</string> <string name="info_send_xmrto_paid">Megerősítés folyamatban</string>
<string name="info_send_xmrto_unpaid">Kifizetés folyamatban</string> <string name="info_send_xmrto_unpaid">Kifizetés folyamatban</string>
<string name="info_send_xmrto_error">XMR.TO-hiba (%1$s)</string> <string name="info_send_xmrto_error">XMR.TO-hiba (%1$s)</string>
@ -322,4 +322,20 @@
<string name="menu_language">Nyelv</string> <string name="menu_language">Nyelv</string>
<string name="language_system_default">Rendszernyelv használata</string> <string name="language_system_default">Rendszernyelv használata</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -177,4 +177,15 @@
<p>Non appena il conto alla rovescia arriva a zero, è necessario richiedere una nuova quotazione a XMR.TO tornando indietro al passo precedente e tornando poi di nuovo alla schermata \"Conferma\".</p> <p>Non appena il conto alla rovescia arriva a zero, è necessario richiedere una nuova quotazione a XMR.TO tornando indietro al passo precedente e tornando poi di nuovo alla schermata \"Conferma\".</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -34,6 +34,7 @@
<string name="info_xmrto_enabled">Pagamento BTC abilitato, tocca per maggiori informazioni.</string> <string name="info_xmrto_enabled">Pagamento BTC abilitato, tocca per maggiori informazioni.</string>
<string name="info_crazypass_enabled">CrAzYpass abilitato, tocca per maggiori informazioni.</string> <string name="info_crazypass_enabled">CrAzYpass abilitato, tocca per maggiori informazioni.</string>
<string name="info_ledger_enabled">Ledger abilitato, tocca per maggiori informazioni.</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>Hai inserito un indirizzo Bitcoin.</b><br/> <b>Hai inserito un indirizzo Bitcoin.</b><br/>
@ -322,4 +323,20 @@
<string name="menu_language">Language</string> <string name="menu_language">Language</string>
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -233,4 +233,15 @@
det tidligere steget og komme tilbake til \"Bekreft\" skjermen.</p> det tidligere steget og komme tilbake til \"Bekreft\" skjermen.</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -34,6 +34,7 @@
<string name="info_xmrto_enabled">BTC betaling tilgjengelig, trykk for mer info.</string> <string name="info_xmrto_enabled">BTC betaling tilgjengelig, trykk for mer info.</string>
<string name="info_crazypass_enabled">CrAzYpass tilgjengelig, trykk for mer info.</string> <string name="info_crazypass_enabled">CrAzYpass tilgjengelig, trykk for mer info.</string>
<string name="info_ledger_enabled">Ledger tilgjengelig, trykk for mer info.</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>Du skrev inn en Bitcoin addresse.</b><br/> <b>Du skrev inn en Bitcoin addresse.</b><br/>
@ -320,4 +321,20 @@
<string name="menu_language">Language</string> <string name="menu_language">Language</string>
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -236,4 +236,15 @@
indo ao passo anterior e depois voltando ao ecrã de \"Confirmar\".</p> indo ao passo anterior e depois voltando ao ecrã de \"Confirmar\".</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -33,8 +33,8 @@
<string name="info_send_prio_fees">Prioridade Alta = Taxas Altas</string> <string name="info_send_prio_fees">Prioridade Alta = Taxas Altas</string>
<string name="info_xmrto_enabled">Pagamento em BTC activado, toca para mais informação.</string> <string name="info_xmrto_enabled">Pagamento em BTC activado, toca para mais informação.</string>
<string name="info_crazypass_enabled">passLoUCa activa, toca para mais informação.</string> <string name="info_crazypass_enabled">passLoUCa activa, toca para mais informação.</string>
<string name="info_ledger_enabled">Ledger activa, toca para mais informação.</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>Introduziu um endereço Bitcoin.</b><br/> <b>Introduziu um endereço Bitcoin.</b><br/>
@ -324,4 +324,20 @@
<string name="menu_language">Language</string> <string name="menu_language">Language</string>
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -221,4 +221,15 @@
înapoi la pasul anterior apoi revenind la ecranul \"Confirm\".</p> înapoi la pasul anterior apoi revenind la ecranul \"Confirm\".</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -32,6 +32,8 @@
<string name="info_send_prio_fees">Prioritate mare = Comision mare</string> <string name="info_send_prio_fees">Prioritate mare = Comision mare</string>
<string name="info_xmrto_enabled">Plată BTC activată, apasă pentru mai multe informații.</string> <string name="info_xmrto_enabled">Plată BTC activată, apasă pentru mai multe informații.</string>
<string name="info_crazypass_enabled">CrAzYpass activată, apasă pentru mai multe informații.</string>
<string name="info_ledger_enabled">Ledger activată, apasă pentru mai multe informații.</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>Ai introdus o adresă de Bitcoin.</b><br/> <b>Ai introdus o adresă de Bitcoin.</b><br/>
@ -284,7 +286,6 @@
<string name="fab_restore_seed">Restaurează portofel folosind cele 25 de cuvinte mnemonice</string> <string name="fab_restore_seed">Restaurează portofel folosind cele 25 de cuvinte mnemonice</string>
<string name="menu_changepw">Change Passphrase</string> <string name="menu_changepw">Change Passphrase</string>
<string name="info_crazypass_enabled">CrAzYpass enabled, tap for more info.</string>
<string name="changepw_progress">Change Password in progress</string> <string name="changepw_progress">Change Password in progress</string>
<string name="changepw_failed">Change Password failed!</string> <string name="changepw_failed">Change Password failed!</string>
<string name="changepw_success">Password changed</string> <string name="changepw_success">Password changed</string>
@ -320,4 +321,20 @@
<string name="menu_language">Language</string> <string name="menu_language">Language</string>
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -239,4 +239,15 @@
предложение, вернувшись к предыдущему шагу, а затем к экрану \"Подтверждение\".</p> предложение, вернувшись к предыдущему шагу, а затем к экрану \"Подтверждение\".</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -33,8 +33,8 @@
<string name="info_send_prio_fees">Высокий приоритет = Высокие комиссии</string> <string name="info_send_prio_fees">Высокий приоритет = Высокие комиссии</string>
<string name="info_xmrto_enabled">Доступны переводы в BTC, нажмите для доп. информации</string> <string name="info_xmrto_enabled">Доступны переводы в BTC, нажмите для доп. информации</string>
<string name="info_crazypass_enabled">Доступен CrAzYpass, нажмите для доп. информации</string> <string name="info_crazypass_enabled">Доступен CrAzYpass, нажмите для доп. информации</string>
<string name="info_ledger_enabled">Доступен Ledger, нажмите для доп. информации</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>Вы ввели Bitcoin адрес.</b><br/> <b>Вы ввели Bitcoin адрес.</b><br/>
@ -323,4 +323,20 @@
<string name="menu_language">Language</string> <string name="menu_language">Language</string>
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -214,4 +214,15 @@
tillbaka till den tidigare skärmen och sedan gå till \"Bekräfta\"skärmen.</p> tillbaka till den tidigare skärmen och sedan gå till \"Bekräfta\"skärmen.</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -34,8 +34,8 @@
<string name="info_send_prio_fees">Högre prioritet = Högre avgift</string> <string name="info_send_prio_fees">Högre prioritet = Högre avgift</string>
<string name="info_xmrto_enabled">BTC-betalning aktiverad, tryck för mer info.</string> <string name="info_xmrto_enabled">BTC-betalning aktiverad, tryck för mer info.</string>
<string name="info_crazypass_enabled">CrAzYpass aktiverat, tryck för mer info.</string> <string name="info_crazypass_enabled">CrAzYpass aktiverat, tryck för mer info.</string>
<string name="info_ledger_enabled">Ledger aktiverat, tryck för mer info.</string>
<string name="info_xmrto"><![CDATA[<b>Du har angivit en Bitcoin-adress.</b><br/> <i>Du kommer att skicka XMR och mottagaren får BTC via tjänsten <b>XMR.TO</b>.</i>]]></string> <string name="info_xmrto"><![CDATA[<b>Du har angivit en Bitcoin-adress.</b><br/> <i>Du kommer att skicka XMR och mottagaren får BTC via tjänsten <b>XMR.TO</b>.</i>]]></string>
@ -305,4 +305,20 @@
<string name="menu_language">Language</string> <string name="menu_language">Language</string>
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -189,4 +189,15 @@
<p>当倒数计时归零的时候,你将会需要回到上一步再回到\"确认\"页面重新向XMR.TO寻求汇率报价</p> <p>当倒数计时归零的时候,你将会需要回到上一步再回到\"确认\"页面重新向XMR.TO寻求汇率报价</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -34,6 +34,7 @@
<string name="info_xmrto_enabled">BTC付款已启用, 点选了解更多</string> <string name="info_xmrto_enabled">BTC付款已启用, 点选了解更多</string>
<string name="info_crazypass_enabled">CrAzYpass已启用, 点选了解更多</string> <string name="info_crazypass_enabled">CrAzYpass已启用, 点选了解更多</string>
<string name="info_ledger_enabled">Ledger已启用, 点选了解更多</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>你输入了Bitcoin地址</b><br/> <b>你输入了Bitcoin地址</b><br/>
@ -318,4 +319,20 @@
<string name="menu_language">语言</string> <string name="menu_language">语言</string>
<string name="language_system_default">使用系统语言</string> <string name="language_system_default">使用系统语言</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -189,4 +189,15 @@
<p>當倒數計時歸零的時候,你將會需要回到上一步再回到\"確認\"頁面重新向XMR.TO尋求匯率報價</p> <p>當倒數計時歸零的時候,你將會需要回到上一步再回到\"確認\"頁面重新向XMR.TO尋求匯率報價</p>
]]></string> ]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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>

View File

@ -34,6 +34,7 @@
<string name="info_xmrto_enabled">BTC付款已啟用, 點選了解更多</string> <string name="info_xmrto_enabled">BTC付款已啟用, 點選了解更多</string>
<string name="info_crazypass_enabled">CrAzYpass已啟用, 點選了解更多</string> <string name="info_crazypass_enabled">CrAzYpass已啟用, 點選了解更多</string>
<string name="info_ledger_enabled">Ledger已啟用, 點選了解更多</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>你輸入了Bitcoin地址</b><br/> <b>你輸入了Bitcoin地址</b><br/>
@ -319,4 +320,20 @@
<string name="menu_language">語言</string> <string name="menu_language">語言</string>
<string name="language_system_default">使用系統語言</string> <string name="language_system_default">使用系統語言</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -26,9 +26,21 @@
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the device. <p>Enter a unique wallet name and password. The password is used for securing your wallet data on the device.
Use a strong password - even better use a passphrase.</p> Use a strong password - even better use a passphrase.</p>
<p>Enter your Seed in the field \"Mnemonic Seed\".<p> <p>Enter your Seed in the field \"Mnemonic Seed\".<p>
<p>If you know the block number of the first transaction used for this address, enter it in the <p>Enter the block number of the first transaction used for this address in the
field \"Restore Height\" - leaving it blank will scan the <em>entire</em> blockchain for field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
transactions belonging to your address. This takes a <em>long</em> time.</p> enter an approximate date/blockheight <em>before</em> you first used this wallet address.</p>
]]></string>
<string name="help_create_ledger"><![CDATA[
<h1>Create Wallet - Ledger</h1>
<p>You want to recover your wallet from your Ledger Nano S device.</p>
<p>Your secret keys never leave the Ledger device, so you need it plugged in every
time you want to access your wallet.</p>
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the Android
device. Use a strong password - even better use a passphrase.</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> ]]></string>
<string name="help_create_keys"><![CDATA[ <string name="help_create_keys"><![CDATA[
@ -37,9 +49,9 @@
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the device. <p>Enter a unique wallet name and password. The password is used for securing your wallet data on the device.
Use a strong password - even better use a passphrase.<p> Use a strong password - even better use a passphrase.<p>
<p>Enter your Monero Address in the field \"Public Address\" and fill out \"View Key\" and \"Spend Key\".</p> <p>Enter your Monero Address in the field \"Public Address\" and fill out \"View Key\" and \"Spend Key\".</p>
<p>If you know the block number of the first transaction used for this address, enter it in the <p>Enter the block number of the first transaction used for this address in the
field \"Restore Height\" - leaving it blank will scan the <em>entire</em> blockchain for field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
transactions belonging to your address. This takes a <em>long</em> time.</p> enter an approximate date/blockheight <em>before</em> you first used this wallet address.</p>
]]></string> ]]></string>
<string name="help_create_view"><![CDATA[ <string name="help_create_view"><![CDATA[
@ -48,9 +60,9 @@
<p>Enter a unique wallet name and password. The password is used for securing your wallet data on the device. <p>Enter a unique wallet name and password. The password is used for securing your wallet data on the device.
Use a strong password - even better use a passphrase.<p> Use a strong password - even better use a passphrase.<p>
<p>Enter your Monero Address in the field \"Public Address\" and fill out the \"View Key\".</p> <p>Enter your Monero Address in the field \"Public Address\" and fill out the \"View Key\".</p>
<p>If you know the block number of the first transaction used for this address, enter it in the <p>Enter the block number of the first transaction used for this address in the
field \"Restore Height\" - leaving it blank will scan the <em>entire</em> blockchain for field \"Restore Height\". You can also use a date in the format YYYY-MM-DD. If you are not sure,
transactions belonging to your address. This takes a <em>long</em> time.</p> enter an approximate date/blockheight <em>before</em> you first used this wallet address.</p>
]]></string> ]]></string>
<string name="help_details"><![CDATA[ <string name="help_details"><![CDATA[
@ -69,7 +81,7 @@
If the password displayed here is 52 alphanumeric characters in groups of 4 - congratulations! If the password displayed here is 52 alphanumeric characters in groups of 4 - congratulations!
Your wallet files are secured with a 256-bit key generated by your device&apos;s security Your wallet files are secured with a 256-bit key generated by your device&apos;s security
features based on the passphrase you chose (on creation or by changing it). This makes it features based on the passphrase you chose (on creation or by changing it). This makes it
extremenly difficult to hack!<br/> extremely difficult to hack!<br/>
This feature is mandatory for all newly created wallets. This feature is mandatory for all newly created wallets.
<h3>Legacy Password</h3> <h3>Legacy Password</h3>
If you see your passphrase here, your wallet files are not as secure as when using If you see your passphrase here, your wallet files are not as secure as when using
@ -232,5 +244,4 @@
<p>Once the countdown reaches zero, you need to get a new quote from XMR.TO by going back to the <p>Once the countdown reaches zero, you need to get a new quote from XMR.TO by going back to the
previous step and then coming back to the \"Confirm\" screen.</p> previous step and then coming back to the \"Confirm\" screen.</p>
]]></string> ]]></string>
</resources> </resources>

View File

@ -35,8 +35,8 @@
<string name="info_send_prio_fees">Higher Priority = Higher Fees</string> <string name="info_send_prio_fees">Higher Priority = Higher Fees</string>
<string name="info_xmrto_enabled">BTC payment enabled, tap for more info.</string> <string name="info_xmrto_enabled">BTC payment enabled, tap for more info.</string>
<string name="info_crazypass_enabled">CrAzYpass enabled, tap for more info.</string> <string name="info_crazypass_enabled">CrAzYpass enabled, tap for more info.</string>
<string name="info_ledger_enabled">Ledger enabled, tap for more info.</string>
<string name="info_xmrto"><![CDATA[ <string name="info_xmrto"><![CDATA[
<b>You entered a Bitcoin address.</b><br/> <b>You entered a Bitcoin address.</b><br/>
@ -47,7 +47,6 @@
<string name="info_send_xmrto_success_btc">%1$s BTC</string> <string name="info_send_xmrto_success_btc">%1$s BTC</string>
<string name="info_send_xmrto_paid">Confirmation Pending</string> <string name="info_send_xmrto_paid">Confirmation Pending</string>
<string name="info_send_xmrto_unpaid">Payment Pending</string> <string name="info_send_xmrto_unpaid">Payment Pending</string>
<string name="info_send_xmrto_error">XMR.TO Error (%1$s)</string> <string name="info_send_xmrto_error">XMR.TO Error (%1$s)</string>
@ -209,6 +208,7 @@
<string name="generate_wallet_type_new">New</string> <string name="generate_wallet_type_new">New</string>
<string name="generate_wallet_type_seed">Seed</string> <string name="generate_wallet_type_seed">Seed</string>
<string name="generate_wallet_type_view">View</string> <string name="generate_wallet_type_view">View</string>
<string name="generate_wallet_type_ledger" translatable="false">Ledger</string>
<string name="generate_address_hint">Public Address</string> <string name="generate_address_hint">Public Address</string>
<string name="generate_viewkey_hint">View Key</string> <string name="generate_viewkey_hint">View Key</string>
@ -368,4 +368,20 @@
<string name="menu_language">Language</string> <string name="menu_language">Language</string>
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="fab_restore_ledger">Restore from Ledger Nano S</string>
<string name="progress_ledger_progress">Communicating with Ledger</string>
<string name="progress_ledger_confirm">Confirmation on Ledger required!</string>
<string name="progress_ledger_lookahead">Retrieving subaddresses</string>
<string name="progress_ledger_verify">Verifying keys</string>
<string name="progress_ledger_opentx">Doing crazy maths</string>
<string name="progress_ledger_mlsag">Hashing stuff</string>
<string name="open_wallet_ledger_missing">Please (re)connect Ledger device</string>
<string name="accounts_progress_new">Creating account</string>
<string name="accounts_progress_update">Updating wallet</string>
<string name="toast_ledger_attached">%1$s attached</string>
<string name="toast_ledger_detached">%1$s detached</string>
</resources> </resources>

View File

@ -329,5 +329,4 @@
<style name="MoneroSpinnerItemGray" parent="android:Widget.TextView.SpinnerItem"> <style name="MoneroSpinnerItemGray" parent="android:Widget.TextView.SpinnerItem">
<item name="android:textAppearance">@style/MoneroTextAppearanceSpinnerItemGray</item> <item name="android:textAppearance">@style/MoneroTextAppearanceSpinnerItemGray</item>
</style> </style>
</resources> </resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Ledger Nano S HID: VID=0x2C97 PID=0x0001 -->
<usb-device
product-id="1"
vendor-id="11415" />
</resources>

View File

@ -802,6 +802,12 @@ struct Wallet
//! Initiates a light wallet import wallet request //! Initiates a light wallet import wallet request
virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) = 0; virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) = 0;
/*!
* \brief Queries if the wallet keys are on a hardware device
* \return true if they are
*/
virtual bool isKeyOnDevice() const = 0;
}; };
/** /**
@ -975,6 +981,17 @@ struct WalletManager
*/ */
virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const = 0; virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const = 0;
/*!
* \brief determine the key storage for the specified wallet file
* \param keys_file_name Keys file to verify password for
* \param password Password to verify
* \return -1: incorrect password, 0 = default hw, 1 ledger hw
*
* for verification only - determines key storage hardware
*
*/
virtual int queryWalletHardware(const std::string &keys_file_name, const std::string &password) const = 0;
/*! /*!
* \brief findWallets - searches for the wallet files by given path name recursively * \brief findWallets - searches for the wallet files by given path name recursively
* \param path - starting point to search * \param path - starting point to search