mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-14 08:40:49 +02:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bf5ed793b3 | ||
![]() |
679bae5f42 | ||
![]() |
e3ccda910e | ||
![]() |
ae75a34977 | ||
![]() |
03efedf35c | ||
![]() |
403dbdf14f | ||
![]() |
0bf3c6f099 | ||
![]() |
1b0ac1c481 | ||
![]() |
dc95539fc1 | ||
![]() |
023fb9e215 | ||
![]() |
92728026c7 | ||
![]() |
8fd9598c6c | ||
![]() |
6633261ba2 | ||
![]() |
dc8c8634cb | ||
![]() |
dcf9b6db15 | ||
![]() |
654d63c32e | ||
![]() |
caf91fccfd | ||
![]() |
41f1f3dec0 | ||
![]() |
bb66d1f68d | ||
![]() |
310548b031 | ||
![]() |
ad7737475f | ||
![]() |
b2d07a65b7 | ||
![]() |
5dfcaae5b9 | ||
![]() |
3c91fc060c | ||
![]() |
f8f113faab | ||
![]() |
268a00cb3e | ||
![]() |
878500ae71 | ||
![]() |
a0debb0f7e | ||
![]() |
3a71e8d352 | ||
![]() |
af68c5e51f | ||
![]() |
5a2b48a087 | ||
![]() |
0776b7b6a3 | ||
![]() |
c2eed85a83 | ||
![]() |
d7c2b4a727 | ||
![]() |
085e41f5da | ||
![]() |
6c6b3061a8 | ||
![]() |
5fc15779b7 | ||
![]() |
520d151f3c | ||
![]() |
7aad941dab | ||
![]() |
01e7693425 | ||
![]() |
091538752b | ||
![]() |
d5b95dd976 | ||
![]() |
008f06959c | ||
![]() |
817816cd34 | ||
![]() |
9d41d5da52 | ||
![]() |
ad76a7ffc1 | ||
![]() |
475542c4f3 | ||
![]() |
b5d0659ca9 | ||
![]() |
781bfbc78b |
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "com.m2049r.xmrwallet"
|
applicationId "com.m2049r.xmrwallet"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
versionCode 95
|
versionCode 111
|
||||||
versionName "1.5.5 'Accounts Nacho'"
|
versionName "1.6.1 'Nano S'"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
@@ -45,8 +45,17 @@ android {
|
|||||||
// Map for the version code that gives each ABI a value.
|
// Map for the version code that gives each ABI a value.
|
||||||
def abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
|
def abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
|
||||||
|
|
||||||
|
// Enumerate translated locales
|
||||||
|
def availableLocales = ["en"]
|
||||||
|
new File("app/src/main/res/").eachFileMatch(~/^values-.*/) { file ->
|
||||||
|
def languageTag = file.name.substring(7).replace("-r", "-")
|
||||||
|
availableLocales.add(languageTag)
|
||||||
|
}
|
||||||
|
|
||||||
// APKs for the same app that all have the same version information.
|
// APKs for the same app that all have the same version information.
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
|
// Update string resource: available_locales
|
||||||
|
variant.resValue("string", "available_locales", availableLocales.join(","))
|
||||||
// Assigns a different version code for each output APK.
|
// Assigns a different version code for each output APK.
|
||||||
variant.outputs.all {
|
variant.outputs.all {
|
||||||
output ->
|
output ->
|
||||||
|
@@ -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"
|
||||||
|
File diff suppressed because it is too large
Load Diff
46
app/src/main/cpp/monerujo_ledger.h
Normal file
46
app/src/main/cpp/monerujo_ledger.h
Normal 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
|
53
app/src/main/java/com/btchip/BTChipException.java
Normal file
53
app/src/main/java/com/btchip/BTChipException.java
Normal 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;
|
||||||
|
|
||||||
|
}
|
31
app/src/main/java/com/btchip/comm/BTChipTransport.java
Normal file
31
app/src/main/java/com/btchip/comm/BTChipTransport.java
Normal 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);
|
||||||
|
}
|
126
app/src/main/java/com/btchip/comm/LedgerHelper.java
Normal file
126
app/src/main/java/com/btchip/comm/LedgerHelper.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
62
app/src/main/java/com/btchip/utils/Dump.java
Normal file
62
app/src/main/java/com/btchip/utils/Dump.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
105
app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
Normal file
105
app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
@@ -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) {
|
||||||
@@ -391,16 +403,24 @@ public class GenerateFragment extends Fragment {
|
|||||||
// is it a date?
|
// is it a date?
|
||||||
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
|
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
parser.setLenient(false);
|
parser.setLenient(false);
|
||||||
parser.parse(restoreHeight);
|
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||||
height = RestoreHeight.getInstance().getHeight(restoreHeight);
|
} catch (ParseException ex) {
|
||||||
} catch (ParseException exPE) {
|
}
|
||||||
|
if (height <= 0)
|
||||||
|
try {
|
||||||
|
// is it a date without dashes?
|
||||||
|
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");
|
||||||
|
parser.setLenient(false);
|
||||||
|
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||||
|
} catch (ParseException ex) {
|
||||||
|
}
|
||||||
|
if (height <= 0)
|
||||||
try {
|
try {
|
||||||
// or is it a height?
|
// or is it a height?
|
||||||
height = Long.parseLong(restoreHeight);
|
height = Long.parseLong(restoreHeight);
|
||||||
} catch (NumberFormatException exNFE) {
|
} catch (NumberFormatException ex) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Timber.d("Using Restore Height = %d", height);
|
Timber.d("Using Restore Height = %d", height);
|
||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
@@ -477,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);
|
||||||
@@ -515,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:
|
||||||
@@ -532,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);
|
||||||
@@ -567,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;
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||||
@@ -372,7 +376,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
"node.moneroworld.com:18089;node.xmrbackb.one;node.xmr.be";
|
"node.moneroworld.com:18089;node.xmrbackb.one;node.xmr.be";
|
||||||
|
|
||||||
private static final String PREF_DAEMONLIST_STAGENET =
|
private static final String PREF_DAEMONLIST_STAGENET =
|
||||||
"stagenet.xmr-tw.org";
|
"stagenet.monerujo.io;stagenet.xmr-tw.org";
|
||||||
|
|
||||||
private NodeList daemonStageNet;
|
private NodeList daemonStageNet;
|
||||||
private NodeList daemonMainNet;
|
private NodeList daemonMainNet;
|
||||||
@@ -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;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
package com.m2049r.xmrwallet;
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import static android.view.WindowManager.LayoutParams;
|
import static android.view.WindowManager.LayoutParams;
|
||||||
|
|
||||||
@@ -36,4 +35,9 @@ public abstract class SecureActivity extends AppCompatActivity {
|
|||||||
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(Context context) {
|
||||||
|
super.attachBaseContext(LocaleHelper.setLocale(context, LocaleHelper.getLocale(context)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -224,7 +224,7 @@ public class TxFragment extends Fragment {
|
|||||||
activityCallback.setSubtitle(getString(R.string.tx_title));
|
activityCallback.setSubtitle(getString(R.string.tx_title));
|
||||||
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||||
|
|
||||||
tvAccount.setText("" + info.subaddrAccount);
|
tvAccount.setText(getString(R.string.tx_account_formatted, info.account, info.subaddress));
|
||||||
|
|
||||||
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
|
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
|
||||||
tvTxId.setText(info.hash);
|
tvTxId.setText(info.hash);
|
||||||
@@ -287,7 +287,10 @@ public class TxFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sb.append("-");
|
sb.append("-");
|
||||||
dstSb.append(info.direction == TransactionInfo.Direction.Direction_In ? activityCallback.getWalletAddress() : "-");
|
dstSb.append(info.direction ==
|
||||||
|
TransactionInfo.Direction.Direction_In ?
|
||||||
|
activityCallback.getWalletSubaddress(info.account, info.subaddress) :
|
||||||
|
"-");
|
||||||
}
|
}
|
||||||
tvTxTransfers.setText(sb.toString());
|
tvTxTransfers.setText(sb.toString());
|
||||||
tvDestination.setText(dstSb.toString());
|
tvDestination.setText(dstSb.toString());
|
||||||
@@ -322,7 +325,7 @@ public class TxFragment extends Fragment {
|
|||||||
Listener activityCallback;
|
Listener activityCallback;
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
String getWalletAddress();
|
String getWalletSubaddress(int accountIndex, int subaddressIndex);
|
||||||
|
|
||||||
String getTxKey(String hash);
|
String getTxKey(String hash);
|
||||||
|
|
||||||
|
@@ -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,
|
||||||
@@ -179,7 +182,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
|||||||
saveWallet();
|
saveWallet();
|
||||||
}
|
}
|
||||||
stopWalletService();
|
stopWalletService();
|
||||||
drawer.removeDrawerListener(drawerToggle);
|
if (drawer != null) drawer.removeDrawerListener(drawerToggle);
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,8 +289,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
|||||||
});
|
});
|
||||||
|
|
||||||
drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||||
drawerToggle = new ActionBarDrawerToggle(this, drawer, toolbar,
|
drawerToggle = new ActionBarDrawerToggle(this, drawer, toolbar, 0, 0);
|
||||||
R.string.accounts_drawer_open, R.string.accounts_drawer_close);
|
|
||||||
drawer.addDrawerListener(drawerToggle);
|
drawer.addDrawerListener(drawerToggle);
|
||||||
drawerToggle.syncState();
|
drawerToggle.syncState();
|
||||||
setDrawerEnabled(false); // disable until synced
|
setDrawerEnabled(false); // disable until synced
|
||||||
@@ -398,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);
|
||||||
@@ -491,8 +471,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
|||||||
@Override
|
@Override
|
||||||
public boolean onRefreshed(final Wallet wallet, final boolean full) {
|
public boolean onRefreshed(final Wallet wallet, final boolean full) {
|
||||||
Timber.d("onRefreshed()");
|
Timber.d("onRefreshed()");
|
||||||
if (numAccounts != wallet.numAccounts()) {
|
if (numAccounts != wallet.getNumAccounts()) {
|
||||||
numAccounts = wallet.numAccounts();
|
numAccounts = wallet.getNumAccounts();
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
updateAccountsList();
|
updateAccountsList();
|
||||||
@@ -504,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
|
||||||
@@ -545,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();
|
||||||
}
|
}
|
||||||
@@ -579,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();
|
||||||
@@ -734,14 +726,16 @@ 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWalletAddress() {
|
public String getWalletSubaddress(int accountIndex, int subaddressIndex) {
|
||||||
return getWallet().getAddress();
|
return getWallet().getSubaddress(accountIndex, subaddressIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getWalletName() {
|
public String getWalletName() {
|
||||||
@@ -891,7 +885,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWalletReceive() {
|
public void onWalletReceive() {
|
||||||
startReceive(getWalletAddress());
|
startReceive(getWallet().getAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
void startReceive(String address) {
|
void startReceive(String address) {
|
||||||
@@ -966,7 +960,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
|||||||
Helper.getDisplayAmount(wallet.getBalanceAll(), 5)));
|
Helper.getDisplayAmount(wallet.getBalanceAll(), 5)));
|
||||||
Menu menu = accountsView.getMenu();
|
Menu menu = accountsView.getMenu();
|
||||||
menu.removeGroup(R.id.accounts_list);
|
menu.removeGroup(R.id.accounts_list);
|
||||||
for (int i = 0; i < wallet.numAccounts(); i++) {
|
final int n = wallet.getNumAccounts();
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
final String label = wallet.getAccountLabel(i);
|
final String label = wallet.getAccountLabel(i);
|
||||||
final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label);
|
final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label);
|
||||||
item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp);
|
item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp);
|
||||||
@@ -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().numAccounts() - 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -271,7 +271,7 @@ public class WalletFragment extends Fragment
|
|||||||
bSend.setVisibility(View.VISIBLE);
|
bSend.setVisibility(View.VISIBLE);
|
||||||
bSend.setEnabled(true);
|
bSend.setEnabled(true);
|
||||||
}
|
}
|
||||||
enableAccountsList(true);
|
if (isVisible()) enableAccountsList(true); //otherwise it is enabled in onResume()
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean walletLoaded = false;
|
boolean walletLoaded = false;
|
||||||
@@ -422,6 +422,7 @@ public class WalletFragment extends Fragment
|
|||||||
setProgress(syncProgress);
|
setProgress(syncProgress);
|
||||||
setProgress(syncText);
|
setProgress(syncText);
|
||||||
showReceive();
|
showReceive();
|
||||||
|
if (activityCallback.isSynced()) enableAccountsList(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -18,8 +18,10 @@ package com.m2049r.xmrwallet;
|
|||||||
|
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@@ -27,9 +29,21 @@ public class XmrWalletApplication extends Application {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Timber.plant(new Timber.DebugTree());
|
Timber.plant(new Timber.DebugTree());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(Context context) {
|
||||||
|
super.attachBaseContext(LocaleHelper.setLocale(context, LocaleHelper.getLocale(context)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigurationChanged(Configuration configuration) {
|
||||||
|
super.onConfigurationChanged(configuration);
|
||||||
|
LocaleHelper.updateSystemDefaultLocale(configuration.locale);
|
||||||
|
LocaleHelper.setLocale(XmrWalletApplication.this, LocaleHelper.getLocale(XmrWalletApplication.this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -20,6 +20,9 @@ import android.os.Bundle;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.R;
|
import com.m2049r.xmrwallet.R;
|
||||||
@@ -57,8 +60,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
|||||||
|
|
||||||
private TextView tvFunds;
|
private TextView tvFunds;
|
||||||
private ExchangeTextView evAmount;
|
private ExchangeTextView evAmount;
|
||||||
//private Button bSendAll;
|
private View llAmount;
|
||||||
private NumberPadView numberPad;
|
private View rlSweep;
|
||||||
|
private ImageButton ibSweep;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
@@ -73,37 +77,64 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
|||||||
tvFunds = (TextView) view.findViewById(R.id.tvFunds);
|
tvFunds = (TextView) view.findViewById(R.id.tvFunds);
|
||||||
|
|
||||||
evAmount = (ExchangeTextView) view.findViewById(R.id.evAmount);
|
evAmount = (ExchangeTextView) view.findViewById(R.id.evAmount);
|
||||||
numberPad = (NumberPadView) view.findViewById(R.id.numberPad);
|
((NumberPadView) view.findViewById(R.id.numberPad)).setListener(evAmount);
|
||||||
numberPad.setListener(evAmount);
|
|
||||||
|
|
||||||
/*
|
rlSweep = view.findViewById(R.id.rlSweep);
|
||||||
bSendAll = (Button) view.findViewById(R.id.bSendAll);
|
llAmount = view.findViewById(R.id.llAmount);
|
||||||
bSendAll.setOnClickListener(new View.OnClickListener() {
|
view.findViewById(R.id.ivSweep).setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
// TODO: send all - figure out how to display this
|
sweepAll(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ibSweep = (ImageButton) view.findViewById(R.id.ibSweep);
|
||||||
|
|
||||||
|
ibSweep.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
sweepAll(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
Helper.hideKeyboard(getActivity());
|
Helper.hideKeyboard(getActivity());
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean spendAllMode = false;
|
||||||
|
|
||||||
|
private void sweepAll(boolean spendAllMode) {
|
||||||
|
if (spendAllMode) {
|
||||||
|
ibSweep.setVisibility(View.INVISIBLE);
|
||||||
|
llAmount.setVisibility(View.GONE);
|
||||||
|
rlSweep.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
ibSweep.setVisibility(View.VISIBLE);
|
||||||
|
llAmount.setVisibility(View.VISIBLE);
|
||||||
|
rlSweep.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
this.spendAllMode = spendAllMode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onValidateFields() {
|
public boolean onValidateFields() {
|
||||||
if (!evAmount.validate(maxFunds)) {
|
if (spendAllMode) {
|
||||||
return false;
|
if (sendListener != null) {
|
||||||
}
|
sendListener.getTxData().setAmount(Wallet.SWEEP_ALL);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!evAmount.validate(maxFunds)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (sendListener != null) {
|
if (sendListener != null) {
|
||||||
String xmr = evAmount.getAmount();
|
String xmr = evAmount.getAmount();
|
||||||
if (xmr != null) {
|
if (xmr != null) {
|
||||||
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
|
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
|
||||||
} else {
|
} else {
|
||||||
sendListener.getTxData().setAmount(0L);
|
sendListener.getTxData().setAmount(0L);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@@ -36,6 +36,7 @@ import com.m2049r.xmrwallet.util.UserNotes;
|
|||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class SendSettingsWizardFragment extends SendWizardFragment {
|
public class SendSettingsWizardFragment extends SendWizardFragment {
|
||||||
|
final static public int MIXIN = 6;
|
||||||
|
|
||||||
public static SendSettingsWizardFragment newInstance(Listener listener) {
|
public static SendSettingsWizardFragment newInstance(Listener listener) {
|
||||||
SendSettingsWizardFragment instance = new SendSettingsWizardFragment();
|
SendSettingsWizardFragment instance = new SendSettingsWizardFragment();
|
||||||
@@ -54,15 +55,12 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
|
|||||||
TxData getTxData();
|
TxData getTxData();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mixin = Ringsize - 1
|
|
||||||
final static int Mixins[] = {6, 9, 12, 25}; // must match the layout XML / "@array/mixin"
|
|
||||||
final static PendingTransaction.Priority Priorities[] =
|
final static PendingTransaction.Priority Priorities[] =
|
||||||
{PendingTransaction.Priority.Priority_Default,
|
{PendingTransaction.Priority.Priority_Default,
|
||||||
PendingTransaction.Priority.Priority_Low,
|
PendingTransaction.Priority.Priority_Low,
|
||||||
PendingTransaction.Priority.Priority_Medium,
|
PendingTransaction.Priority.Priority_Medium,
|
||||||
PendingTransaction.Priority.Priority_High}; // must match the layout XML
|
PendingTransaction.Priority.Priority_High}; // must match the layout XML
|
||||||
|
|
||||||
private Spinner sMixin;
|
|
||||||
private Spinner sPriority;
|
private Spinner sPriority;
|
||||||
private EditText etNotes;
|
private EditText etNotes;
|
||||||
private EditText etDummy;
|
private EditText etDummy;
|
||||||
@@ -77,7 +75,6 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
|
|||||||
View view = inflater.inflate(
|
View view = inflater.inflate(
|
||||||
R.layout.fragment_send_settings, container, false);
|
R.layout.fragment_send_settings, container, false);
|
||||||
|
|
||||||
sMixin = (Spinner) view.findViewById(R.id.sMixin);
|
|
||||||
sPriority = (Spinner) view.findViewById(R.id.sPriority);
|
sPriority = (Spinner) view.findViewById(R.id.sPriority);
|
||||||
|
|
||||||
etNotes = (EditText) view.findViewById(R.id.etNotes);
|
etNotes = (EditText) view.findViewById(R.id.etNotes);
|
||||||
@@ -104,7 +101,7 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
|
|||||||
if (sendListener != null) {
|
if (sendListener != null) {
|
||||||
TxData txData = sendListener.getTxData();
|
TxData txData = sendListener.getTxData();
|
||||||
txData.setPriority(Priorities[sPriority.getSelectedItemPosition()]);
|
txData.setPriority(Priorities[sPriority.getSelectedItemPosition()]);
|
||||||
txData.setMixin(Mixins[sMixin.getSelectedItemPosition()]);
|
txData.setMixin(MIXIN);
|
||||||
txData.setUserNotes(new UserNotes(etNotes.getText().toString()));
|
txData.setUserNotes(new UserNotes(etNotes.getText().toString()));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@@ -166,7 +166,11 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((userNotes.note.isEmpty())) {
|
if ((userNotes.note.isEmpty())) {
|
||||||
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ? "" : infoItem.paymentId);
|
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ?
|
||||||
|
(infoItem.subaddress != 0 ?
|
||||||
|
(context.getString(R.string.tx_subaddress, infoItem.subaddress)) :
|
||||||
|
"") :
|
||||||
|
infoItem.paymentId);
|
||||||
} else {
|
} else {
|
||||||
this.tvPaymentId.setText(userNotes.note);
|
this.tvPaymentId.setText(userNotes.note);
|
||||||
}
|
}
|
||||||
|
142
app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java
Normal file
142
app/src/main/java/com/m2049r/xmrwallet/ledger/Instruction.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
272
app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java
Normal file
272
app/src/main/java/com/m2049r/xmrwallet/ledger/Ledger.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -75,7 +75,7 @@ public class TransactionHistory {
|
|||||||
Timber.d("refreshed %d", t.size());
|
Timber.d("refreshed %d", t.size());
|
||||||
for (Iterator<TransactionInfo> iterator = t.iterator(); iterator.hasNext(); ) {
|
for (Iterator<TransactionInfo> iterator = t.iterator(); iterator.hasNext(); ) {
|
||||||
TransactionInfo info = iterator.next();
|
TransactionInfo info = iterator.next();
|
||||||
if (info.subaddrAccount != accountIndex) {
|
if (info.account != accountIndex) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
Timber.d("removed %s", info.hash);
|
Timber.d("removed %s", info.hash);
|
||||||
} else {
|
} else {
|
||||||
|
@@ -67,7 +67,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
public String hash;
|
public String hash;
|
||||||
public long timestamp;
|
public long timestamp;
|
||||||
public String paymentId;
|
public String paymentId;
|
||||||
public int subaddrAccount;
|
public int account;
|
||||||
|
public int subaddress;
|
||||||
public long confirmations;
|
public long confirmations;
|
||||||
public List<Transfer> transfers;
|
public List<Transfer> transfers;
|
||||||
|
|
||||||
@@ -84,7 +85,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
String hash,
|
String hash,
|
||||||
long timestamp,
|
long timestamp,
|
||||||
String paymentId,
|
String paymentId,
|
||||||
int subaddrAccount,
|
int account,
|
||||||
|
int subaddress,
|
||||||
long confirmations,
|
long confirmations,
|
||||||
List<Transfer> transfers) {
|
List<Transfer> transfers) {
|
||||||
this.direction = Direction.values()[direction];
|
this.direction = Direction.values()[direction];
|
||||||
@@ -96,7 +98,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
this.hash = hash;
|
this.hash = hash;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.paymentId = paymentId;
|
this.paymentId = paymentId;
|
||||||
this.subaddrAccount = subaddrAccount;
|
this.account = account;
|
||||||
|
this.subaddress = subaddress;
|
||||||
this.confirmations = confirmations;
|
this.confirmations = confirmations;
|
||||||
this.transfers = transfers;
|
this.transfers = transfers;
|
||||||
}
|
}
|
||||||
@@ -116,7 +119,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
out.writeString(hash);
|
out.writeString(hash);
|
||||||
out.writeLong(timestamp);
|
out.writeLong(timestamp);
|
||||||
out.writeString(paymentId);
|
out.writeString(paymentId);
|
||||||
out.writeInt(subaddrAccount);
|
out.writeInt(account);
|
||||||
|
out.writeInt(subaddress);
|
||||||
out.writeLong(confirmations);
|
out.writeLong(confirmations);
|
||||||
out.writeList(transfers);
|
out.writeList(transfers);
|
||||||
out.writeString(txKey);
|
out.writeString(txKey);
|
||||||
@@ -143,7 +147,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
hash = in.readString();
|
hash = in.readString();
|
||||||
timestamp = in.readLong();
|
timestamp = in.readLong();
|
||||||
paymentId = in.readString();
|
paymentId = in.readString();
|
||||||
subaddrAccount = in.readInt();
|
account = in.readInt();
|
||||||
|
subaddress = in.readInt();
|
||||||
confirmations = in.readLong();
|
confirmations = in.readLong();
|
||||||
transfers = in.readArrayList(Transfer.class.getClassLoader());
|
transfers = in.readArrayList(Transfer.class.getClassLoader());
|
||||||
txKey = in.readString();
|
txKey = in.readString();
|
||||||
|
@@ -19,10 +19,15 @@ package com.m2049r.xmrwallet.model;
|
|||||||
import com.m2049r.xmrwallet.data.TxData;
|
import com.m2049r.xmrwallet.data.TxData;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class Wallet {
|
public class Wallet {
|
||||||
|
final static public long SWEEP_ALL = Long.MAX_VALUE;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
System.loadLibrary("monerujo");
|
System.loadLibrary("monerujo");
|
||||||
}
|
}
|
||||||
@@ -84,10 +89,22 @@ public class Wallet {
|
|||||||
public native boolean setPassword(String password);
|
public native boolean setPassword(String password);
|
||||||
|
|
||||||
public String getAddress() {
|
public String getAddress() {
|
||||||
return getAddressJ(accountIndex);
|
return getAddress(accountIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private native String getAddressJ(int accountIndex);
|
public String getAddress(int accountIndex) {
|
||||||
|
return getAddressJ(accountIndex, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubaddress(int addressIndex) {
|
||||||
|
return getAddressJ(accountIndex, addressIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSubaddress(int accountIndex, int addressIndex) {
|
||||||
|
return getAddressJ(accountIndex, addressIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private native String getAddressJ(int accountIndex, int addressIndex);
|
||||||
|
|
||||||
public native String getPath();
|
public native String getPath();
|
||||||
|
|
||||||
@@ -107,7 +124,9 @@ public class Wallet {
|
|||||||
public native String getSecretSpendKey();
|
public native String getSecretSpendKey();
|
||||||
|
|
||||||
public boolean store() {
|
public boolean store() {
|
||||||
return store("");
|
final boolean ok = store("");
|
||||||
|
Timber.d("stored");
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
public native boolean store(String path);
|
public native boolean store(String path);
|
||||||
@@ -231,8 +250,12 @@ public class Wallet {
|
|||||||
PendingTransaction.Priority priority) {
|
PendingTransaction.Priority priority) {
|
||||||
disposePendingTransaction();
|
disposePendingTransaction();
|
||||||
int _priority = priority.getValue();
|
int _priority = priority.getValue();
|
||||||
long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority,
|
long txHandle =
|
||||||
accountIndex);
|
(amount == SWEEP_ALL ?
|
||||||
|
createSweepTransaction(dst_addr, payment_id, mixin_count, _priority,
|
||||||
|
accountIndex) :
|
||||||
|
createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority,
|
||||||
|
accountIndex));
|
||||||
pendingTransaction = new PendingTransaction(txHandle);
|
pendingTransaction = new PendingTransaction(txHandle);
|
||||||
return pendingTransaction;
|
return pendingTransaction;
|
||||||
}
|
}
|
||||||
@@ -241,6 +264,10 @@ public class Wallet {
|
|||||||
long amount, int mixin_count,
|
long amount, int mixin_count,
|
||||||
int priority, int accountIndex);
|
int priority, int accountIndex);
|
||||||
|
|
||||||
|
private native long createSweepTransaction(String dst_addr, String payment_id,
|
||||||
|
int mixin_count,
|
||||||
|
int priority, int accountIndex);
|
||||||
|
|
||||||
|
|
||||||
public PendingTransaction createSweepUnmixableTransaction() {
|
public PendingTransaction createSweepUnmixableTransaction() {
|
||||||
disposePendingTransaction();
|
disposePendingTransaction();
|
||||||
@@ -313,17 +340,21 @@ public class Wallet {
|
|||||||
public String getAccountLabel(int accountIndex) {
|
public String getAccountLabel(int accountIndex) {
|
||||||
String label = getSubaddressLabel(accountIndex, 0);
|
String label = getSubaddressLabel(accountIndex, 0);
|
||||||
if (label.equals(NEW_ACCOUNT_NAME)) {
|
if (label.equals(NEW_ACCOUNT_NAME)) {
|
||||||
String address = getAddressJ(accountIndex);
|
String address = getAddress(accountIndex);
|
||||||
int len = address.length();
|
int len = address.length();
|
||||||
return address.substring(0, 6) +
|
return address.substring(0, 6) +
|
||||||
"\u2026" + address.substring(len - 6, len);
|
"\u2026" + address.substring(len - 6, len);
|
||||||
} else return label;
|
} else return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSubaddressLabel(int addressIndex) {
|
||||||
|
return getSubaddressLabel(accountIndex, addressIndex);
|
||||||
|
}
|
||||||
|
|
||||||
public native String getSubaddressLabel(int accountIndex, int addressIndex);
|
public native String getSubaddressLabel(int accountIndex, int addressIndex);
|
||||||
|
|
||||||
public void setAccountLabel(String label) {
|
public void setAccountLabel(String label) {
|
||||||
setSubaddressLabel(accountIndex, 0, label);
|
setAccountLabel(accountIndex, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAccountLabel(int accountIndex, String label) {
|
public void setAccountLabel(int accountIndex, String label) {
|
||||||
@@ -332,9 +363,31 @@ public class Wallet {
|
|||||||
|
|
||||||
public native void setSubaddressLabel(int accountIndex, int addressIndex, String label);
|
public native void setSubaddressLabel(int accountIndex, int addressIndex, String label);
|
||||||
|
|
||||||
public int numAccounts() {
|
public native int getNumAccounts();
|
||||||
return numSubaddressAccounts();
|
|
||||||
|
public int getNumSubaddresses() {
|
||||||
|
return getNumSubaddresses(accountIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public native int numSubaddressAccounts();
|
public native int getNumSubaddresses(int accountIndex);
|
||||||
|
|
||||||
|
public String getNewSubaddress() {
|
||||||
|
return getNewSubaddress(accountIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewSubaddress(int accountIndex) {
|
||||||
|
String timeStamp = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).format(new Date());
|
||||||
|
addSubaddress(accountIndex, timeStamp);
|
||||||
|
String subaddress = getLastSubaddress(accountIndex);
|
||||||
|
Timber.d("%d: %s", getNumSubaddresses(accountIndex) - 1, subaddress);
|
||||||
|
return subaddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public native void addSubaddress(int accountIndex, String label);
|
||||||
|
|
||||||
|
public String getLastSubaddress(int accountIndex) {
|
||||||
|
return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public native boolean isKeyOnDevice();
|
||||||
}
|
}
|
||||||
|
@@ -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> {
|
||||||
|
@@ -19,6 +19,7 @@ package com.m2049r.xmrwallet.service;
|
|||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -36,6 +37,7 @@ import com.m2049r.xmrwallet.model.Wallet;
|
|||||||
import com.m2049r.xmrwallet.model.WalletListener;
|
import com.m2049r.xmrwallet.model.WalletListener;
|
||||||
import com.m2049r.xmrwallet.model.WalletManager;
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@@ -219,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;
|
||||||
@@ -418,6 +422,11 @@ public class WalletService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void attachBaseContext(Context context) {
|
||||||
|
super.attachBaseContext(LocaleHelper.setLocale(context, LocaleHelper.getLocale(context)));
|
||||||
|
}
|
||||||
|
|
||||||
public class WalletServiceBinder extends Binder {
|
public class WalletServiceBinder extends Binder {
|
||||||
public WalletService getService() {
|
public WalletService getService() {
|
||||||
return WalletService.this;
|
return WalletService.this;
|
||||||
@@ -528,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");
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user