mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-03 08:23:04 +02:00
Compare commits
95 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
65991ff554 | ||
![]() |
b0629e46e8 | ||
![]() |
45ec3198a0 | ||
![]() |
9b66c466f2 | ||
![]() |
d257e183ad | ||
![]() |
10d8e441fe | ||
![]() |
9f9bc4793d | ||
![]() |
8b28e3ea1e | ||
![]() |
e394394538 | ||
![]() |
7424ef07f7 | ||
![]() |
f5ce6ec824 | ||
![]() |
4215a8bf9e | ||
![]() |
8b016f93dc | ||
![]() |
b239a5094b | ||
![]() |
6a2de36578 | ||
![]() |
5aded68c53 | ||
![]() |
d78a2be120 | ||
![]() |
e6c7800911 | ||
![]() |
cbbe079f67 | ||
![]() |
7fc2dc3ba1 | ||
![]() |
43204d64ef | ||
![]() |
1433143a39 | ||
![]() |
a9a78393a9 | ||
![]() |
fe7ab31050 | ||
![]() |
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 | ||
![]() |
8985511209 | ||
![]() |
3c8a4ce967 | ||
![]() |
fcfedbcfae | ||
![]() |
74279b135a | ||
![]() |
d6d2de8312 | ||
![]() |
af0ecb2894 | ||
![]() |
975cc4f43c | ||
![]() |
74ba36de26 | ||
![]() |
7627e15a48 | ||
![]() |
37244cb9e0 | ||
![]() |
843566b820 | ||
![]() |
0bcf156929 | ||
![]() |
b1d91e2671 | ||
![]() |
271cd2d4a8 | ||
![]() |
22c5a543db | ||
![]() |
cd986860c5 | ||
![]() |
0cf5981eae | ||
![]() |
e109df34f0 | ||
![]() |
5a7aa6cc77 | ||
![]() |
f50629ff81 | ||
![]() |
cb12d64e5f | ||
![]() |
a8f08fb9b9 |
@@ -1,14 +1,14 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion '27.0.3'
|
||||
compileSdkVersion 27
|
||||
buildToolsVersion '28.0.2'
|
||||
defaultConfig {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 25
|
||||
versionCode 91
|
||||
versionName "1.5.1 'CrAzY Nacho'"
|
||||
targetSdkVersion 27
|
||||
versionCode 122
|
||||
versionName "1.7.2 'OpenAlias'"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
@@ -45,8 +45,17 @@ android {
|
||||
// Map for the version code that gives each ABI a value.
|
||||
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.
|
||||
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.
|
||||
variant.outputs.all {
|
||||
output ->
|
||||
@@ -61,11 +70,11 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.android.support:appcompat-v7:25.4.0'
|
||||
implementation 'com.android.support:design:25.4.0'
|
||||
implementation 'com.android.support:support-v4:25.4.0'
|
||||
implementation 'com.android.support:recyclerview-v7:25.4.0'
|
||||
implementation 'com.android.support:cardview-v7:25.4.0'
|
||||
implementation "com.android.support:appcompat-v7:$rootProject.ext.supportVersion"
|
||||
implementation "com.android.support:design:$rootProject.ext.supportVersion"
|
||||
implementation "com.android.support:support-v4:$rootProject.ext.supportVersion"
|
||||
implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion"
|
||||
implementation "com.android.support:cardview-v7:$rootProject.ext.supportVersion"
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
|
||||
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
||||
@@ -73,6 +82,10 @@ dependencies {
|
||||
|
||||
implementation 'com.nulab-inc:zxcvbn:1.2.3'
|
||||
|
||||
implementation 'dnsjava:dnsjava:2.1.8'
|
||||
implementation 'org.jitsi:dnssecjava:1.1.3'
|
||||
implementation 'org.slf4j:slf4j-nop:1.7.25'
|
||||
|
||||
testImplementation "junit:junit:$rootProject.ext.junitVersion"
|
||||
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
||||
|
@@ -8,13 +8,14 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:name=".XmrWalletApplication"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:name=".XmrWalletApplication"
|
||||
android:theme="@style/MyMaterialTheme">
|
||||
|
||||
<activity
|
||||
@@ -28,19 +29,25 @@
|
||||
android:name=".LoginActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="portrait">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<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
|
||||
android:name=".service.WalletService"
|
||||
android:description="@string/service_description"
|
||||
android:exported="false"
|
||||
android:label="Monero Wallet Service" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
826
app/src/main/assets/licenses.html
Normal file
826
app/src/main/assets/licenses.html
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -60,7 +60,15 @@ enum {
|
||||
HASH_DATA_AREA = 136
|
||||
};
|
||||
|
||||
void cn_slow_hash(const void *data, size_t length, char *hash);
|
||||
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed);
|
||||
|
||||
inline void slow_hash(const void *data, const size_t length, char *hash) {
|
||||
cn_slow_hash(data, length, hash, 0 /* variant */, 0/*prehashed*/);
|
||||
}
|
||||
|
||||
inline void slow_hash_broken(const void *data, char *hash, int variant) {
|
||||
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
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();
|
||||
}
|
||||
|
||||
|
||||
}
|
294
app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
Normal file
294
app/src/main/java/com/m2049r/xmrwallet/BaseActivity.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -61,6 +61,7 @@ public class GenerateFragment extends Fragment {
|
||||
static final String TYPE_NEW = "new";
|
||||
static final String TYPE_KEY = "key";
|
||||
static final String TYPE_SEED = "seed";
|
||||
static final String TYPE_LEDGER = "ledger";
|
||||
static final String TYPE_VIEWONLY = "view";
|
||||
|
||||
private TextInputLayout etWalletName;
|
||||
@@ -144,7 +145,8 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
etWalletName.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_NEXT)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
if (checkName()) {
|
||||
etWalletPassword.requestFocus();
|
||||
} // otherwise ignore
|
||||
@@ -182,7 +184,8 @@ public class GenerateFragment extends Fragment {
|
||||
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)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
Helper.hideKeyboard(getActivity());
|
||||
generateWallet();
|
||||
return true;
|
||||
@@ -190,10 +193,23 @@ public class GenerateFragment extends Fragment {
|
||||
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) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
etWalletRestoreHeight.requestFocus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else if (type.equals(TYPE_SEED)) {
|
||||
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_NEXT)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
etWalletMnemonic.requestFocus();
|
||||
return true;
|
||||
}
|
||||
@@ -203,7 +219,8 @@ public class GenerateFragment extends Fragment {
|
||||
etWalletMnemonic.setVisibility(View.VISIBLE);
|
||||
etWalletMnemonic.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_NEXT)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
if (checkMnemonic()) {
|
||||
etWalletRestoreHeight.requestFocus();
|
||||
}
|
||||
@@ -215,7 +232,8 @@ public class GenerateFragment extends Fragment {
|
||||
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
|
||||
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_NEXT)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
etWalletAddress.requestFocus();
|
||||
return true;
|
||||
}
|
||||
@@ -227,7 +245,8 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
{
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
if (checkAddress()) {
|
||||
etWalletViewKey.requestFocus();
|
||||
}
|
||||
@@ -239,7 +258,8 @@ public class GenerateFragment extends Fragment {
|
||||
etWalletViewKey.setVisibility(View.VISIBLE);
|
||||
etWalletViewKey.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_NEXT)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
if (checkViewKey()) {
|
||||
if (type.equals(TYPE_KEY)) {
|
||||
etWalletSpendKey.requestFocus();
|
||||
@@ -259,7 +279,8 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
{
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||
if (checkSpendKey()) {
|
||||
etWalletRestoreHeight.requestFocus();
|
||||
}
|
||||
@@ -273,7 +294,8 @@ public class GenerateFragment extends Fragment {
|
||||
etWalletRestoreHeight.setVisibility(View.VISIBLE);
|
||||
etWalletRestoreHeight.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)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
Helper.hideKeyboard(getActivity());
|
||||
generateWallet();
|
||||
return true;
|
||||
@@ -391,16 +413,24 @@ public class GenerateFragment extends Fragment {
|
||||
// is it a date?
|
||||
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
|
||||
parser.setLenient(false);
|
||||
parser.parse(restoreHeight);
|
||||
height = RestoreHeight.getInstance().getHeight(restoreHeight);
|
||||
} catch (ParseException exPE) {
|
||||
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||
} catch (ParseException ex) {
|
||||
}
|
||||
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 {
|
||||
// or is it a height?
|
||||
height = Long.parseLong(restoreHeight);
|
||||
} catch (NumberFormatException exNFE) {
|
||||
} catch (NumberFormatException ex) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
Timber.d("Using Restore Height = %d", height);
|
||||
return height;
|
||||
}
|
||||
@@ -477,6 +507,12 @@ public class GenerateFragment extends Fragment {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||
}
|
||||
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)) {
|
||||
if (checkAddress() && checkViewKey() && checkSpendKey()) {
|
||||
bGenerate.setEnabled(false);
|
||||
@@ -515,6 +551,8 @@ public class GenerateFragment extends Fragment {
|
||||
return getString(R.string.generate_wallet_type_new);
|
||||
case TYPE_SEED:
|
||||
return getString(R.string.generate_wallet_type_seed);
|
||||
case TYPE_LEDGER:
|
||||
return getString(R.string.generate_wallet_type_ledger);
|
||||
case TYPE_VIEWONLY:
|
||||
return getString(R.string.generate_wallet_type_view);
|
||||
default:
|
||||
@@ -532,6 +570,8 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
|
||||
|
||||
void onGenerateLedger(String name, String password, long height);
|
||||
|
||||
void setTitle(String title);
|
||||
|
||||
void setToolbarButton(int type);
|
||||
@@ -567,6 +607,9 @@ public class GenerateFragment extends Fragment {
|
||||
case TYPE_SEED:
|
||||
inflater.inflate(R.menu.create_wallet_seed, menu);
|
||||
break;
|
||||
case TYPE_LEDGER:
|
||||
inflater.inflate(R.menu.create_wallet_ledger, menu);
|
||||
break;
|
||||
case TYPE_VIEWONLY:
|
||||
inflater.inflate(R.menu.create_wallet_view, menu);
|
||||
break;
|
||||
|
@@ -43,6 +43,8 @@ import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
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.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
@@ -52,9 +54,6 @@ import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.KeyStoreException;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class GenerateReviewFragment extends Fragment {
|
||||
@@ -77,6 +76,9 @@ public class GenerateReviewFragment extends Fragment {
|
||||
private ImageButton bCopyAddress;
|
||||
private LinearLayout llAdvancedInfo;
|
||||
private LinearLayout llPassword;
|
||||
private LinearLayout llMnemonic;
|
||||
private LinearLayout llSpendKey;
|
||||
private LinearLayout llViewKey;
|
||||
private Button bAdvancedInfo;
|
||||
private Button bAccept;
|
||||
|
||||
@@ -100,13 +102,16 @@ public class GenerateReviewFragment extends Fragment {
|
||||
bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
|
||||
llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
|
||||
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);
|
||||
|
||||
boolean testnet = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
|
||||
tvWalletMnemonic.setTextIsSelectable(testnet);
|
||||
tvWalletSpendKey.setTextIsSelectable(testnet);
|
||||
tvWalletPassword.setTextIsSelectable(testnet);
|
||||
boolean allowCopy = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
|
||||
tvWalletMnemonic.setTextIsSelectable(allowCopy);
|
||||
tvWalletSpendKey.setTextIsSelectable(allowCopy);
|
||||
tvWalletPassword.setTextIsSelectable(allowCopy);
|
||||
|
||||
bAccept.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@@ -143,7 +148,6 @@ public class GenerateReviewFragment extends Fragment {
|
||||
}
|
||||
|
||||
void showDetails() {
|
||||
showProgress();
|
||||
tvWalletPassword.setText(null);
|
||||
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR, walletPath);
|
||||
}
|
||||
@@ -189,6 +193,20 @@ public class GenerateReviewFragment extends Fragment {
|
||||
boolean isWatchOnly;
|
||||
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
|
||||
protected Boolean doInBackground(String... params) {
|
||||
if (params.length != 1) return false;
|
||||
@@ -213,7 +231,11 @@ public class GenerateReviewFragment extends Fragment {
|
||||
|
||||
address = wallet.getAddress();
|
||||
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();
|
||||
isWatchOnly = wallet.isWatchOnly();
|
||||
if (closeWallet) wallet.close();
|
||||
@@ -223,6 +245,8 @@ public class GenerateReviewFragment extends Fragment {
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
if (dialogOpened)
|
||||
progressCallback.dismissProgressDialog();
|
||||
if (!isAdded()) return; // never mind
|
||||
walletName = name;
|
||||
if (result) {
|
||||
@@ -233,10 +257,22 @@ public class GenerateReviewFragment extends Fragment {
|
||||
llPassword.setVisibility(View.VISIBLE);
|
||||
tvWalletPassword.setText(getPassword());
|
||||
tvWalletAddress.setText(address);
|
||||
tvWalletMnemonic.setText(seed);
|
||||
tvWalletViewKey.setText(viewKey);
|
||||
tvWalletSpendKey.setText(spendKey);
|
||||
bAdvancedInfo.setVisibility(View.VISIBLE);
|
||||
if (!seed.isEmpty()) {
|
||||
llMnemonic.setVisibility(View.VISIBLE);
|
||||
tvWalletMnemonic.setText(seed);
|
||||
}
|
||||
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.setImageResource(R.drawable.ic_content_copy_black_24dp);
|
||||
activityCallback.setTitle(name, getString(R.string.details_title));
|
||||
@@ -268,6 +304,8 @@ public class GenerateReviewFragment extends Fragment {
|
||||
public interface ProgressListener {
|
||||
void showProgressDialog(int msgId);
|
||||
|
||||
void showLedgerProgressDialog(int mode);
|
||||
|
||||
void dismissProgressDialog();
|
||||
}
|
||||
|
||||
@@ -392,19 +430,19 @@ public class GenerateReviewFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(String... params) {
|
||||
if (params.length != 4) return false;
|
||||
File walletFile = Helper.getWalletFile(getActivity(), params[0]);
|
||||
String oldPassword = params[1];
|
||||
String userPassword = params[2];
|
||||
boolean fingerprintAuthAllowed = Boolean.valueOf(params[3]);
|
||||
if (params.length != 2) return false;
|
||||
final String userPassword = params[0];
|
||||
final boolean fingerPassValid = Boolean.valueOf(params[1]);
|
||||
newPassword = KeyStoreHelper.getCrazyPass(getActivity(), userPassword);
|
||||
boolean success = changeWalletPassword(newPassword);
|
||||
final boolean success = changeWalletPassword(newPassword);
|
||||
if (success) {
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), walletName, userPassword);
|
||||
} else {
|
||||
KeyStoreHelper.removeWalletUserPass(getActivity(), walletName);
|
||||
}
|
||||
Context ctx = getActivity();
|
||||
if (ctx != null)
|
||||
if (fingerPassValid) {
|
||||
KeyStoreHelper.saveWalletUserPass(ctx, walletName, userPassword);
|
||||
} else {
|
||||
KeyStoreHelper.removeWalletUserPass(ctx, walletName);
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
@@ -412,7 +450,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
if (getActivity().isDestroyed()) {
|
||||
if ((getActivity() == null) || getActivity().isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
if (progressCallback != null)
|
||||
@@ -467,11 +505,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
swFingerprintAllowed.setChecked(FingerprintHelper.isFingerprintAuthAllowed(walletName));
|
||||
} catch (KeyStoreException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
swFingerprintAllowed.setChecked(FingerprintHelper.isFingerPassValid(getActivity(), walletName));
|
||||
}
|
||||
|
||||
etPasswordA.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
@@ -547,7 +581,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
} else if (!newPasswordA.equals(newPasswordB)) {
|
||||
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
|
||||
} else if (newPasswordA.equals(newPasswordB)) {
|
||||
new AsyncChangePassword().execute(walletName, getPassword(), newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
||||
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
||||
Helper.hideKeyboardAlways(getActivity());
|
||||
openDialog.dismiss();
|
||||
openDialog = null;
|
||||
@@ -560,7 +594,8 @@ public class GenerateReviewFragment extends Fragment {
|
||||
// accept keyboard "ok"
|
||||
etPasswordB.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)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
String newPasswordA = etPasswordA.getEditText().getText().toString();
|
||||
String newPasswordB = etPasswordB.getEditText().getText().toString();
|
||||
// disallow empty passwords
|
||||
@@ -569,7 +604,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
} else if (!newPasswordA.equals(newPasswordB)) {
|
||||
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
|
||||
} else if (newPasswordA.equals(newPasswordB)) {
|
||||
new AsyncChangePassword().execute(walletName, getPassword(), newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
||||
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
||||
Helper.hideKeyboardAlways(getActivity());
|
||||
openDialog.dismiss();
|
||||
openDialog = null;
|
||||
@@ -582,4 +617,10 @@ public class GenerateReviewFragment extends Fragment {
|
||||
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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2017 m2049r
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import com.m2049r.xmrwallet.data.BarcodeData;
|
||||
|
||||
public interface OnUriScannedListener {
|
||||
boolean onUriScanned(BarcodeData barcodeData);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -16,13 +16,12 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
|
||||
import java.io.File;
|
||||
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams;
|
||||
|
||||
@@ -36,4 +35,9 @@ public abstract class SecureActivity extends AppCompatActivity {
|
||||
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context context) {
|
||||
super.attachBaseContext(LocaleHelper.setLocale(context, LocaleHelper.getLocale(context)));
|
||||
}
|
||||
}
|
||||
|
@@ -59,6 +59,7 @@ public class TxFragment extends Fragment {
|
||||
TS_FORMATTER.setTimeZone(tz);
|
||||
}
|
||||
|
||||
private TextView tvAccount;
|
||||
private TextView tvTxTimestamp;
|
||||
private TextView tvTxId;
|
||||
private TextView tvTxKey;
|
||||
@@ -88,6 +89,7 @@ public class TxFragment extends Fragment {
|
||||
tvDestinationBtc = (TextView) view.findViewById(R.id.tvDestinationBtc);
|
||||
tvTxAmountBtc = (TextView) view.findViewById(R.id.tvTxAmountBtc);
|
||||
|
||||
tvAccount = (TextView) view.findViewById(R.id.tvAccount);
|
||||
tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
|
||||
tvTxId = (TextView) view.findViewById(R.id.tvTxId);
|
||||
tvTxKey = (TextView) view.findViewById(R.id.tvTxKey);
|
||||
@@ -222,6 +224,8 @@ public class TxFragment extends Fragment {
|
||||
activityCallback.setSubtitle(getString(R.string.tx_title));
|
||||
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||
|
||||
tvAccount.setText(getString(R.string.tx_account_formatted, info.account, info.subaddress));
|
||||
|
||||
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
|
||||
tvTxId.setText(info.hash);
|
||||
tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey);
|
||||
@@ -236,9 +240,6 @@ public class TxFragment extends Fragment {
|
||||
String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-");
|
||||
|
||||
long realAmount = info.amount;
|
||||
if (info.isPending) {
|
||||
realAmount = realAmount - info.fee;
|
||||
}
|
||||
tvTxAmount.setText(sign + Wallet.getDisplayAmount(realAmount));
|
||||
|
||||
if ((info.fee > 0)) {
|
||||
@@ -286,7 +287,10 @@ public class TxFragment extends Fragment {
|
||||
}
|
||||
} else {
|
||||
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());
|
||||
tvDestination.setText(dstSb.toString());
|
||||
@@ -321,7 +325,7 @@ public class TxFragment extends Fragment {
|
||||
Listener activityCallback;
|
||||
|
||||
public interface Listener {
|
||||
String getWalletAddress();
|
||||
String getWalletSubaddress(int accountIndex, int subaddressIndex);
|
||||
|
||||
String getTxKey(String hash);
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -43,9 +43,7 @@ import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
|
||||
import com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.OkHttpClientSingleton;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
@@ -101,7 +99,9 @@ public class WalletFragment extends Fragment
|
||||
ivSynced = (ImageView) view.findViewById(R.id.ivSynced);
|
||||
|
||||
sCurrency = (Spinner) view.findViewById(R.id.sCurrency);
|
||||
sCurrency.setAdapter(ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner_balance));
|
||||
ArrayAdapter currencyAdapter = ArrayAdapter.createFromResource(getContext(), R.array.currency, R.layout.item_spinner_balance);
|
||||
currencyAdapter.setDropDownViewResource(R.layout.item_spinner_dropdown_item);
|
||||
sCurrency.setAdapter(currencyAdapter);
|
||||
|
||||
bSend = (Button) view.findViewById(R.id.bSend);
|
||||
bReceive = (Button) view.findViewById(R.id.bReceive);
|
||||
@@ -150,7 +150,7 @@ public class WalletFragment extends Fragment
|
||||
// at this point selection is XMR in case of error
|
||||
String displayB;
|
||||
double amountA = Double.parseDouble(Wallet.getDisplayAmount(unlockedBalance)); // crash if this fails!
|
||||
if (!"XMR".equals(balanceCurrency)) { // not XMR
|
||||
if (!Helper.CRYPTO.equals(balanceCurrency)) { // not XMR
|
||||
double amountB = amountA * balanceRate;
|
||||
displayB = Helper.getFormattedAmount(amountB, false);
|
||||
} else { // XMR
|
||||
@@ -159,10 +159,10 @@ public class WalletFragment extends Fragment
|
||||
tvBalance.setText(displayB);
|
||||
}
|
||||
|
||||
String balanceCurrency = "XMR";
|
||||
String balanceCurrency = Helper.CRYPTO;
|
||||
double balanceRate = 1.0;
|
||||
|
||||
private final ExchangeApi exchangeApi = new ExchangeApiImpl(OkHttpClientSingleton.getOkHttpClient());
|
||||
private final ExchangeApi exchangeApi = Helper.getExchangeApi();
|
||||
|
||||
void refreshBalance() {
|
||||
if (sCurrency.getSelectedItemPosition() == 0) { // XMR
|
||||
@@ -170,9 +170,10 @@ public class WalletFragment extends Fragment
|
||||
tvBalance.setText(Helper.getFormattedAmount(amountXmr, true));
|
||||
} else { // not XMR
|
||||
String currency = (String) sCurrency.getSelectedItem();
|
||||
Timber.d(currency);
|
||||
if (!currency.equals(balanceCurrency) || (balanceRate <= 0)) {
|
||||
showExchanging();
|
||||
exchangeApi.queryExchangeRate("XMR", currency,
|
||||
exchangeApi.queryExchangeRate(Helper.CRYPTO, currency,
|
||||
new ExchangeCallback() {
|
||||
@Override
|
||||
public void onSuccess(final ExchangeRate exchangeRate) {
|
||||
@@ -228,10 +229,10 @@ public class WalletFragment extends Fragment
|
||||
|
||||
public void exchange(final ExchangeRate exchangeRate) {
|
||||
hideExchanging();
|
||||
if (!"XMR".equals(exchangeRate.getBaseCurrency())) {
|
||||
if (!Helper.CRYPTO.equals(exchangeRate.getBaseCurrency())) {
|
||||
Timber.e("Not XMR");
|
||||
sCurrency.setSelection(0, true);
|
||||
balanceCurrency = "XMR";
|
||||
balanceCurrency = Helper.CRYPTO;
|
||||
balanceRate = 1.0;
|
||||
} else {
|
||||
int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency());
|
||||
@@ -256,7 +257,7 @@ public class WalletFragment extends Fragment
|
||||
// called from activity
|
||||
|
||||
public void onRefreshed(final Wallet wallet, final boolean full) {
|
||||
Timber.d("onRefreshed()");
|
||||
Timber.d("onRefreshed(%b)", full);
|
||||
if (full) {
|
||||
List<TransactionInfo> list = wallet.getHistory().getAll();
|
||||
adapter.setInfos(list);
|
||||
@@ -270,6 +271,7 @@ public class WalletFragment extends Fragment
|
||||
bSend.setVisibility(View.VISIBLE);
|
||||
bSend.setEnabled(true);
|
||||
}
|
||||
if (isVisible()) enableAccountsList(true); //otherwise it is enabled in onResume()
|
||||
}
|
||||
|
||||
boolean walletLoaded = false;
|
||||
@@ -313,7 +315,7 @@ public class WalletFragment extends Fragment
|
||||
if (wallet == null) return;
|
||||
walletTitle = wallet.getName();
|
||||
String watchOnly = (wallet.isWatchOnly() ? getString(R.string.label_watchonly) : "");
|
||||
walletSubtitle = wallet.getAddress().substring(0, 10) + "…" + watchOnly;
|
||||
walletSubtitle = wallet.getAccountLabel();
|
||||
activityCallback.setTitle(walletTitle, walletSubtitle);
|
||||
Timber.d("wallet title is %s", walletTitle);
|
||||
}
|
||||
@@ -323,10 +325,13 @@ public class WalletFragment extends Fragment
|
||||
private String walletSubtitle = null;
|
||||
private long unlockedBalance = 0;
|
||||
|
||||
private int accountIdx = -1;
|
||||
|
||||
private void updateStatus(Wallet wallet) {
|
||||
if (!isAdded()) return;
|
||||
Timber.d("updateStatus()");
|
||||
if (walletTitle == null) {
|
||||
if ((walletTitle == null) || (accountIdx != wallet.getAccountIndex())) {
|
||||
accountIdx = wallet.getAccountIndex();
|
||||
setActivityTitle(wallet);
|
||||
}
|
||||
long balance = wallet.getBalance();
|
||||
@@ -412,9 +417,28 @@ public class WalletFragment extends Fragment
|
||||
super.onResume();
|
||||
Timber.d("onResume()");
|
||||
activityCallback.setTitle(walletTitle, walletSubtitle);
|
||||
activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE);
|
||||
//activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE); // TODO: Close button somewhere else
|
||||
activityCallback.setToolbarButton(Toolbar.BUTTON_NONE);
|
||||
setProgress(syncProgress);
|
||||
setProgress(syncText);
|
||||
showReceive();
|
||||
if (activityCallback.isSynced()) enableAccountsList(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
enableAccountsList(false);
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
public interface DrawerLocker {
|
||||
void setDrawerEnabled(boolean enabled);
|
||||
}
|
||||
|
||||
private void enableAccountsList(boolean enable) {
|
||||
if (activityCallback instanceof DrawerLocker) {
|
||||
((DrawerLocker) activityCallback).setDrawerEnabled(enable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -18,8 +18,10 @@ package com.m2049r.xmrwallet;
|
||||
|
||||
|
||||
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;
|
||||
|
||||
@@ -27,9 +29,21 @@ public class XmrWalletApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@@ -18,10 +18,9 @@ package com.m2049r.xmrwallet.data;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import com.m2049r.xmrwallet.model.NetworkType;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
|
||||
import com.m2049r.xmrwallet.util.OpenAliasHelper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -29,9 +28,14 @@ import java.util.Map;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class BarcodeData {
|
||||
|
||||
public static final String XMR_SCHEME = "monero:";
|
||||
public static final String XMR_PAYMENTID = "tx_payment_id";
|
||||
public static final String XMR_AMOUNT = "tx_amount";
|
||||
public static final String XMR_DESCRIPTION = "tx_description";
|
||||
|
||||
public static final String OA_XMR_ASSET = "xmr";
|
||||
public static final String OA_BTC_ASSET = "btc";
|
||||
|
||||
static final String BTC_SCHEME = "bitcoin:";
|
||||
static final String BTC_AMOUNT = "amount";
|
||||
@@ -40,10 +44,24 @@ public class BarcodeData {
|
||||
XMR, BTC
|
||||
}
|
||||
|
||||
public enum Security {
|
||||
NORMAL,
|
||||
OA_NO_DNSSEC,
|
||||
OA_DNSSEC
|
||||
}
|
||||
|
||||
public Asset asset = null;
|
||||
public String addressName = null;
|
||||
public String address = null;
|
||||
public String paymentId = null;
|
||||
public String amount = null;
|
||||
public String description = null;
|
||||
public Security security = Security.NORMAL;
|
||||
|
||||
public BarcodeData(String uri) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address) {
|
||||
this.asset = asset;
|
||||
@@ -63,6 +81,48 @@ public class BarcodeData {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public BarcodeData(Asset asset, String address, String paymentId, String description, String amount) {
|
||||
this.asset = asset;
|
||||
this.address = address;
|
||||
this.paymentId = paymentId;
|
||||
this.description = description;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public void setAddressName(String name) {
|
||||
addressName = name;
|
||||
}
|
||||
|
||||
public void setSecurity(Security security) {
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
return Uri.parse(getUriString());
|
||||
}
|
||||
|
||||
public String getUriString() {
|
||||
if (asset != Asset.XMR) throw new IllegalStateException("We can only do XMR stuff!");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(BarcodeData.XMR_SCHEME).append(address);
|
||||
boolean first = true;
|
||||
if ((paymentId != null) && !paymentId.isEmpty()) {
|
||||
sb.append("?");
|
||||
first = false;
|
||||
sb.append(BarcodeData.XMR_PAYMENTID).append('=').append(paymentId);
|
||||
}
|
||||
if ((description != null) && !description.isEmpty()) {
|
||||
sb.append(first ? "?" : "&");
|
||||
first = false;
|
||||
sb.append(BarcodeData.XMR_DESCRIPTION).append('=').append(Uri.encode(description));
|
||||
}
|
||||
if ((amount != null) && !amount.isEmpty()) {
|
||||
sb.append(first ? "?" : "&");
|
||||
sb.append(BarcodeData.XMR_AMOUNT).append('=').append(amount);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static public BarcodeData fromQrCode(String qrCode) {
|
||||
// check for monero uri
|
||||
BarcodeData bcData = parseMoneroUri(qrCode);
|
||||
@@ -74,10 +134,14 @@ public class BarcodeData {
|
||||
if (bcData == null) {
|
||||
bcData = parseBitcoinUri(qrCode);
|
||||
}
|
||||
// check for naked btc addres
|
||||
// check for naked btc address
|
||||
if (bcData == null) {
|
||||
bcData = parseBitcoinNaked(qrCode);
|
||||
}
|
||||
// check for OpenAlias
|
||||
if (bcData == null) {
|
||||
bcData = parseOpenAlias(qrCode);
|
||||
}
|
||||
return bcData;
|
||||
}
|
||||
|
||||
@@ -112,6 +176,7 @@ public class BarcodeData {
|
||||
}
|
||||
String address = monero.getPath();
|
||||
String paymentId = parms.get(XMR_PAYMENTID);
|
||||
String description = parms.get(XMR_DESCRIPTION);
|
||||
String amount = parms.get(XMR_AMOUNT);
|
||||
if (amount != null) {
|
||||
try {
|
||||
@@ -130,7 +195,7 @@ public class BarcodeData {
|
||||
Timber.d("address invalid");
|
||||
return null;
|
||||
}
|
||||
return new BarcodeData(Asset.XMR, address, paymentId, amount);
|
||||
return new BarcodeData(Asset.XMR, address, paymentId, description, amount);
|
||||
}
|
||||
|
||||
static public BarcodeData parseMoneroNaked(String address) {
|
||||
@@ -198,4 +263,61 @@ public class BarcodeData {
|
||||
|
||||
return new BarcodeData(BarcodeData.Asset.BTC, address);
|
||||
}
|
||||
|
||||
static public BarcodeData parseOpenAlias(String oaString) {
|
||||
Timber.d("parseOpenAlias=%s", oaString);
|
||||
if (oaString == null) return null;
|
||||
|
||||
Map<String, String> oaAttrs = OpenAliasHelper.parse(oaString);
|
||||
if (oaAttrs == null) return null;
|
||||
|
||||
String oaAsset = oaAttrs.get(OpenAliasHelper.OA1_ASSET);
|
||||
if (oaAsset == null) return null;
|
||||
|
||||
String address = oaAttrs.get(OpenAliasHelper.OA1_ADDRESS);
|
||||
if (address == null) return null;
|
||||
|
||||
Asset asset;
|
||||
if (OA_XMR_ASSET.equals(oaAsset)) {
|
||||
if (!Wallet.isAddressValid(address)) {
|
||||
Timber.d("XMR address invalid");
|
||||
return null;
|
||||
}
|
||||
asset = Asset.XMR;
|
||||
} else if (OA_BTC_ASSET.equals(oaAsset)) {
|
||||
if (!BitcoinAddressValidator.validate(address)) {
|
||||
Timber.d("BTC address invalid");
|
||||
return null;
|
||||
}
|
||||
asset = Asset.BTC;
|
||||
} else {
|
||||
Timber.i("Unsupported OpenAlias asset %s", oaAsset);
|
||||
return null;
|
||||
}
|
||||
|
||||
String paymentId = oaAttrs.get(OpenAliasHelper.OA1_PAYMENTID);
|
||||
String description = oaAttrs.get(OpenAliasHelper.OA1_DESCRIPTION);
|
||||
if (description == null) {
|
||||
description = oaAttrs.get(OpenAliasHelper.OA1_NAME);
|
||||
}
|
||||
String amount = oaAttrs.get(OpenAliasHelper.OA1_AMOUNT);
|
||||
String addressName = oaAttrs.get(OpenAliasHelper.OA1_NAME);
|
||||
|
||||
if (amount != null) {
|
||||
try {
|
||||
Double.parseDouble(amount);
|
||||
} catch (NumberFormatException ex) {
|
||||
Timber.d(ex.getLocalizedMessage());
|
||||
return null; // we have an amount but its not a number!
|
||||
}
|
||||
}
|
||||
if ((paymentId != null) && !Wallet.isPaymentIdValid(paymentId)) {
|
||||
Timber.d("paymentId invalid");
|
||||
return null;
|
||||
}
|
||||
|
||||
BarcodeData bc = new BarcodeData(asset, address, paymentId, description, amount);
|
||||
bc.setAddressName(addressName);
|
||||
return bc;
|
||||
}
|
||||
}
|
@@ -32,6 +32,14 @@ import android.widget.TextView;
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class AboutFragment extends DialogFragment {
|
||||
static final String TAG = "AboutFragment";
|
||||
|
||||
@@ -52,7 +60,7 @@ public class AboutFragment extends DialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_about, null);
|
||||
((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getString(R.string.about_licenses)));
|
||||
((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getLicencesHtml()));
|
||||
((TextView) view.findViewById(R.id.tvVersion)).setText(getString(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
@@ -66,4 +74,18 @@ public class AboutFragment extends DialogFragment {
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private String getLicencesHtml() {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(getContext().getAssets().open("licenses.html"), StandardCharsets.UTF_8))) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null)
|
||||
sb.append(line);
|
||||
return sb.toString();
|
||||
} catch (IOException ex) {
|
||||
Timber.e(ex);
|
||||
return ex.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,9 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
@@ -57,8 +60,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
|
||||
private TextView tvFunds;
|
||||
private ExchangeTextView evAmount;
|
||||
//private Button bSendAll;
|
||||
private NumberPadView numberPad;
|
||||
private View llAmount;
|
||||
private View rlSweep;
|
||||
private ImageButton ibSweep;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
@@ -73,37 +77,64 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
||||
tvFunds = (TextView) view.findViewById(R.id.tvFunds);
|
||||
|
||||
evAmount = (ExchangeTextView) view.findViewById(R.id.evAmount);
|
||||
numberPad = (NumberPadView) view.findViewById(R.id.numberPad);
|
||||
numberPad.setListener(evAmount);
|
||||
((NumberPadView) view.findViewById(R.id.numberPad)).setListener(evAmount);
|
||||
|
||||
/*
|
||||
bSendAll = (Button) view.findViewById(R.id.bSendAll);
|
||||
bSendAll.setOnClickListener(new View.OnClickListener() {
|
||||
rlSweep = view.findViewById(R.id.rlSweep);
|
||||
llAmount = view.findViewById(R.id.llAmount);
|
||||
view.findViewById(R.id.ivSweep).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
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());
|
||||
|
||||
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
|
||||
public boolean onValidateFields() {
|
||||
if (!evAmount.validate(maxFunds)) {
|
||||
return false;
|
||||
}
|
||||
if (spendAllMode) {
|
||||
if (sendListener != null) {
|
||||
sendListener.getTxData().setAmount(Wallet.SWEEP_ALL);
|
||||
}
|
||||
} else {
|
||||
if (!evAmount.validate(maxFunds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sendListener != null) {
|
||||
String xmr = evAmount.getAmount();
|
||||
if (xmr != null) {
|
||||
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
|
||||
} else {
|
||||
sendListener.getTxData().setAmount(0L);
|
||||
if (sendListener != null) {
|
||||
String xmr = evAmount.getAmount();
|
||||
if (xmr != null) {
|
||||
sendListener.getTxData().setAmount(Wallet.getAmountFromString(xmr));
|
||||
} else {
|
||||
sendListener.getTxData().setAmount(0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@@ -247,7 +247,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
|
||||
private XmrToApi xmrToApi = null;
|
||||
|
||||
private final XmrToApi getXmrToApi() {
|
||||
private XmrToApi getXmrToApi() {
|
||||
if (xmrToApi == null) {
|
||||
synchronized (this) {
|
||||
if (xmrToApi == null) {
|
||||
|
@@ -216,9 +216,9 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendFailed() {
|
||||
Timber.e("SEND FAILED");
|
||||
public void sendFailed(String error) {
|
||||
pbProgressSend.setVisibility(View.INVISIBLE);
|
||||
Toast.makeText(getContext(), getString(R.string.status_transaction_failed, error), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -421,7 +421,8 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
// accept keyboard "ok"
|
||||
etPassword.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)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (getActivityCallback().verifyWalletPassword(pass)) {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
|
@@ -19,7 +19,7 @@ package com.m2049r.xmrwallet.fragment.send;
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
|
||||
interface SendConfirm {
|
||||
void sendFailed();
|
||||
void sendFailed(String errorText);
|
||||
|
||||
void createTransactionFailed(String errorText);
|
||||
|
||||
|
@@ -145,17 +145,22 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendFailed() {
|
||||
public void sendFailed(String errorText) {
|
||||
pbProgressSend.setVisibility(View.INVISIBLE);
|
||||
showAlert(getString(R.string.send_create_tx_error_title), errorText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createTransactionFailed(String errorText) {
|
||||
hideProgress();
|
||||
showAlert(getString(R.string.send_create_tx_error_title), errorText);
|
||||
}
|
||||
|
||||
private void showAlert(String title, String message) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setCancelable(true).
|
||||
setTitle(getString(R.string.send_create_tx_error_title)).
|
||||
setMessage(errorText).
|
||||
setTitle(title).
|
||||
setMessage(message).
|
||||
create().
|
||||
show();
|
||||
}
|
||||
@@ -297,7 +302,8 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
||||
// accept keyboard "ok"
|
||||
etPassword.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)) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (getActivityCallback().verifyWalletPassword(pass)) {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user