1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-04 00:53:36 +02:00

Compare commits

...

29 Commits

Author SHA1 Message Date
m2049r
c64fb1a769 Merge pull request #954 from m2049r/feature/show_connected
show when sidekick is connected
2024-09-19 22:44:00 +02:00
m2049r
2d2a7d2db0 show when sidekick is connected 2024-09-15 20:46:00 +02:00
m2049r
ac9e24ee9f Merge pull request #952 from m2049r/feature/sidekick_v
Support for Sidekick device
2024-09-06 00:45:48 +02:00
m2049r
059438a09e targetSdkVersion 35 2024-09-05 23:13:11 +02:00
m2049r
d22fdfe2dc Sidekick Support for Monerokon 2024-09-05 23:12:42 +02:00
m2049r
48577e46aa lock in background (#936) 2024-04-05 16:03:30 +02:00
m2049r
451371cd92 refactor onBackPressed to use the callback dispatcher (#937) 2024-04-05 15:57:37 +02:00
m2049r
cd6f646b63 bump version 2024-03-17 14:10:14 +01:00
m2049r
aa530caa28 improve german transaltion. remove permissions translations 2024-03-17 14:04:49 +01:00
m2049r
5df43f1274 fix translation errors 2024-03-17 13:38:36 +01:00
m2049r
3b0cb3ffdb upgrade gradle 2024-03-17 13:28:22 +01:00
m2049r
300551dbe4 update zlib to 1.3.1 2024-03-17 13:06:05 +01:00
m2049r
942519adb7 ignore monero native stuff 2024-03-17 12:53:51 +01:00
m2049r
0652a8ba14 upgrade dnsjava (#924) 2024-01-13 17:33:23 +01:00
m2049r
04d0bd2ffb fix tests 2024-01-13 16:14:05 +01:00
m2049r
8473e66c69 upgrade dependencies 2024-01-13 15:58:00 +01:00
m2049r
2218ff615c fix unescaped quotes 2024-01-13 15:41:23 +01:00
m2049r
54b55b9f8f optimize build time 2024-01-13 15:33:21 +01:00
m2049r
5abf84f62b optimize build time 2024-01-13 15:31:14 +01:00
m2049r
6ff75d221f add RSD (#923) 2024-01-13 14:28:30 +01:00
audioz
c90f107c3c Hebrew Translation (#895) 2024-01-13 14:13:56 +01:00
Digitale Souveränität - JETZT!
a3db18032e add ds-jetzt node (#905)
add ds-jetzt node as clear & onion
2024-01-13 14:13:18 +01:00
anhdres
9db2c10679 updated texts for eng and spa (#911)
Co-authored-by: m2049r <m2049r@monerujo.io>
2024-01-13 14:11:41 +01:00
XD22
eab85c7f0a Fix Docker libs Builds (#918)
Co-authored-by: m2049r <m2049r@monerujo.io>
2024-01-13 14:08:02 +01:00
kuznetsovgm
6bf3229e77 fixed a translation error (#912) 2024-01-13 14:05:52 +01:00
audioz
9f4f626acb Docker build fix. (#896)
* Hebrew Translation

* ~~ https://zlib.net/
Version 1.2.13 has these key updates from 1.2.12:

    Fix a bug when getting a gzip header extra field with inflateGetHeader(). This remedies CVE-2022-37434.
    Fix a bug in block type selection when Z_FIXED used. Now the smallest block type is selected, for better compression.
    Fix a configure issue that discarded the provided CC definition.
    Correct incorrect inputs provided to the CRC functions. This mitigates a bug in Java.
    Repair prototypes and exporting of the new CRC functions.
    Fix inflateBack to detect invalid input with distances too far.

Due to the first bug fix, any installations of 1.2.12 or earlier should be replaced with 1.2.13.

* Delete app/src/main/res/values-he directory
2024-01-13 14:05:22 +01:00
XD22
434dab55ba Fix TalkBack Screen reader (#917)
* Fixing TalkBack p1

* Fix TalkBack español translate p2

* Fix TalkBack français translate p3

* Escape some apostrophe, Fix wrong Locale codes
2024-01-13 14:03:31 +01:00
m2049r
59cc6b1864 upgrade dependencies 2024-01-13 13:59:38 +01:00
m2049r
a6e9d0e77c use yadio for pricing "exotic" fiat (#921)
* yadio api

* add more currencies

* fix es typo

* bump version & fix cicleci build
2023-12-10 14:24:35 +01:00
167 changed files with 5798 additions and 2501 deletions

View File

@@ -3,7 +3,7 @@ jobs:
build:
working_directory: ~/code
docker:
- image: cimg/android:2022.03-ndk
- image: cimg/android:2023.12-ndk
environment:
JVM_OPTS: -Xmx3200m
steps:

10
.gitignore vendored
View File

@@ -8,11 +8,9 @@
.DS_Store
/app/build
/app/release
/app/alpha
/app/prod
/app/alphaMainnet
/app/prodMainnet
/app/alphaStagenet
/app/prodStagenet
/app/alpha*
/app/prod*
/app/.cxx
/monerujo.id
/external-libs/VERSION
/external-libs/include/wallet2_api.h

View File

@@ -4,12 +4,12 @@ android {
ndkVersion '17.2.4988734'
defaultConfig {
applicationId "com.m2049r.xmrwallet"
buildToolsVersion = '34.0.0'
compileSdk 33
buildToolsVersion = '35.0.0'
compileSdk 35
minSdkVersion 21
targetSdkVersion 33
versionCode 3308
versionName "3.3.8 'Pocket Change'"
targetSdkVersion 35
versionCode 4006
versionName "4.0.6 'Sidekick'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -24,7 +24,7 @@ android {
}
}
flavorDimensions 'type', 'net'
flavorDimensions = ['type', 'net']
productFlavors {
mainnet {
dimension 'net'
@@ -112,6 +112,17 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
namespace 'com.m2049r.xmrwallet'
buildFeatures {
buildConfig true
}
testOptions {
unitTests {
all {
jvmArgs '--add-opens=java.base/java.lang=ALL-UNNAMED'
}
}
}
}
static def getId(name) {
@@ -121,41 +132,40 @@ static def getId(name) {
}
dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.22"))
implementation 'androidx.core:core:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core:1.13.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.preference:preference:1.2.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation "com.squareup.okhttp3:okhttp:4.9.3"
implementation "io.github.rburgst:okhttp-digest:2.6"
implementation "com.squareup.okhttp3:okhttp:4.12.0"
implementation "io.github.rburgst:okhttp-digest:3.1.0"
implementation "com.jakewharton.timber:timber:5.0.1"
implementation 'info.guardianproject.netcipher:netcipher:2.1.0'
//implementation 'info.guardianproject.netcipher:netcipher-okhttp3:2.1.0'
implementation fileTree(dir: 'libs/classes', include: ['*.jar'])
implementation 'com.nulab-inc:zxcvbn:1.5.2'
implementation 'com.nulab-inc:zxcvbn:1.8.2'
implementation 'dnsjava:dnsjava:2.1.9'
implementation 'org.jitsi:dnssecjava:1.2.0'
implementation 'org.slf4j:slf4j-nop:1.7.36'
implementation 'dnsjava:dnsjava:3.5.3'
implementation 'org.slf4j:slf4j-nop:2.0.11'
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
//noinspection GradleDependency
testImplementation "junit:junit:4.13.2"
testImplementation "org.mockito:mockito-all:1.10.19"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
testImplementation 'org.json:json:20211205'
testImplementation "com.squareup.okhttp3:mockwebserver:4.12.0"
testImplementation 'org.json:json:20231013'
testImplementation 'net.jodah:concurrentunit:0.4.6'
compileOnly 'org.projectlombok:lombok:1.18.22'
annotationProcessor 'org.projectlombok:lombok:1.18.22'
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
}

View File

@@ -5,6 +5,11 @@
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
@@ -12,6 +17,7 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<queries>
<intent>
@@ -98,7 +104,12 @@
android:name=".service.WalletService"
android:description="@string/service_description"
android:exported="false"
android:label="Monero Wallet Service" />
android:foregroundServiceType="specialUse"
android:label="Monero Wallet Service">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Keeps app in sync with the blockchain" />
</service>
<provider
android:name="androidx.core.content.FileProvider"

View File

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

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017 m2049r
* Copyright (c) 2017-2024 m2049r
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@
#include <jni.h>
#include <string>
/*
#include <android/log.h>
@@ -28,6 +30,10 @@
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
*/
void ThrowException(JNIEnv *jenv, const char* type, const char* msg) {
jenv->ThrowNew(jenv->FindClass(type), msg);
}
jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
jclass c = env->GetObjectClass(obj);
return env->GetFieldID(c, fieldName, "J"); // of type long
@@ -35,8 +41,16 @@ jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handl
template<typename T>
T *getHandle(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
return reinterpret_cast<T *>(env->GetLongField(obj, getHandleField(env, obj, fieldName)));
}
template<typename T>
void destroyNativeObject(JNIEnv *env, T nativeObjectHandle, jobject obj, const char *fieldName = "handle") {
jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName));
return reinterpret_cast<T *>(handle);
if (handle != 0) {
ThrowException(env, "java/lang/IllegalStateException", "invalid handle (destroy)");
}
delete reinterpret_cast<T *>(nativeObjectHandle);
}
void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) {
@@ -54,7 +68,7 @@ extern "C"
{
#endif
extern const char* const MONERO_VERSION; // the actual monero core version
extern const char *const MONERO_VERSION; // the actual monero core version
// from monero-core crypto/hash-ops.h - avoid #including monero code here
enum {
@@ -62,18 +76,40 @@ enum {
HASH_DATA_AREA = 136
};
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height);
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed,
uint64_t height);
inline void slow_hash(const void *data, const size_t length, char *hash) {
cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/);
}
inline void slow_hash_broken(const void *data, char *hash, int variant) {
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, 0 /*height*/);
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/,
0 /*height*/);
}
#ifdef __cplusplus
}
#endif
namespace Monerujo {
class SidekickWallet {
public:
enum Status {
Status_Ok,
Status_Error,
Status_Critical
};
SidekickWallet(uint8_t networkType, std::string a, std::string b);
~SidekickWallet();
std::string call(int commandId, const std::string &request);
void reset();
Status status() const;
};
}
#endif //XMRWALLET_WALLET_LIB_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -16,12 +16,26 @@
package com.m2049r.xmrwallet;
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.dialog.ProgressDialog;
@@ -35,18 +49,13 @@ public class BaseActivity extends SecureActivity
ProgressDialog progressDialog = null;
private class SimpleProgressDialog extends ProgressDialog {
private static 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
@@ -59,13 +68,15 @@ public class BaseActivity extends SecureActivity
progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
if (delayMillis > 0) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
if (progressDialog != null) progressDialog.show();
handler.postDelayed(() -> {
if (progressDialog != null) {
progressDialog.show();
disableBackPressed();
}
}, delayMillis);
} else {
progressDialog.show();
disableBackPressed();
}
}
@@ -75,6 +86,7 @@ public class BaseActivity extends SecureActivity
progressDialog = new LedgerProgressDialog(BaseActivity.this, mode);
Ledger.setListener((Ledger.Listener) progressDialog);
progressDialog.show();
disableBackPressed();
}
@Override
@@ -87,6 +99,28 @@ public class BaseActivity extends SecureActivity
progressDialog.dismiss();
}
progressDialog = null;
enableBackPressed();
}
OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
// no going back
}
};
public void disableBackPressed() {
backPressedCallback.setEnabled(true);
}
public void enableBackPressed() {
backPressedCallback.setEnabled(false);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
}
static final int RELEASE_WAKE_LOCK_DELAY = 5000; // millisconds
@@ -136,4 +170,87 @@ public class BaseActivity extends SecureActivity
barcodeData = null;
return popped;
}
public boolean isNetworkAvailable() {
ConnectivityManager connectivityManager = (ConnectivityManager) getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = connectivityManager.getActiveNetwork();
if (network == null) return false;
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
return networkCapabilities != null && (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH));
} else {
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
}
static private final int REQUEST_CODE_BLUETOOTH_PERMISSIONS = 32423;
void btPermissionGranted() {
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_BLUETOOTH_PERMISSIONS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
btPermissionGranted();
} // else onResume() takes care of trying again
}
}
private void showBtPermissionsDialog() {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setMessage(R.string.bluetooth_permissions);
alertDialogBuilder.setPositiveButton(R.string.bluetooth_permissions_ok,
(dialog, which) -> requestBtPermissions());
alertDialogBuilder.setCancelable(false);
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
private void showAppInfoDialog() {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setMessage(R.string.bluetooth_permissions);
alertDialogBuilder.setPositiveButton(R.string.bluetooth_permissions_settings, (dialog, which) -> {
Intent i = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
i.addCategory(Intent.CATEGORY_DEFAULT);
i.setData(Uri.parse("package:" + getPackageName()));
startActivity(i);
});
alertDialogBuilder.setNegativeButton(R.string.bluetooth_permissions_cancel, (dialog, which) -> {
finish();
});
alertDialogBuilder.setCancelable(false);
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
private void requestBtPermissions() {
ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN}, REQUEST_CODE_BLUETOOTH_PERMISSIONS);
}
private boolean firstCheck = true;
void checkBtPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if ((ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) ||
(ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)) {
if (shouldShowRequestPermissionRationale(android.Manifest.permission.BLUETOOTH_SCAN) || shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) {
showBtPermissionsDialog();
} else {
if (firstCheck) {
requestBtPermissions();
} else {
showAppInfoDialog();
}
}
} else {
btPermissionGranted();
}
firstCheck = false;
} else {
btPermissionGranted();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,6 @@ package com.m2049r.xmrwallet;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.Editable;
import android.text.Html;
@@ -67,8 +66,20 @@ public class GenerateFragment extends Fragment {
static final String TYPE_KEY = "key";
static final String TYPE_SEED = "seed";
static final String TYPE_LEDGER = "ledger";
static final String TYPE_SIDEKICK = "sidekick";
static final String TYPE_VIEWONLY = "view";
static Wallet.Device getDeviceType(String type) {
switch (type) {
case TYPE_SIDEKICK:
return Wallet.Device.Sidekick;
case TYPE_LEDGER:
return Wallet.Device.Ledger;
default:
return Wallet.Device.Software;
}
}
private TextInputLayout etWalletName;
private PasswordEntryView etWalletPassword;
private LinearLayout llFingerprintAuth;
@@ -195,6 +206,7 @@ public class GenerateFragment extends Fragment {
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_UNSPECIFIED);
break;
case TYPE_LEDGER:
case TYPE_SIDEKICK:
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
etWalletPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
@@ -308,7 +320,7 @@ public class GenerateFragment extends Fragment {
private boolean checkName() {
String name = etWalletName.getEditText().getText().toString();
boolean ok = true;
if (name.length() == 0) {
if (name.isEmpty()) {
etWalletName.setError(getString(R.string.generate_wallet_name));
ok = false;
} else if (name.charAt(0) == '.') {
@@ -450,11 +462,12 @@ public class GenerateFragment extends Fragment {
activityCallback.onGenerate(name, crazyPass, seed, offset, height);
break;
case TYPE_LEDGER:
case TYPE_SIDEKICK:
bGenerate.setEnabled(false);
if (fingerprintAuthAllowed) {
KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password);
}
activityCallback.onGenerateLedger(name, crazyPass, height);
activityCallback.onGenerateDevice(getDeviceType(type), name, crazyPass, height);
break;
case TYPE_KEY:
case TYPE_VIEWONLY:
@@ -498,6 +511,8 @@ public class GenerateFragment extends Fragment {
return getString(R.string.generate_wallet_type_seed);
case TYPE_LEDGER:
return getString(R.string.generate_wallet_type_ledger);
case TYPE_SIDEKICK:
return getString(R.string.generate_wallet_type_sidekick);
case TYPE_VIEWONLY:
return getString(R.string.generate_wallet_type_view);
default:
@@ -515,7 +530,7 @@ public class GenerateFragment extends Fragment {
void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
void onGenerateLedger(String name, String password, long height);
void onGenerateDevice(Wallet.Device device, String name, String password, long height);
void setTitle(String title);
@@ -555,6 +570,9 @@ public class GenerateFragment extends Fragment {
case TYPE_LEDGER:
inflater.inflate(R.menu.create_wallet_ledger, menu);
break;
case TYPE_SIDEKICK:
inflater.inflate(R.menu.create_wallet_sidekick, menu);
break;
case TYPE_VIEWONLY:
inflater.inflate(R.menu.create_wallet_view, menu);
break;
@@ -581,13 +599,11 @@ public class GenerateFragment extends Fragment {
.setCancelable(false)
.setPositiveButton(getString(R.string.label_ok), null)
.setNegativeButton(getString(R.string.label_cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Helper.hideKeyboardAlways(activity);
etWalletMnemonic.getEditText().getText().clear();
dialog.cancel();
ledgerDialog = null;
}
(dialog, id) -> {
Helper.hideKeyboardAlways(activity);
etWalletMnemonic.getEditText().getText().clear();
dialog.cancel();
ledgerDialog = null;
});
ledgerDialog = alertDialogBuilder.create();

View File

@@ -40,6 +40,7 @@ import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
@@ -98,6 +99,13 @@ public class GenerateReviewFragment extends Fragment {
private String walletPath;
private String walletName;
private OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
// nothing
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -160,6 +168,9 @@ public class GenerateReviewFragment extends Fragment {
walletPath = args.getString(REQUEST_PATH);
localPassword = args.getString(REQUEST_PASSWORD);
showDetails();
if (type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT)) {
backPressedCallback.setEnabled(true);
}
return view;
}
@@ -243,7 +254,7 @@ public class GenerateReviewFragment extends Fragment {
showProgress();
if ((walletPath != null)
&& (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword())
== Wallet.Device.Device_Ledger)
== Wallet.Device.Ledger)
&& (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
dialogOpened = true;
@@ -275,10 +286,11 @@ public class GenerateReviewFragment extends Fragment {
height = wallet.getRestoreHeight();
seed = wallet.getSeed(getSeedOffset());
switch (wallet.getDeviceType()) {
case Device_Ledger:
case Ledger:
viewKey = Ledger.Key();
break;
case Device_Software:
case Software:
case Sidekick:
viewKey = wallet.getSecretViewKey();
break;
default:
@@ -420,14 +432,11 @@ public class GenerateReviewFragment extends Fragment {
pbProgress.setVisibility(View.INVISIBLE);
}
boolean backOk() {
return !type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
requireActivity().getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
}
@Override

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.fragment.app.Fragment;
import timber.log.Timber;
public class LockFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Timber.d("onCreateView");
final FrameLayout frame = new FrameLayout(requireContext());
frame.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return frame;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
@@ -45,6 +46,7 @@ import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
@@ -60,6 +62,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import lombok.Getter;
import timber.log.Timber;
public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInteractionListener,
@@ -114,7 +117,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Set<NodeInfo> getOrPopulateFavourites();
boolean hasLedger();
boolean hasDevice(Wallet.Device type);
boolean isNetworkAvailable();
void runOnNetCipher(Runnable runnable);
}
@@ -145,9 +150,15 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
activityCallback.setToolbarButton(Toolbar.BUTTON_SETTINGS);
activityCallback.showNet();
showNetwork();
//activityCallback.runOnNetCipher(this::pingSelectedNode);
}
private OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
animateFAB();
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -164,6 +175,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabSeed = view.findViewById(R.id.fabSeed);
fabImport = view.findViewById(R.id.fabImport);
fabLedger = view.findViewById(R.id.fabLedger);
fabSidekick = view.findViewById(R.id.fabSidekick);
fabNewL = view.findViewById(R.id.fabNewL);
fabViewL = view.findViewById(R.id.fabViewL);
@@ -171,6 +183,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabSeedL = view.findViewById(R.id.fabSeedL);
fabImportL = view.findViewById(R.id.fabImportL);
fabLedgerL = view.findViewById(R.id.fabLedgerL);
fabSidekickL = view.findViewById(R.id.fabSidekickL);
fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse);
fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen);
@@ -186,6 +199,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabSeed.setOnClickListener(this);
fabImport.setOnClickListener(this);
fabLedger.setOnClickListener(this);
fabSidekick.setOnClickListener(this);
fabScreen.setOnClickListener(this);
RecyclerView recyclerView = view.findViewById(R.id.list);
@@ -194,6 +208,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
recyclerView.setAdapter(adapter);
ViewGroup llNotice = view.findViewById(R.id.llNotice);
Notice.showAll(llNotice, ".*_login");
view.findViewById(R.id.llNode).setOnClickListener(v -> startNodePrefs());
@@ -287,6 +302,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
}
@Override
@@ -295,25 +311,30 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
super.onCreateOptionsMenu(menu, inflater);
}
private boolean isFabOpen = false;
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabImport, fabLedger;
@Getter
private boolean fabOpen = false;
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabImport, fabLedger, fabSidekick;
private RelativeLayout fabScreen;
private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabImportL, fabLedgerL;
private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabImportL, fabLedgerL, fabSidekickL;
private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen;
private Animation fab_pulse;
public boolean isFabOpen() {
return isFabOpen;
private void setFabOpen(boolean value) {
fabOpen = value;
onBackPressedCallback.setEnabled(value);
}
public void animateFAB() {
if (isFabOpen) { // close the fab
if (isFabOpen()) { // close the fab
fabScreen.setClickable(false);
fabScreen.startAnimation(fab_close_screen);
fab.startAnimation(rotate_backward);
if (fabLedgerL.getVisibility() == View.VISIBLE) {
fabLedgerL.startAnimation(fab_close);
fabLedger.setClickable(false);
} else if (fabSidekickL.getVisibility() == View.VISIBLE) {
fabSidekickL.startAnimation(fab_close);
fabSidekick.setClickable(false);
} else {
fabNewL.startAnimation(fab_close);
fabNew.setClickable(false);
@@ -326,22 +347,30 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabImportL.startAnimation(fab_close);
fabImport.setClickable(false);
}
isFabOpen = false;
setFabOpen(false);
} else { // open the fab
fabScreen.setClickable(true);
fabScreen.startAnimation(fab_open_screen);
fab.startAnimation(rotate_forward);
if (activityCallback.hasLedger()) {
fabLedgerL.setVisibility(View.VISIBLE);
if ((activityCallback.hasDevice(Wallet.Device.Ledger)
|| activityCallback.hasDevice(Wallet.Device.Sidekick))) {
fabNewL.setVisibility(View.GONE);
fabViewL.setVisibility(View.GONE);
fabKeyL.setVisibility(View.GONE);
fabSeedL.setVisibility(View.GONE);
fabImportL.setVisibility(View.GONE);
fabLedgerL.startAnimation(fab_open);
fabLedger.setClickable(true);
if (activityCallback.hasDevice(Wallet.Device.Ledger)) {
fabLedgerL.setVisibility(View.VISIBLE);
fabLedgerL.startAnimation(fab_open);
fabLedger.setClickable(true);
} else { // Sidekick
fabSidekickL.setVisibility(View.VISIBLE);
fabSidekickL.startAnimation(fab_open);
fabSidekick.setClickable(true);
}
} else {
fabSidekickL.setVisibility(View.GONE);
fabLedgerL.setVisibility(View.GONE);
fabNewL.setVisibility(View.VISIBLE);
fabViewL.setVisibility(View.VISIBLE);
@@ -360,7 +389,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabImportL.startAnimation(fab_open);
fabImport.setClickable(true);
}
isFabOpen = true;
setFabOpen(true);
}
}
@@ -372,7 +401,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
animateFAB();
} else if (id == R.id.fabNew) {
fabScreen.setVisibility(View.INVISIBLE);
isFabOpen = false;
setFabOpen(false);
activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
} else if (id == R.id.fabView) {
animateFAB();
@@ -390,6 +419,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Timber.d("FAB_LEDGER");
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
} else if (id == R.id.fabSidekick) {
Timber.d("FAB_SIDEKICK");
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_SIDEKICK);
} else if (id == R.id.fabScreen) {
animateFAB();
}

View File

@@ -31,6 +31,7 @@ import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
@@ -145,6 +146,13 @@ public class NodeFragment extends Fragment
}
}
private OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
Toast.makeText(requireActivity(), getString(R.string.node_refresh_wait), Toast.LENGTH_LONG).show();
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -187,6 +195,7 @@ public class NodeFragment extends Fragment
private boolean refresh(int type) {
if (asyncFindNodes != null) return false; // ignore refresh request as one is ongoing
onBackPressedCallback.setEnabled(true);
asyncFindNodes = new AsyncFindNodes();
updateRefreshElements();
asyncFindNodes.execute(type);
@@ -197,6 +206,7 @@ public class NodeFragment extends Fragment
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
}
@Override
@@ -342,6 +352,7 @@ public class NodeFragment extends Fragment
private void complete() {
asyncFindNodes = null;
onBackPressedCallback.setEnabled(false);
if (!isAdded()) return;
//if (isCancelled()) return;
tvPull.setText(getString(R.string.node_pull_hint));
@@ -575,6 +586,7 @@ public class NodeFragment extends Fragment
}
}
}
}
void restoreDefaultNodes() {

View File

@@ -1,21 +0,0 @@
/*
* 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;
public interface OnBackPressedListener {
boolean onBackPressed();
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.m2049r.xmrwallet.data.BluetoothInfo;
import com.m2049r.xmrwallet.layout.BluetoothInfoAdapter;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.util.ArrayList;
import java.util.List;
import timber.log.Timber;
public class SidekickConnectFragment extends Fragment
implements BluetoothInfoAdapter.OnInteractionListener {
private BluetoothAdapter bluetoothAdapter;
private SwipeRefreshLayout pullToRefresh;
private BluetoothInfoAdapter infoAdapter;
private Listener activityCallback;
public interface Listener {
void setToolbarButton(int type);
void setSubtitle(String title);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof Listener) {
this.activityCallback = (Listener) context;
} else {
throw new ClassCastException(context + " must implement Listener");
}
}
@Override
public void onPause() {
Timber.d("onPause()");
if (bluetoothAdapter.isDiscovering()) {
bluetoothAdapter.cancelDiscovery();
}
// the the activity we are connected? why? it can ask the bluetoothservice...
super.onPause();
}
@Override
public void onResume() {
super.onResume();
Timber.d("onResume()");
activityCallback.setSubtitle(getString(R.string.label_bluetooth));
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
final BluetoothFragment btFragment = (BluetoothFragment) getChildFragmentManager().findFragmentById(R.id.bt_fragment);
assert btFragment != null;
btFragment.start();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Timber.d("onCreateView");
View view = inflater.inflate(R.layout.fragment_sidekick_connect, container, false);
RecyclerView recyclerView = view.findViewById(R.id.list);
infoAdapter = new BluetoothInfoAdapter(this);
recyclerView.setAdapter(infoAdapter);
pullToRefresh = view.findViewById(R.id.pullToRefresh);
pullToRefresh.setOnRefreshListener(() -> {
populateList();
pullToRefresh.setRefreshing(false);
});
return view;
}
private void populateList() {
List<BluetoothInfo> items = new ArrayList<>();
for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) {
final int deviceCLass = device.getBluetoothClass().getDeviceClass();
switch (deviceCLass) {
case BluetoothClass.Device.PHONE_SMART:
//TODO verify these are correct
case BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA:
case BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA:
items.add(new BluetoothInfo(device));
}
}
infoAdapter.setItems(items);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Helper.hideKeyboard(getActivity());
// Get the local Bluetooth adapter
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
populateList();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.sidekick_connect_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onDestroy() {
super.onDestroy();
// Make sure we're not doing discovery anymore
if (bluetoothAdapter != null) {
bluetoothAdapter.cancelDiscovery();
}
}
@Override
public void onInteraction(final View view, final BluetoothInfo item) {
Timber.d("onInteraction %s", item);
bluetoothAdapter.cancelDiscovery();
final BluetoothFragment btFragment = (BluetoothFragment) getChildFragmentManager().findFragmentById(R.id.bt_fragment);
assert btFragment != null;
btFragment.connectDevice(item.getAddress());
}
public void allowClick() {
infoAdapter.allowClick(true);
}
}

View File

@@ -195,7 +195,7 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
@Override
protected void onPreExecute() {
super.onPreExecute();
if ((wallet.getDeviceType() == Wallet.Device.Device_Ledger) && (progressCallback != null)) {
if ((wallet.getDeviceType() == Wallet.Device.Ledger) && (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS);
dialogOpened = true;
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,8 +22,8 @@ import android.content.res.Configuration;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
@@ -36,7 +36,6 @@ public class XmrWalletApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
FragmentManager.enableNewStateManager(false);
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.data;
import android.bluetooth.BluetoothDevice;
import java.util.Comparator;
import lombok.Data;
import lombok.Getter;
@Data
public class BluetoothInfo {
@Getter
final private String name;
@Getter
final private String address;
@Getter
private boolean bonded;
public BluetoothInfo(BluetoothDevice device) {
name = device.getName().trim();
address = device.getAddress();
bonded = device.getBondState() == BluetoothDevice.BOND_BONDED;
}
static public Comparator<BluetoothInfo> NameComparator = (o1, o2) -> o1.name.compareTo(o2.name);
}

View File

@@ -29,10 +29,12 @@ public enum DefaultNodes {
HASHVAULT("nodes.hashvault.pro:18081"),
MONEROWORLD("node.moneroworld.com:18089"),
XMRTW("opennode.xmr-tw.org:18089"),
ds_jetzt("monero.ds-jetzt.de:18089"),
MONERUJO_ONION("monerujods7mbghwe6cobdr6ujih6c22zu5rl7zshmizz2udf7v7fsad.onion:18081/mainnet/monerujo.onion"),
Criminales78("56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089/mainnet/Criminales78.onion"),
xmrfail("mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081/mainnet/xmrfail.onion"),
boldsuck("6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081/mainnet/boldsuck.onion");
boldsuck("6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081/mainnet/boldsuck.onion"),
ds_jetzt_onion("qvlr4w7yhnjrdg3txa72jwtpnjn4ezsrivzvocbnvpfbdo342fahhoad.onion:18089/mainnet/ds-jetzt.onion");
@Getter
private final String uri;

View File

@@ -29,6 +29,7 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
@@ -37,7 +38,6 @@ import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.transition.MaterialContainerTransform;
import com.m2049r.xmrwallet.OnBackPressedListener;
import com.m2049r.xmrwallet.OnUriScannedListener;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.WalletActivity;
@@ -63,7 +63,7 @@ public class SendFragment extends Fragment
SendAmountWizardFragment.Listener,
SendConfirmWizardFragment.Listener,
SendSuccessWizardFragment.Listener,
OnBackPressedListener, OnUriScannedListener {
OnUriScannedListener {
final static public int MIXIN = 0;
@@ -248,16 +248,18 @@ public class SendFragment extends Fragment
private SpendViewPager spendViewPager;
private SpendPagerAdapter pagerAdapter;
@Override
public boolean onBackPressed() {
if (isComitted()) return true; // no going back
if (spendViewPager.getCurrentItem() == 0) {
return false;
} else {
spendViewPager.previous();
return true;
OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (isComitted()) return; // no going back
if (spendViewPager.getCurrentItem() == 0) {
setEnabled(false);
requireActivity().getOnBackPressedDispatcher().onBackPressed();
} else {
spendViewPager.previous();
}
}
}
};
@Override
public boolean onUriScanned(BarcodeData barcodeData) {
@@ -546,8 +548,9 @@ public class SendFragment extends Fragment
final MaterialContainerTransform transform = new MaterialContainerTransform();
transform.setDrawingViewId(R.id.fragment_container);
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
transform.setAllContainerColors(ThemeHelper.getThemedColor(requireContext(), android.R.attr.colorBackground));
setSharedElementEnterTransition(transform);
requireActivity().getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
}
@Override

View File

@@ -0,0 +1,147 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.layout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.BluetoothInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class BluetoothInfoAdapter extends RecyclerView.Adapter<BluetoothInfoAdapter.ViewHolder> {
public interface OnInteractionListener {
void onInteraction(View view, BluetoothInfo item);
}
private final List<BluetoothInfo> items = new ArrayList<>();
private final OnInteractionListener listener;
public BluetoothInfoAdapter(OnInteractionListener listener) {
this.listener = listener;
}
private static class BluetoothInfoDiff extends DiffCallback<BluetoothInfo> {
public BluetoothInfoDiff(List<BluetoothInfo> oldList, List<BluetoothInfo> newList) {
super(oldList, newList);
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).getAddress().equals(mNewList.get(newItemPosition).getAddress());
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
final BluetoothInfo oldItem = mOldList.get(oldItemPosition);
final BluetoothInfo newItem = mNewList.get(newItemPosition);
return oldItem.equals(newItem);
}
}
@Override
public @NonNull
ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_bluetooth, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final @NonNull ViewHolder holder, int position) {
holder.bind(position);
}
@Override
public int getItemCount() {
return items.size();
}
public void add(BluetoothInfo item) {
if (item == null) return;
List<BluetoothInfo> newItems = new ArrayList<>(items);
if (!items.contains(item))
newItems.add(item);
setItems(newItems); // in case the nodeinfo has changed
}
public void setItems(Collection<BluetoothInfo> newItemsCollection) {
List<BluetoothInfo> newItems;
if (newItemsCollection != null) {
newItems = new ArrayList<>(newItemsCollection);
Collections.sort(newItems, BluetoothInfo.NameComparator);
} else {
newItems = new ArrayList<>();
}
final BluetoothInfoAdapter.BluetoothInfoDiff diffCallback = new BluetoothInfoAdapter.BluetoothInfoDiff(items, newItems);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
items.clear();
items.addAll(newItems);
diffResult.dispatchUpdatesTo(this);
}
private boolean itemsClickable = true;
public void allowClick(boolean clickable) {
itemsClickable = clickable;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final TextView tvName;
final TextView tvAddress;
BluetoothInfo item;
ViewHolder(View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
tvAddress = itemView.findViewById(R.id.tvAddress);
itemView.setOnClickListener(this);
}
void bind(int position) {
item = items.get(position);
tvName.setText(item.getName());
tvAddress.setText(item.getAddress());
itemView.setClickable(itemsClickable);
itemView.setEnabled(itemsClickable);
}
@Override
public void onClick(View view) {
if (listener != null) {
int position = getBindingAdapterPosition(); // gets item position
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
final BluetoothInfo node = items.get(position);
allowClick(false);
listener.onInteraction(view, node);
}
}
}
}
}

View File

@@ -86,7 +86,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
}
public boolean needsTransactionUpdateOnNewBlock() {
return (infoItems.size() > 0) && !infoItems.get(0).isConfirmed();
return (!infoItems.isEmpty()) && !infoItems.get(0).isConfirmed();
}
private static class TransactionInfoDiff extends DiffCallback<TransactionInfo> {

View File

@@ -0,0 +1,4 @@
package com.m2049r.xmrwallet.ledger;
public interface Hardware {
}

View File

@@ -35,12 +35,11 @@ import java.nio.charset.StandardCharsets;
import timber.log.Timber;
public class Ledger {
public class Ledger implements Hardware {
static final public boolean ENABLED = true;
// 5:20 is same as wallet2.cpp::restore()
static public final int LOOKAHEAD_ACCOUNTS = 5;
static public final int LOOKAHEAD_SUBADDRESSES = 20;
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
private static final byte PROTOCOL_VERSION = 0x03;
public static final int SW_OK = 0x9000;

View File

@@ -46,11 +46,6 @@ public class LedgerProgressDialog extends ProgressDialog implements Ledger.Liste
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;

View File

@@ -111,7 +111,11 @@ public class Wallet {
@RequiredArgsConstructor
@Getter
public enum Device {
Device_Undefined(0, 0), Device_Software(50, 200), Device_Ledger(5, 20);
Undefined(0, 0),
Software(50, 200),
Ledger(5, 20),
Trezor(5, 20),
Sidekick(5, 20);
private final int accountLookahead;
private final int subaddressLookahead;
}

View File

@@ -18,7 +18,6 @@ package com.m2049r.xmrwallet.model;
import com.m2049r.xmrwallet.XmrWalletApplication;
import com.m2049r.xmrwallet.data.Node;
import com.m2049r.xmrwallet.ledger.Ledger;
import com.m2049r.xmrwallet.util.RestoreHeight;
import java.io.File;
@@ -161,10 +160,12 @@ public class WalletManager {
String spendKeyString);
public Wallet createWalletFromDevice(File aFile, String password, long restoreHeight,
String deviceName) {
Wallet.Device device) {
final String lookahead = device.getAccountLookahead() + ":" + device.getSubaddressLookahead();
Timber.d("Creating from %s with %s lookahead", device, lookahead);
long walletHandle = createWalletFromDeviceJ(aFile.getAbsolutePath(), password,
getNetworkType().getValue(), deviceName, restoreHeight,
Ledger.SUBADDRESS_LOOKAHEAD);
getNetworkType().getValue(), device.name(), restoreHeight,
lookahead);
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet);
return wallet;

View File

@@ -19,6 +19,8 @@ package com.m2049r.xmrwallet.onboarding;
import android.content.Context;
import android.content.SharedPreferences;
import com.m2049r.xmrwallet.util.Helper;
import java.util.Date;
import timber.log.Timber;

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More