mirror of
https://codeberg.org/r4v3r23/mysu.git
synced 2024-11-25 07:12:26 +00:00
Refactoring (WIP): Convert to Kotlin
This commit is contained in:
parent
de35fbc457
commit
c1f4b46b3b
61 changed files with 2428 additions and 3257 deletions
|
@ -1,5 +1,6 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: "androidx.navigation.safeargs"
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 34
|
||||
|
@ -96,8 +97,8 @@ android {
|
|||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_9
|
||||
targetCompatibility JavaVersion.VERSION_1_9
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
namespace 'net.mynero.wallet'
|
||||
buildFeatures {
|
||||
|
@ -112,16 +113,16 @@ static def getId(name) {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.core:core:1.10.0'
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||
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.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
|
||||
implementation 'com.google.android.material:material:1.8.0'
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
implementation 'com.ncorti:slidetoact:0.9.0'
|
||||
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||
|
@ -129,10 +130,13 @@ dependencies {
|
|||
implementation fileTree(dir: 'libs/classes', include: ['*.jar'])
|
||||
|
||||
implementation 'org.slf4j:slf4j-nop:1.7.36'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
|
||||
implementation 'androidx.navigation:navigation-fragment:2.5.3'
|
||||
implementation 'androidx.navigation:navigation-ui:2.5.3'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
|
||||
implementation 'androidx.navigation:navigation-fragment:2.7.5'
|
||||
implementation 'androidx.navigation:navigation-ui:2.7.5'
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
//noinspection GradleDependency
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
|
|
|
@ -1004,33 +1004,6 @@ Java_net_mynero_wallet_model_Wallet_getDeviceTypeJ(JNIEnv *env, jobject instance
|
|||
return static_cast<jint>(device_type);
|
||||
}
|
||||
|
||||
//void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_net_mynero_wallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jclass clazz,
|
||||
jbyteArray data, jint brokenVariant) {
|
||||
char hash[HASH_SIZE];
|
||||
jsize size = env->GetArrayLength(data);
|
||||
if ((brokenVariant > 0) && (size < 200 /*sizeof(union hash_state)*/)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jbyte *buffer = env->GetByteArrayElements(data, nullptr);
|
||||
switch (brokenVariant) {
|
||||
case 1:
|
||||
slow_hash_broken(buffer, hash, 1);
|
||||
break;
|
||||
case 2:
|
||||
slow_hash_broken(buffer, hash, 0);
|
||||
break;
|
||||
default: // not broken
|
||||
slow_hash(buffer, (size_t) size, hash);
|
||||
}
|
||||
env->ReleaseByteArrayElements(data, buffer, JNI_ABORT); // do not update java byte[]
|
||||
jbyteArray result = env->NewByteArray(HASH_SIZE);
|
||||
env->SetByteArrayRegion(result, 0, HASH_SIZE, (jbyte *) hash);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_net_mynero_wallet_model_Wallet_getDisplayAmount(JNIEnv *env, jclass clazz,
|
||||
jlong amount) {
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
package net.mynero.wallet;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.PersistableBundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import net.mynero.wallet.data.DefaultNodes;
|
||||
import net.mynero.wallet.data.Node;
|
||||
import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog;
|
||||
import net.mynero.wallet.fragment.dialog.SendBottomSheetDialog;
|
||||
import net.mynero.wallet.livedata.SingleLiveEvent;
|
||||
import net.mynero.wallet.model.Wallet;
|
||||
import net.mynero.wallet.model.WalletManager;
|
||||
import net.mynero.wallet.service.AddressService;
|
||||
import net.mynero.wallet.service.BalanceService;
|
||||
import net.mynero.wallet.service.BlockchainService;
|
||||
import net.mynero.wallet.service.HistoryService;
|
||||
import net.mynero.wallet.service.MoneroHandlerThread;
|
||||
import net.mynero.wallet.service.PrefService;
|
||||
import net.mynero.wallet.service.TxService;
|
||||
import net.mynero.wallet.service.UTXOService;
|
||||
import net.mynero.wallet.util.Constants;
|
||||
import net.mynero.wallet.util.UriData;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements MoneroHandlerThread.Listener, PasswordBottomSheetDialog.PasswordListener {
|
||||
public final SingleLiveEvent restartEvents = new SingleLiveEvent();
|
||||
private MoneroHandlerThread thread = null;
|
||||
private BalanceService balanceService = null;
|
||||
private AddressService addressService = null;
|
||||
private HistoryService historyService = null;
|
||||
private BlockchainService blockchainService = null;
|
||||
private UTXOService utxoService = null;
|
||||
|
||||
private boolean proceedToSend = false;
|
||||
private UriData uriData = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
File walletFile = new File(getApplicationInfo().dataDir, Constants.WALLET_NAME);
|
||||
File walletKeysFile = new File(getApplicationInfo().dataDir, Constants.WALLET_NAME + ".keys");
|
||||
if (walletKeysFile.exists()) {
|
||||
boolean promptPassword = PrefService.getInstance().getBoolean(Constants.PREF_USES_PASSWORD, false);
|
||||
if (!promptPassword) {
|
||||
init(walletFile, "");
|
||||
} else {
|
||||
PasswordBottomSheetDialog passwordDialog = new PasswordBottomSheetDialog();
|
||||
passwordDialog.listener = this;
|
||||
passwordDialog.show(getSupportFragmentManager(), "password_dialog");
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
Uri uri = intent.getData();
|
||||
if (uri != null) {
|
||||
uriData = UriData.parse(uri.toString());
|
||||
if (uriData != null) {
|
||||
proceedToSend = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
navigate(R.id.onboarding_fragment);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
|
||||
super.onPostCreate(savedInstanceState, persistentState);
|
||||
}
|
||||
|
||||
private void navigate(int destination) {
|
||||
FragmentActivity activity = this;
|
||||
FragmentManager fm = activity.getSupportFragmentManager();
|
||||
NavHostFragment navHostFragment =
|
||||
(NavHostFragment) fm.findFragmentById(R.id.nav_host_fragment);
|
||||
if (navHostFragment != null) {
|
||||
navHostFragment.getNavController().navigate(destination);
|
||||
}
|
||||
}
|
||||
|
||||
public MoneroHandlerThread getThread() {
|
||||
return thread;
|
||||
}
|
||||
|
||||
public void init(File walletFile, String password) {
|
||||
Wallet wallet = WalletManager.getInstance().openWallet(walletFile.getAbsolutePath(), password);
|
||||
thread = new MoneroHandlerThread("WalletService", this, wallet);
|
||||
new TxService(thread);
|
||||
this.balanceService = new BalanceService(thread);
|
||||
this.addressService = new AddressService(thread);
|
||||
this.historyService = new HistoryService(thread);
|
||||
this.blockchainService = new BlockchainService(thread);
|
||||
this.utxoService = new UTXOService(thread);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRefresh(boolean walletSynced) {
|
||||
if(walletSynced)
|
||||
this.utxoService.refreshUtxos();
|
||||
|
||||
this.historyService.refreshHistory();
|
||||
this.balanceService.refreshBalance();
|
||||
this.blockchainService.refreshBlockchain();
|
||||
this.addressService.refreshAddresses();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFail() {
|
||||
runOnUiThread(() -> Toast.makeText(getApplication(), R.string.connection_failed, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPasswordSuccess(String password) {
|
||||
File walletFile = new File(getApplicationInfo().dataDir, Constants.WALLET_NAME);
|
||||
init(walletFile, password);
|
||||
restartEvents.call();
|
||||
|
||||
if (proceedToSend) {
|
||||
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
|
||||
sendDialog.uriData = uriData;
|
||||
sendDialog.show(getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPasswordFail() {
|
||||
runOnUiThread(() -> Toast.makeText(getApplication(), R.string.bad_password, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
}
|
115
app/src/main/java/net/mynero/wallet/MainActivity.kt
Normal file
115
app/src/main/java/net/mynero/wallet/MainActivity.kt
Normal file
|
@ -0,0 +1,115 @@
|
|||
package net.mynero.wallet
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.PersistableBundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog
|
||||
import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog.PasswordListener
|
||||
import net.mynero.wallet.fragment.dialog.SendBottomSheetDialog
|
||||
import net.mynero.wallet.livedata.SingleLiveEvent
|
||||
import net.mynero.wallet.model.WalletManager.Companion.instance
|
||||
import net.mynero.wallet.service.AddressService
|
||||
import net.mynero.wallet.service.BalanceService
|
||||
import net.mynero.wallet.service.BlockchainService
|
||||
import net.mynero.wallet.service.HistoryService
|
||||
import net.mynero.wallet.service.MoneroHandlerThread
|
||||
import net.mynero.wallet.service.PrefService
|
||||
import net.mynero.wallet.service.TxService
|
||||
import net.mynero.wallet.service.UTXOService
|
||||
import net.mynero.wallet.util.Constants
|
||||
import net.mynero.wallet.util.UriData
|
||||
import java.io.File
|
||||
|
||||
class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, PasswordListener {
|
||||
@JvmField
|
||||
val restartEvents: SingleLiveEvent<*> = SingleLiveEvent<Any?>()
|
||||
var thread: MoneroHandlerThread? = null
|
||||
private set
|
||||
private var balanceService: BalanceService? = null
|
||||
private var addressService: AddressService? = null
|
||||
private var historyService: HistoryService? = null
|
||||
private var blockchainService: BlockchainService? = null
|
||||
private var utxoService: UTXOService? = null
|
||||
private var proceedToSend = false
|
||||
private var uriData: UriData? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
|
||||
val walletKeysFile = File(applicationInfo.dataDir, Constants.WALLET_NAME + ".keys")
|
||||
if (walletKeysFile.exists()) {
|
||||
val promptPassword =
|
||||
PrefService.getInstance().getBoolean(Constants.PREF_USES_PASSWORD, false)
|
||||
if (!promptPassword) {
|
||||
init(walletFile, "")
|
||||
} else {
|
||||
val passwordDialog = PasswordBottomSheetDialog()
|
||||
passwordDialog.listener = this
|
||||
passwordDialog.show(supportFragmentManager, "password_dialog")
|
||||
}
|
||||
val intent = intent
|
||||
val uri = intent.data
|
||||
if (uri != null) {
|
||||
uriData = UriData.parse(uri.toString())
|
||||
if (uriData != null) {
|
||||
proceedToSend = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
navigate(R.id.onboarding_fragment)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigate(destination: Int) {
|
||||
val activity: FragmentActivity = this
|
||||
val fm = activity.supportFragmentManager
|
||||
val navHostFragment = fm.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
|
||||
navHostFragment?.navController?.navigate(destination)
|
||||
}
|
||||
|
||||
fun init(walletFile: File, password: String?) {
|
||||
val wallet = password?.let { instance?.openWallet(walletFile.absolutePath, it) }
|
||||
thread = MoneroHandlerThread("WalletService", this, wallet)
|
||||
TxService(thread)
|
||||
balanceService = BalanceService(thread)
|
||||
addressService = AddressService(thread)
|
||||
historyService = HistoryService(thread)
|
||||
blockchainService = BlockchainService(thread)
|
||||
utxoService = UTXOService(thread)
|
||||
thread?.start()
|
||||
}
|
||||
|
||||
override fun onRefresh(walletSynced: Boolean) {
|
||||
if (walletSynced) utxoService?.refreshUtxos()
|
||||
historyService?.refreshHistory()
|
||||
balanceService?.refreshBalance()
|
||||
blockchainService?.refreshBlockchain()
|
||||
addressService?.refreshAddresses()
|
||||
}
|
||||
|
||||
override fun onConnectionFail() {
|
||||
runOnUiThread {
|
||||
Toast.makeText(application, R.string.connection_failed, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPasswordSuccess(password: String) {
|
||||
val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
|
||||
init(walletFile, password)
|
||||
restartEvents.call()
|
||||
if (proceedToSend) {
|
||||
val sendDialog = SendBottomSheetDialog()
|
||||
sendDialog.uriData = uriData
|
||||
sendDialog.show(supportFragmentManager, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPasswordFail() {
|
||||
runOnUiThread {
|
||||
Toast.makeText(application, R.string.bad_password, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package net.mynero.wallet;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import net.mynero.wallet.service.PrefService;
|
||||
import net.mynero.wallet.util.NightmodeHelper;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class MoneroApplication extends Application {
|
||||
private ExecutorService executor = null;
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
new PrefService(this);
|
||||
NightmodeHelper.getAndSetPreferredNightmode();
|
||||
executor = Executors.newFixedThreadPool(16);
|
||||
}
|
||||
|
||||
public ExecutorService getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
}
|
19
app/src/main/java/net/mynero/wallet/MoneroApplication.kt
Normal file
19
app/src/main/java/net/mynero/wallet/MoneroApplication.kt
Normal file
|
@ -0,0 +1,19 @@
|
|||
package net.mynero.wallet
|
||||
|
||||
import android.app.Application
|
||||
import net.mynero.wallet.service.PrefService
|
||||
import net.mynero.wallet.util.NightmodeHelper
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class MoneroApplication : Application() {
|
||||
var executor: ExecutorService? = null
|
||||
private set
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
PrefService(this)
|
||||
NightmodeHelper.preferredNightmode
|
||||
executor = Executors.newFixedThreadPool(16)
|
||||
}
|
||||
}
|
|
@ -60,7 +60,7 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter<CoinsInfoAdapter.View
|
|||
}
|
||||
|
||||
public void deselectUtxo(CoinsInfo coinsInfo) {
|
||||
this.selectedUtxos.remove(coinsInfo.getPubKey());
|
||||
this.selectedUtxos.remove(coinsInfo.pubKey);
|
||||
|
||||
if(this.selectedUtxos.size() == 0) {
|
||||
editing = false;
|
||||
|
@ -71,12 +71,12 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter<CoinsInfoAdapter.View
|
|||
|
||||
public void selectUtxo(CoinsInfo coinsInfo) {
|
||||
editing = true;
|
||||
this.selectedUtxos.put(coinsInfo.getPubKey(), coinsInfo);
|
||||
this.selectedUtxos.put(coinsInfo.pubKey, coinsInfo);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public boolean contains(CoinsInfo coinsInfo) {
|
||||
return selectedUtxos.containsKey(coinsInfo.getPubKey());
|
||||
return selectedUtxos.containsKey(coinsInfo.pubKey);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
@ -135,7 +135,7 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter<CoinsInfoAdapter.View
|
|||
public void bind(boolean editing, CoinsInfo coinsInfo, HashMap<String, CoinsInfo> selectedUtxos) {
|
||||
this.editing = editing;
|
||||
this.coinsInfo = coinsInfo;
|
||||
boolean selected = selectedUtxos.containsKey(coinsInfo.getPubKey());
|
||||
boolean selected = selectedUtxos.containsKey(coinsInfo.pubKey);
|
||||
|
||||
TextView pubKeyTextView = itemView.findViewById(R.id.utxo_pub_key_textview);
|
||||
TextView amountTextView = itemView.findViewById(R.id.utxo_amount_textview);
|
||||
|
@ -143,12 +143,12 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter<CoinsInfoAdapter.View
|
|||
TextView globalIdxTextView = itemView.findViewById(R.id.utxo_global_index_textview);
|
||||
TextView outpointTextView = itemView.findViewById(R.id.utxo_outpoint_textview);
|
||||
boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
|
||||
String balanceString = streetModeEnabled ? Constants.STREET_MODE_BALANCE : Wallet.getDisplayAmount(coinsInfo.getAmount());
|
||||
String balanceString = streetModeEnabled ? Constants.STREET_MODE_BALANCE : Wallet.getDisplayAmount(coinsInfo.amount);
|
||||
amountTextView.setText(itemView.getResources().getString(R.string.tx_amount_no_prefix, balanceString));
|
||||
pubKeyTextView.setText(coinsInfo.getPubKey());
|
||||
addressTextView.setText(coinsInfo.getAddress());
|
||||
globalIdxTextView.setText(itemView.getResources().getString(R.string.global_index_text, coinsInfo.getGlobalOutputIndex()));
|
||||
outpointTextView.setText(itemView.getResources().getString(R.string.outpoint_text, coinsInfo.getHash() + ":" + coinsInfo.getLocalOutputIndex()));
|
||||
pubKeyTextView.setText(coinsInfo.pubKey);
|
||||
addressTextView.setText(coinsInfo.address);
|
||||
globalIdxTextView.setText(itemView.getResources().getString(R.string.global_index_text, coinsInfo.globalOutputIndex));
|
||||
outpointTextView.setText(itemView.getResources().getString(R.string.outpoint_text, coinsInfo.hash + ":" + coinsInfo.localOutputIndex));
|
||||
if (selected) {
|
||||
itemView.setBackgroundTintList(ContextCompat.getColorStateList(itemView.getContext(), R.color.oled_colorSecondary));
|
||||
} else if(coinsInfo.isFrozen() || UTXOService.instance.isCoinFrozen(coinsInfo)) {
|
||||
|
|
|
@ -99,14 +99,14 @@ public class SubaddressAdapter extends RecyclerView.Adapter<SubaddressAdapter.Vi
|
|||
TextView addressLabelTextView = itemView.findViewById(R.id.address_label_textview);
|
||||
TextView addressAmountTextView = itemView.findViewById(R.id.address_amount_textview);
|
||||
|
||||
addressTextView.setText(subaddress.getAddress());
|
||||
addressTextView.setText(subaddress.address);
|
||||
|
||||
final String label = subaddress.getDisplayLabel();
|
||||
final String address = itemView.getContext().getString(R.string.subbaddress_info_subtitle,
|
||||
subaddress.getAddressIndex(), subaddress.getSquashedAddress());
|
||||
subaddress.addressIndex, subaddress.getSquashedAddress());
|
||||
addressLabelTextView.setText(label.isEmpty() ? address : label);
|
||||
|
||||
final long amount = subaddress.getAmount();
|
||||
final long amount = subaddress.amount;
|
||||
if (amount > 0) {
|
||||
boolean streetMode = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
|
||||
if(streetMode) {
|
||||
|
|
|
@ -30,9 +30,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||
import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||
|
||||
import net.mynero.wallet.R;
|
||||
import net.mynero.wallet.data.UserNotes;
|
||||
import net.mynero.wallet.model.TransactionInfo;
|
||||
import net.mynero.wallet.model.Wallet;
|
||||
import net.mynero.wallet.service.PrefService;
|
||||
import net.mynero.wallet.util.Constants;
|
||||
import net.mynero.wallet.util.Helper;
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 net.mynero.wallet.data;
|
||||
|
||||
// Nodes stolen from https://moneroworld.com/#nodes
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public enum DefaultNodes {
|
||||
SAMOURAI("163.172.56.213", 18089, "mainnet", "SamouraiWallet"),
|
||||
MONERUJO("nodex.monerujo.io", 18081, "mainnet", "monerujo"),
|
||||
SUPPORTXMR("node.supportxmr.com", 18081, "mainnet", "SupportXMR"),
|
||||
HASHVAULT("nodes.hashvault.pro", 18081, "mainnet", "Hashvault"),
|
||||
MONEROWORLD("node.moneroworld.com", 18089, "mainnet", "MoneroWorld"),
|
||||
XMRTW("opennode.xmr-tw.org", 18089, "mainnet", "XMRTW"),
|
||||
MYNERO_I2P("ynk3hrwte23asonojqeskoulek2g2cd6tqg4neghnenfyljrvhga.b32.i2p", 0, "mainnet", "node.mysu.i2p"),
|
||||
MYNERO_ONION("tiopyrxseconw73thwlv2pf5hebfcqxj5zdolym7z6pbq6gl4z7xz4ad.onion", 18081, "mainnet", "node.mysu.onion"),
|
||||
SAMOURAI_ONION("446unwib5vc7pfbzflosy6m6vtyuhddnalr3hutyavwe4esfuu5g6ryd.onion", 18089, "mainnet", "SamouraiWallet.onion"),
|
||||
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");
|
||||
|
||||
private final String address;
|
||||
private final int port;
|
||||
private final String network;
|
||||
private final String name;
|
||||
|
||||
DefaultNodes(String address, int port, String network, String name) {
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.network = network;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getNodeString() {
|
||||
return address + ":" + port + "/" + network + "/" + name;
|
||||
}
|
||||
|
||||
public JSONObject getJson() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
try {
|
||||
jsonObject.put("host", address);
|
||||
jsonObject.put("rpcPort", port);
|
||||
jsonObject.put("network", network);
|
||||
if (!name.isEmpty())
|
||||
jsonObject.put("name", name);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
40
app/src/main/java/net/mynero/wallet/data/DefaultNodes.kt
Normal file
40
app/src/main/java/net/mynero/wallet/data/DefaultNodes.kt
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2020 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 net.mynero.wallet.data
|
||||
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
// Nodes stolen from https://moneroworld.com/#nodes
|
||||
enum class DefaultNodes(val address: String, private val port: Int, private val network: String, private val nodeName: String) {
|
||||
SAMOURAI("163.172.56.213", 18089, "mainnet", "SamouraiWallet"), MONERUJO("nodex.monerujo.io", 18081, "mainnet", "monerujo"), SUPPORTXMR("node.supportxmr.com", 18081, "mainnet", "SupportXMR"), HASHVAULT("nodes.hashvault.pro", 18081, "mainnet", "Hashvault"), MONEROWORLD("node.moneroworld.com", 18089, "mainnet", "MoneroWorld"), XMRTW("opennode.xmr-tw.org", 18089, "mainnet", "XMRTW"), MYNERO_I2P("ynk3hrwte23asonojqeskoulek2g2cd6tqg4neghnenfyljrvhga.b32.i2p", 0, "mainnet", "node.mysu.i2p"), MYNERO_ONION("tiopyrxseconw73thwlv2pf5hebfcqxj5zdolym7z6pbq6gl4z7xz4ad.onion", 18081, "mainnet", "node.mysu.onion"), SAMOURAI_ONION("446unwib5vc7pfbzflosy6m6vtyuhddnalr3hutyavwe4esfuu5g6ryd.onion", 18089, "mainnet", "SamouraiWallet.onion"), 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");
|
||||
|
||||
val nodeString: String
|
||||
get() = "$address:$port/$network/$nodeName"
|
||||
val json: JSONObject
|
||||
get() {
|
||||
val jsonObject = JSONObject()
|
||||
try {
|
||||
jsonObject.put("host", address)
|
||||
jsonObject.put("rpcPort", port)
|
||||
jsonObject.put("network", network)
|
||||
if (nodeName.isNotEmpty()) jsonObject.put("name", nodeName)
|
||||
} catch (e: JSONException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
return jsonObject
|
||||
}
|
||||
}
|
|
@ -1,382 +0,0 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.data;
|
||||
|
||||
import net.mynero.wallet.model.NetworkType;
|
||||
import net.mynero.wallet.model.WalletManager;
|
||||
import net.mynero.wallet.util.OnionHelper;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Node {
|
||||
static public final String MAINNET = "mainnet";
|
||||
static public final String STAGENET = "stagenet";
|
||||
static public final String TESTNET = "testnet";
|
||||
static private int DEFAULT_LEVIN_PORT = 0;
|
||||
static private int DEFAULT_RPC_PORT = 0;
|
||||
private NetworkType networkType;
|
||||
private final boolean selected = false;
|
||||
int rpcPort = 0;
|
||||
private String name = null;
|
||||
private String host;
|
||||
private int levinPort = 0;
|
||||
private String username = "";
|
||||
private String password = "";
|
||||
private boolean favourite = false;
|
||||
|
||||
|
||||
public NetworkType getNetworkType() {
|
||||
return networkType;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
Node(String nodeString) {
|
||||
if ((nodeString == null) || nodeString.isEmpty())
|
||||
throw new IllegalArgumentException("daemon is empty");
|
||||
String daemonAddress;
|
||||
String[] a = nodeString.split("@");
|
||||
if (a.length == 1) { // no credentials
|
||||
daemonAddress = a[0];
|
||||
username = "";
|
||||
password = "";
|
||||
} else if (a.length == 2) { // credentials
|
||||
String[] userPassword = a[0].split(":");
|
||||
if (userPassword.length != 2)
|
||||
throw new IllegalArgumentException("User:Password invalid");
|
||||
username = userPassword[0];
|
||||
if (!username.isEmpty()) {
|
||||
password = userPassword[1];
|
||||
} else {
|
||||
password = "";
|
||||
}
|
||||
daemonAddress = a[1];
|
||||
} else {
|
||||
throw new IllegalArgumentException("Too many @");
|
||||
}
|
||||
|
||||
String[] daParts = daemonAddress.split("/");
|
||||
if ((daParts.length > 3) || (daParts.length < 1))
|
||||
throw new IllegalArgumentException("Too many '/' or too few");
|
||||
|
||||
daemonAddress = daParts[0];
|
||||
String[] da = daemonAddress.split(":");
|
||||
if ((da.length > 2) || (da.length < 1))
|
||||
throw new IllegalArgumentException("Too many ':' or too few");
|
||||
String host = da[0];
|
||||
|
||||
if (daParts.length == 1) {
|
||||
networkType = NetworkType.NetworkType_Mainnet;
|
||||
} else {
|
||||
switch (daParts[1]) {
|
||||
case MAINNET:
|
||||
networkType = NetworkType.NetworkType_Mainnet;
|
||||
break;
|
||||
case STAGENET:
|
||||
networkType = NetworkType.NetworkType_Stagenet;
|
||||
break;
|
||||
case TESTNET:
|
||||
networkType = NetworkType.NetworkType_Testnet;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid net: " + daParts[1]);
|
||||
}
|
||||
}
|
||||
if (networkType != WalletManager.getInstance().getNetworkType())
|
||||
throw new IllegalArgumentException("wrong net: " + networkType);
|
||||
|
||||
String name = host;
|
||||
if (daParts.length == 3) {
|
||||
try {
|
||||
name = URLDecoder.decode(daParts[2], "UTF-8");
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
Timber.w(ex); // if we can't encode it, we don't use it
|
||||
}
|
||||
}
|
||||
this.name = name;
|
||||
|
||||
int port;
|
||||
if (da.length == 2) {
|
||||
try {
|
||||
port = Integer.parseInt(da[1]);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("Port not numeric");
|
||||
}
|
||||
} else {
|
||||
port = getDefaultRpcPort();
|
||||
}
|
||||
try {
|
||||
setHost(host);
|
||||
} catch (UnknownHostException ex) {
|
||||
throw new IllegalArgumentException("cannot resolve host " + host);
|
||||
}
|
||||
this.rpcPort = port;
|
||||
this.levinPort = getDefaultLevinPort();
|
||||
}
|
||||
|
||||
Node(JSONObject jsonObject) throws JSONException, UnknownHostException {
|
||||
if (jsonObject == null)
|
||||
throw new IllegalArgumentException("daemon is empty");
|
||||
if(jsonObject.has("username")) {
|
||||
username = jsonObject.getString("username");
|
||||
}
|
||||
if(jsonObject.has("password")) {
|
||||
password = jsonObject.getString("password");
|
||||
}
|
||||
if(jsonObject.has("host")) {
|
||||
setHost(jsonObject.getString("host"));
|
||||
}
|
||||
if(jsonObject.has("rpcPort")) {
|
||||
this.rpcPort = jsonObject.getInt("rpcPort");
|
||||
} else {
|
||||
this.rpcPort = getDefaultRpcPort();
|
||||
}
|
||||
if(jsonObject.has("name")) {
|
||||
this.name = jsonObject.getString("name");
|
||||
}
|
||||
|
||||
if(jsonObject.has("network")) {
|
||||
switch (jsonObject.getString("network")) {
|
||||
case MAINNET:
|
||||
networkType = NetworkType.NetworkType_Mainnet;
|
||||
break;
|
||||
case STAGENET:
|
||||
networkType = NetworkType.NetworkType_Stagenet;
|
||||
break;
|
||||
case TESTNET:
|
||||
networkType = NetworkType.NetworkType_Testnet;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid net: " + jsonObject.getString("network"));
|
||||
}
|
||||
|
||||
if (networkType != WalletManager.getInstance().getNetworkType())
|
||||
throw new IllegalArgumentException("wrong net: " + networkType);
|
||||
}
|
||||
}
|
||||
|
||||
public Node() {
|
||||
this.networkType = WalletManager.getInstance().getNetworkType();
|
||||
}
|
||||
|
||||
// constructor used for created nodes from retrieved peer lists
|
||||
public Node(InetSocketAddress socketAddress) {
|
||||
this();
|
||||
this.host = socketAddress.getHostString();
|
||||
this.rpcPort = 0; // unknown
|
||||
this.levinPort = socketAddress.getPort();
|
||||
this.username = "";
|
||||
this.password = "";
|
||||
}
|
||||
|
||||
public Node(Node anotherNode) {
|
||||
networkType = anotherNode.networkType;
|
||||
overwriteWith(anotherNode);
|
||||
}
|
||||
|
||||
static public Node fromString(String nodeString) {
|
||||
try {
|
||||
return new Node(nodeString);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Timber.w(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static public Node fromJson(JSONObject jsonObject) {
|
||||
try {
|
||||
return new Node(jsonObject);
|
||||
} catch (IllegalArgumentException | UnknownHostException | JSONException ex) {
|
||||
Timber.w(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// every node knows its network, but they are all the same
|
||||
static public int getDefaultLevinPort() {
|
||||
if (DEFAULT_LEVIN_PORT > 0) return DEFAULT_LEVIN_PORT;
|
||||
switch (WalletManager.getInstance().getNetworkType()) {
|
||||
case NetworkType_Mainnet:
|
||||
DEFAULT_LEVIN_PORT = 18080;
|
||||
break;
|
||||
case NetworkType_Testnet:
|
||||
DEFAULT_LEVIN_PORT = 28080;
|
||||
break;
|
||||
case NetworkType_Stagenet:
|
||||
DEFAULT_LEVIN_PORT = 38080;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
|
||||
}
|
||||
return DEFAULT_LEVIN_PORT;
|
||||
}
|
||||
|
||||
// every node knows its network, but they are all the same
|
||||
static public int getDefaultRpcPort() {
|
||||
if (DEFAULT_RPC_PORT > 0) return DEFAULT_RPC_PORT;
|
||||
switch (WalletManager.getInstance().getNetworkType()) {
|
||||
case NetworkType_Mainnet:
|
||||
DEFAULT_RPC_PORT = 18081;
|
||||
break;
|
||||
case NetworkType_Testnet:
|
||||
DEFAULT_RPC_PORT = 28081;
|
||||
break;
|
||||
case NetworkType_Stagenet:
|
||||
DEFAULT_RPC_PORT = 38081;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
|
||||
}
|
||||
return DEFAULT_RPC_PORT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return host.hashCode();
|
||||
}
|
||||
|
||||
// Nodes are equal if they are the same host address:port & are on the same network
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof Node)) return false;
|
||||
final Node anotherNode = (Node) other;
|
||||
return (host.equals(anotherNode.host)
|
||||
&& (getAddress().equals(anotherNode.getAddress()))
|
||||
&& (rpcPort == anotherNode.rpcPort)
|
||||
&& (networkType == anotherNode.networkType))
|
||||
&& (username.equals(anotherNode.getUsername()))
|
||||
&& (password.equals(anotherNode.getPassword()));
|
||||
}
|
||||
|
||||
public boolean isOnion() {
|
||||
return OnionHelper.isOnionHost(host);
|
||||
}
|
||||
|
||||
public boolean isI2P() {
|
||||
return OnionHelper.isI2PHost(host);
|
||||
}
|
||||
|
||||
public String toNodeString() {
|
||||
return toString();
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
try {
|
||||
if (!username.isEmpty() && !password.isEmpty()) {
|
||||
jsonObject.put("username", username);
|
||||
jsonObject.put("password", password);
|
||||
}
|
||||
jsonObject.put("host", host);
|
||||
jsonObject.put("rpcPort", rpcPort);
|
||||
switch (networkType) {
|
||||
case NetworkType_Mainnet:
|
||||
jsonObject.put("network", MAINNET);
|
||||
break;
|
||||
case NetworkType_Stagenet:
|
||||
jsonObject.put("network", STAGENET);
|
||||
break;
|
||||
case NetworkType_Testnet:
|
||||
jsonObject.put("network", TESTNET);
|
||||
break;
|
||||
}
|
||||
if (!name.isEmpty())
|
||||
jsonObject.put("name", name);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (!username.isEmpty() && !password.isEmpty()) {
|
||||
sb.append(username).append(":").append(password).append("@");
|
||||
}
|
||||
sb.append(host).append(":").append(rpcPort);
|
||||
sb.append("/");
|
||||
switch (networkType) {
|
||||
case NetworkType_Mainnet:
|
||||
sb.append(MAINNET);
|
||||
break;
|
||||
case NetworkType_Stagenet:
|
||||
sb.append(STAGENET);
|
||||
break;
|
||||
case NetworkType_Testnet:
|
||||
sb.append(TESTNET);
|
||||
break;
|
||||
}
|
||||
if (name != null)
|
||||
try {
|
||||
sb.append("/").append(URLEncoder.encode(name, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException ex) {
|
||||
Timber.w(ex); // if we can't encode it, we don't store it
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return getHost() + ":" + getRpcPort();
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getRpcPort() {
|
||||
return rpcPort;
|
||||
}
|
||||
|
||||
public void setHost(String host) throws UnknownHostException {
|
||||
if ((host == null) || (host.isEmpty()))
|
||||
throw new UnknownHostException("loopback not supported (yet?)");
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public void overwriteWith(Node anotherNode) {
|
||||
if (networkType != anotherNode.networkType)
|
||||
throw new IllegalStateException("network types do not match");
|
||||
name = anotherNode.name;
|
||||
host = anotherNode.host;
|
||||
rpcPort = anotherNode.rpcPort;
|
||||
levinPort = anotherNode.levinPort;
|
||||
username = anotherNode.username;
|
||||
password = anotherNode.password;
|
||||
favourite = anotherNode.favourite;
|
||||
}
|
||||
}
|
295
app/src/main/java/net/mynero/wallet/data/Node.kt
Normal file
295
app/src/main/java/net/mynero/wallet/data/Node.kt
Normal file
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.data
|
||||
|
||||
import net.mynero.wallet.model.NetworkType
|
||||
import net.mynero.wallet.model.WalletManager
|
||||
import net.mynero.wallet.util.OnionHelper
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import timber.log.Timber
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.URLDecoder
|
||||
import java.net.URLEncoder
|
||||
import java.net.UnknownHostException
|
||||
|
||||
class Node {
|
||||
var networkType: NetworkType? = null
|
||||
private set
|
||||
var rpcPort = 0
|
||||
var name: String? = null
|
||||
private set
|
||||
var host: String? = null
|
||||
private var levinPort = 0
|
||||
var username = ""
|
||||
private set
|
||||
var password = ""
|
||||
private set
|
||||
private var favourite = false
|
||||
|
||||
internal constructor(nodeString: String?) {
|
||||
require(!nodeString.isNullOrEmpty()) { "daemon is empty" }
|
||||
var daemonAddress: String
|
||||
val a = nodeString.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
when (a.size) {
|
||||
1 -> { // no credentials
|
||||
daemonAddress = a[0]
|
||||
this.username = ""
|
||||
this.password = ""
|
||||
}
|
||||
2 -> { // credentials
|
||||
val userPassword = a[0].split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
require(userPassword.size == 2) { "User:Password invalid" }
|
||||
this.username = userPassword[0]
|
||||
this.password = if (this.username.isNotEmpty()) {
|
||||
userPassword[1]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
daemonAddress = a[1]
|
||||
}
|
||||
else -> {
|
||||
throw IllegalArgumentException("Too many @")
|
||||
}
|
||||
}
|
||||
val daParts = daemonAddress.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
require(!(daParts.size > 3 || daParts.isEmpty())) { "Too many '/' or too few" }
|
||||
daemonAddress = daParts[0]
|
||||
val da = daemonAddress.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
require(!(da.size > 2 || da.isEmpty())) { "Too many ':' or too few" }
|
||||
val host = da[0]
|
||||
this.networkType = if (daParts.size == 1) {
|
||||
NetworkType.NetworkType_Mainnet
|
||||
} else {
|
||||
when (daParts[1]) {
|
||||
MAINNET -> NetworkType.NetworkType_Mainnet
|
||||
STAGENET -> NetworkType.NetworkType_Stagenet
|
||||
TESTNET -> NetworkType.NetworkType_Testnet
|
||||
else -> throw IllegalArgumentException("invalid net: " + daParts[1])
|
||||
}
|
||||
}
|
||||
require(networkType == WalletManager.instance?.networkType) { "wrong net: $networkType" }
|
||||
var name: String? = host
|
||||
if (daParts.size == 3) {
|
||||
try {
|
||||
name = URLDecoder.decode(daParts[2], "UTF-8")
|
||||
} catch (ex: UnsupportedEncodingException) {
|
||||
Timber.w(ex) // if we can't encode it, we don't use it
|
||||
}
|
||||
}
|
||||
this.name = name
|
||||
val port: Int = if (da.size == 2) {
|
||||
try {
|
||||
da[1].toInt()
|
||||
} catch (ex: NumberFormatException) {
|
||||
throw IllegalArgumentException("Port not numeric")
|
||||
}
|
||||
} else {
|
||||
this.defaultRpcPort
|
||||
}
|
||||
try {
|
||||
this.host = host
|
||||
} catch (ex: UnknownHostException) {
|
||||
throw IllegalArgumentException("cannot resolve host $host")
|
||||
}
|
||||
this.rpcPort = port
|
||||
this.levinPort = this.defaultLevinPort
|
||||
}
|
||||
|
||||
internal constructor(jsonObject: JSONObject?) {
|
||||
requireNotNull(jsonObject) { "daemon is empty" }
|
||||
if (jsonObject.has("username")) {
|
||||
username = jsonObject.getString("username")
|
||||
}
|
||||
if (jsonObject.has("password")) {
|
||||
password = jsonObject.getString("password")
|
||||
}
|
||||
if (jsonObject.has("host")) {
|
||||
this.host = jsonObject.getString("host")
|
||||
}
|
||||
this.rpcPort = if (jsonObject.has("rpcPort")) {
|
||||
jsonObject.getInt("rpcPort")
|
||||
} else {
|
||||
defaultRpcPort
|
||||
}
|
||||
if (jsonObject.has("name")) {
|
||||
this.name = jsonObject.getString("name")
|
||||
}
|
||||
if (jsonObject.has("network")) {
|
||||
networkType = when (jsonObject.getString("network")) {
|
||||
MAINNET -> NetworkType.NetworkType_Mainnet
|
||||
STAGENET -> NetworkType.NetworkType_Stagenet
|
||||
TESTNET -> NetworkType.NetworkType_Testnet
|
||||
else -> throw IllegalArgumentException("invalid net: " + jsonObject.getString("network"))
|
||||
}
|
||||
require(networkType == WalletManager.instance?.networkType) { "wrong net: $networkType" }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
networkType = WalletManager.instance?.networkType
|
||||
}
|
||||
|
||||
// constructor used for created nodes from retrieved peer lists
|
||||
constructor(socketAddress: InetSocketAddress) : this() {
|
||||
host = socketAddress.hostString
|
||||
rpcPort = 0 // unknown
|
||||
levinPort = socketAddress.port
|
||||
username = ""
|
||||
password = ""
|
||||
}
|
||||
|
||||
constructor(anotherNode: Node) {
|
||||
networkType = anotherNode.networkType
|
||||
overwriteWith(anotherNode)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return host.hashCode()
|
||||
}
|
||||
|
||||
// Nodes are equal if they are the same host address:port & are on the same network
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is Node) return false
|
||||
return host == other.host && address == other.address && rpcPort == other.rpcPort && networkType == other.networkType && username == other.username && password == other.password
|
||||
}
|
||||
|
||||
val isOnion: Boolean
|
||||
get() = host?.let { OnionHelper.isOnionHost(it) } == true
|
||||
val isI2P: Boolean
|
||||
get() = host?.let { OnionHelper.isI2PHost(it) } == true
|
||||
|
||||
fun toNodeString(): String {
|
||||
return toString()
|
||||
}
|
||||
|
||||
fun toJson(): JSONObject {
|
||||
val jsonObject = JSONObject()
|
||||
try {
|
||||
if (username.isNotEmpty() && password.isNotEmpty()) {
|
||||
jsonObject.put("username", username)
|
||||
jsonObject.put("password", password)
|
||||
}
|
||||
jsonObject.put("host", host)
|
||||
jsonObject.put("rpcPort", rpcPort)
|
||||
when (networkType) {
|
||||
NetworkType.NetworkType_Mainnet -> jsonObject.put("network", MAINNET)
|
||||
NetworkType.NetworkType_Stagenet -> jsonObject.put("network", STAGENET)
|
||||
NetworkType.NetworkType_Testnet -> jsonObject.put("network", TESTNET)
|
||||
null -> TODO()
|
||||
}
|
||||
if (name?.isNotEmpty() == true) jsonObject.put("name", name)
|
||||
} catch (e: JSONException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
return jsonObject
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val sb = StringBuilder()
|
||||
if (username.isNotEmpty() && password.isNotEmpty()) {
|
||||
sb.append(username).append(":").append(password).append("@")
|
||||
}
|
||||
sb.append(host).append(":").append(rpcPort)
|
||||
sb.append("/")
|
||||
when (networkType) {
|
||||
NetworkType.NetworkType_Mainnet -> sb.append(MAINNET)
|
||||
NetworkType.NetworkType_Stagenet -> sb.append(STAGENET)
|
||||
NetworkType.NetworkType_Testnet -> sb.append(TESTNET)
|
||||
null -> TODO()
|
||||
}
|
||||
if (name != null) try {
|
||||
sb.append("/").append(URLEncoder.encode(name, "UTF-8"))
|
||||
} catch (ex: UnsupportedEncodingException) {
|
||||
Timber.w(ex) // if we can't encode it, we don't store it
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
val address: String
|
||||
get() = "$host:$rpcPort"
|
||||
|
||||
private fun overwriteWith(anotherNode: Node) {
|
||||
check(networkType == anotherNode.networkType) { "network types do not match" }
|
||||
name = anotherNode.name
|
||||
host = anotherNode.host
|
||||
rpcPort = anotherNode.rpcPort
|
||||
levinPort = anotherNode.levinPort
|
||||
username = anotherNode.username
|
||||
password = anotherNode.password
|
||||
favourite = anotherNode.favourite
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAINNET = "mainnet"
|
||||
const val STAGENET = "stagenet"
|
||||
const val TESTNET = "testnet"
|
||||
private var DEFAULT_LEVIN_PORT = 0
|
||||
private var DEFAULT_RPC_PORT = 0
|
||||
@JvmStatic
|
||||
fun fromString(nodeString: String?): Node? {
|
||||
return try {
|
||||
Node(nodeString)
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
Timber.w(ex)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun fromJson(jsonObject: JSONObject?): Node? {
|
||||
return try {
|
||||
Node(jsonObject)
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
Timber.w(ex)
|
||||
null
|
||||
} catch (ex: UnknownHostException) {
|
||||
Timber.w(ex)
|
||||
null
|
||||
} catch (ex: JSONException) {
|
||||
Timber.w(ex)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val defaultRpcPort: Int
|
||||
// every node knows its network, but they are all the same
|
||||
get() {
|
||||
if (DEFAULT_RPC_PORT > 0) return DEFAULT_RPC_PORT
|
||||
DEFAULT_RPC_PORT = when (WalletManager.instance?.networkType) {
|
||||
NetworkType.NetworkType_Mainnet -> 18081
|
||||
NetworkType.NetworkType_Testnet -> 28081
|
||||
NetworkType.NetworkType_Stagenet -> 38081
|
||||
else -> throw IllegalStateException("unsupported net " + WalletManager.instance?.networkType)
|
||||
}
|
||||
return DEFAULT_RPC_PORT
|
||||
}
|
||||
|
||||
private val defaultLevinPort: Int
|
||||
// every node knows its network, but they are all the same
|
||||
get() {
|
||||
if (DEFAULT_LEVIN_PORT > 0) return DEFAULT_LEVIN_PORT
|
||||
DEFAULT_LEVIN_PORT = when (WalletManager.instance?.networkType) {
|
||||
NetworkType.NetworkType_Mainnet -> 18080
|
||||
NetworkType.NetworkType_Testnet -> 28080
|
||||
NetworkType.NetworkType_Stagenet -> 38080
|
||||
else -> throw IllegalStateException("unsupported net " + WalletManager.instance?.networkType)
|
||||
}
|
||||
return DEFAULT_LEVIN_PORT
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.data;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class Subaddress implements Comparable<Subaddress> {
|
||||
public static final Pattern DEFAULT_LABEL_FORMATTER = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$");
|
||||
final private int accountIndex;
|
||||
final private int addressIndex;
|
||||
final private String address;
|
||||
private final String label;
|
||||
private long amount;
|
||||
|
||||
public Subaddress(int accountIndex, int addressIndex, String address, String label) {
|
||||
this.accountIndex = accountIndex;
|
||||
this.addressIndex = addressIndex;
|
||||
this.address = address;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Subaddress another) { // newer is <
|
||||
final int compareAccountIndex = another.accountIndex - accountIndex;
|
||||
if (compareAccountIndex == 0)
|
||||
return another.addressIndex - addressIndex;
|
||||
return compareAccountIndex;
|
||||
}
|
||||
|
||||
public String getSquashedAddress() {
|
||||
return address.substring(0, 8) + "…" + address.substring(address.length() - 8);
|
||||
}
|
||||
|
||||
public String getDisplayLabel() {
|
||||
if (label.isEmpty() || (DEFAULT_LABEL_FORMATTER.matcher(label).matches()))
|
||||
return ("#" + addressIndex);
|
||||
else
|
||||
return label;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public int getAccountIndex() {
|
||||
return accountIndex;
|
||||
}
|
||||
|
||||
public int getAddressIndex() {
|
||||
return addressIndex;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setAmount(long amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
38
app/src/main/java/net/mynero/wallet/data/Subaddress.kt
Normal file
38
app/src/main/java/net/mynero/wallet/data/Subaddress.kt
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.data
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class Subaddress(private val accountIndex: Int, @JvmField val addressIndex: Int, @JvmField val address: String, val label: String) : Comparable<Subaddress> {
|
||||
@JvmField
|
||||
var amount: Long = 0
|
||||
|
||||
override fun compareTo(other: Subaddress): Int { // newer is <
|
||||
val compareAccountIndex = other.accountIndex - accountIndex
|
||||
return if (compareAccountIndex == 0) other.addressIndex - addressIndex else compareAccountIndex
|
||||
}
|
||||
|
||||
val squashedAddress: String
|
||||
get() = address.substring(0, 8) + "…" + address.substring(address.length - 8)
|
||||
val displayLabel: String
|
||||
get() = if (label.isEmpty() || DEFAULT_LABEL_FORMATTER.matcher(label).matches()) "#$addressIndex" else label
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val DEFAULT_LABEL_FORMATTER: Pattern = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$")
|
||||
}
|
||||
}
|
|
@ -1,157 +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 net.mynero.wallet.data;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import net.mynero.wallet.model.PendingTransaction;
|
||||
import net.mynero.wallet.model.Wallet;
|
||||
import net.mynero.wallet.util.Helper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
|
||||
public class TxData implements Parcelable {
|
||||
|
||||
// this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
|
||||
public static final Parcelable.Creator<TxData> CREATOR = new Parcelable.Creator<TxData>() {
|
||||
public TxData createFromParcel(Parcel in) {
|
||||
return new TxData(in);
|
||||
}
|
||||
|
||||
public TxData[] newArray(int size) {
|
||||
return new TxData[size];
|
||||
}
|
||||
};
|
||||
private String dstAddr;
|
||||
private long amount;
|
||||
private int mixin;
|
||||
private PendingTransaction.Priority priority;
|
||||
private UserNotes userNotes;
|
||||
private ArrayList<String> preferredInputs;
|
||||
|
||||
public TxData() {
|
||||
}
|
||||
|
||||
public TxData(TxData txData) {
|
||||
this.dstAddr = txData.dstAddr;
|
||||
this.amount = txData.amount;
|
||||
this.mixin = txData.mixin;
|
||||
this.priority = txData.priority;
|
||||
this.preferredInputs = txData.preferredInputs;
|
||||
}
|
||||
|
||||
public TxData(String dstAddr,
|
||||
long amount,
|
||||
int mixin,
|
||||
PendingTransaction.Priority priority,
|
||||
ArrayList<String> preferredInputs) {
|
||||
this.dstAddr = dstAddr;
|
||||
this.amount = amount;
|
||||
this.mixin = mixin;
|
||||
this.priority = priority;
|
||||
this.preferredInputs = preferredInputs;
|
||||
}
|
||||
|
||||
protected TxData(Parcel in) {
|
||||
dstAddr = in.readString();
|
||||
amount = in.readLong();
|
||||
mixin = in.readInt();
|
||||
priority = PendingTransaction.Priority.fromInteger(in.readInt());
|
||||
in.readStringList(preferredInputs);
|
||||
}
|
||||
|
||||
public String getDestinationAddress() {
|
||||
return dstAddr;
|
||||
}
|
||||
|
||||
public void setDestinationAddress(String dstAddr) {
|
||||
this.dstAddr = dstAddr;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(long amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public void setAmount(double amount) {
|
||||
this.amount = Wallet.getAmountFromDouble(amount);
|
||||
}
|
||||
|
||||
public double getAmountAsDouble() {
|
||||
return 1.0 * amount / Helper.ONE_XMR;
|
||||
}
|
||||
|
||||
public ArrayList<String> getPreferredInputs() {
|
||||
return preferredInputs;
|
||||
}
|
||||
|
||||
public int getMixin() {
|
||||
return mixin;
|
||||
}
|
||||
|
||||
public void setMixin(int mixin) {
|
||||
this.mixin = mixin;
|
||||
}
|
||||
|
||||
public PendingTransaction.Priority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
public void setPriority(PendingTransaction.Priority priority) {
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public UserNotes getUserNotes() {
|
||||
return userNotes;
|
||||
}
|
||||
|
||||
public void setUserNotes(UserNotes userNotes) {
|
||||
this.userNotes = userNotes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeString(dstAddr);
|
||||
out.writeLong(amount);
|
||||
out.writeInt(mixin);
|
||||
out.writeInt(priority.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("dstAddr:");
|
||||
sb.append(dstAddr);
|
||||
sb.append(",amount:");
|
||||
sb.append(amount);
|
||||
sb.append(",mixin:");
|
||||
sb.append(mixin);
|
||||
sb.append(",priority:");
|
||||
sb.append(priority);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
104
app/src/main/java/net/mynero/wallet/data/TxData.kt
Normal file
104
app/src/main/java/net/mynero/wallet/data/TxData.kt
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.data
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.os.Parcelable.Creator
|
||||
import net.mynero.wallet.model.PendingTransaction
|
||||
import net.mynero.wallet.model.Wallet
|
||||
import net.mynero.wallet.util.Helper
|
||||
|
||||
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
|
||||
class TxData : Parcelable {
|
||||
var destinationAddress: String? = null
|
||||
var amount: Long = 0
|
||||
var mixin = 0
|
||||
var priority: PendingTransaction.Priority? = null
|
||||
var userNotes: UserNotes? = null
|
||||
var preferredInputs: ArrayList<String>? = null
|
||||
private set
|
||||
|
||||
constructor()
|
||||
constructor(txData: TxData) {
|
||||
destinationAddress = txData.destinationAddress
|
||||
amount = txData.amount
|
||||
mixin = txData.mixin
|
||||
priority = txData.priority
|
||||
preferredInputs = txData.preferredInputs
|
||||
}
|
||||
|
||||
constructor(dstAddr: String?,
|
||||
amount: Long,
|
||||
mixin: Int,
|
||||
priority: PendingTransaction.Priority?,
|
||||
preferredInputs: ArrayList<String>?) {
|
||||
destinationAddress = dstAddr
|
||||
this.amount = amount
|
||||
this.mixin = mixin
|
||||
this.priority = priority
|
||||
this.preferredInputs = preferredInputs
|
||||
}
|
||||
|
||||
protected constructor(`in`: Parcel) {
|
||||
destinationAddress = `in`.readString()
|
||||
amount = `in`.readLong()
|
||||
mixin = `in`.readInt()
|
||||
priority = PendingTransaction.Priority.fromInteger(`in`.readInt())
|
||||
`in`.readStringList(preferredInputs!!)
|
||||
}
|
||||
|
||||
fun setAmount(amount: Double) {
|
||||
this.amount = Wallet.getAmountFromDouble(amount)
|
||||
}
|
||||
|
||||
val amountAsDouble: Double
|
||||
get() = 1.0 * amount / Helper.ONE_XMR
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
out.writeString(destinationAddress)
|
||||
out.writeLong(amount)
|
||||
out.writeInt(mixin)
|
||||
out.writeInt(priority!!.value)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val sb = StringBuffer()
|
||||
sb.append("dstAddr:")
|
||||
sb.append(destinationAddress)
|
||||
sb.append(",amount:")
|
||||
sb.append(amount)
|
||||
sb.append(",mixin:")
|
||||
sb.append(mixin)
|
||||
sb.append(",priority:")
|
||||
sb.append(priority)
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
companion object CREATOR : Creator<TxData> {
|
||||
override fun createFromParcel(parcel: Parcel): TxData {
|
||||
return TxData(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<TxData?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,80 +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 net.mynero.wallet.data;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class UserNotes {
|
||||
public String txNotes = "";
|
||||
public String note = "";
|
||||
public String xmrtoTag = null;
|
||||
public String xmrtoKey = null;
|
||||
public String xmrtoAmount = null; // could be a double - but we are not doing any calculations
|
||||
public String xmrtoCurrency = null;
|
||||
public String xmrtoDestination = null;
|
||||
|
||||
public UserNotes(final String txNotes) {
|
||||
if (txNotes == null) {
|
||||
return;
|
||||
}
|
||||
this.txNotes = txNotes;
|
||||
Pattern p = Pattern.compile("^\\{([a-z]+)-(\\w{6,}),([0-9.]*)([A-Z]+),(\\w*)\\} ?(.*)");
|
||||
Matcher m = p.matcher(txNotes);
|
||||
if (m.find()) {
|
||||
xmrtoTag = m.group(1);
|
||||
xmrtoKey = m.group(2);
|
||||
xmrtoAmount = m.group(3);
|
||||
xmrtoCurrency = m.group(4);
|
||||
xmrtoDestination = m.group(5);
|
||||
note = m.group(6);
|
||||
} else {
|
||||
note = txNotes;
|
||||
}
|
||||
}
|
||||
|
||||
public void setNote(String newNote) {
|
||||
if (newNote != null) {
|
||||
note = newNote;
|
||||
} else {
|
||||
note = "";
|
||||
}
|
||||
txNotes = buildTxNote();
|
||||
}
|
||||
|
||||
private String buildTxNote() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (xmrtoKey != null) {
|
||||
if ((xmrtoAmount == null) || (xmrtoDestination == null))
|
||||
throw new IllegalArgumentException("Broken notes");
|
||||
sb.append("{");
|
||||
sb.append(xmrtoTag);
|
||||
sb.append("-");
|
||||
sb.append(xmrtoKey);
|
||||
sb.append(",");
|
||||
sb.append(xmrtoAmount);
|
||||
sb.append(xmrtoCurrency);
|
||||
sb.append(",");
|
||||
sb.append(xmrtoDestination);
|
||||
sb.append("}");
|
||||
if ((note != null) && (!note.isEmpty()))
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(note);
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
74
app/src/main/java/net/mynero/wallet/data/UserNotes.kt
Normal file
74
app/src/main/java/net/mynero/wallet/data/UserNotes.kt
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.data
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class UserNotes(txNotes: String?) {
|
||||
var txNotes: String? = ""
|
||||
@JvmField
|
||||
var note: String? = ""
|
||||
@JvmField
|
||||
var xmrtoTag: String? = null
|
||||
@JvmField
|
||||
var xmrtoKey: String? = null
|
||||
@JvmField
|
||||
var xmrtoAmount: String? = null // could be a double - but we are not doing any calculations
|
||||
var xmrtoCurrency: String? = null
|
||||
@JvmField
|
||||
var xmrtoDestination: String? = null
|
||||
|
||||
init {
|
||||
this.txNotes = txNotes
|
||||
val p = Pattern.compile("^\\{([a-z]+)-(\\w{6,}),([0-9.]*)([A-Z]+),(\\w*)\\} ?(.*)")
|
||||
val m = p.matcher(txNotes)
|
||||
if (m.find()) {
|
||||
xmrtoTag = m.group(1)
|
||||
xmrtoKey = m.group(2)
|
||||
xmrtoAmount = m.group(3)
|
||||
xmrtoCurrency = m.group(4)
|
||||
xmrtoDestination = m.group(5)
|
||||
note = m.group(6)
|
||||
} else {
|
||||
note = txNotes
|
||||
}
|
||||
}
|
||||
|
||||
fun setNote(newNote: String?) {
|
||||
note = newNote ?: ""
|
||||
txNotes = buildTxNote()
|
||||
}
|
||||
|
||||
private fun buildTxNote(): String {
|
||||
val sb = StringBuilder()
|
||||
if (xmrtoKey != null) {
|
||||
require(!(xmrtoAmount == null || xmrtoDestination == null)) { "Broken notes" }
|
||||
sb.append("{")
|
||||
sb.append(xmrtoTag)
|
||||
sb.append("-")
|
||||
sb.append(xmrtoKey)
|
||||
sb.append(",")
|
||||
sb.append(xmrtoAmount)
|
||||
sb.append(xmrtoCurrency)
|
||||
sb.append(",")
|
||||
sb.append(xmrtoDestination)
|
||||
sb.append("}")
|
||||
if (note != null && !note!!.isEmpty()) sb.append(" ")
|
||||
}
|
||||
sb.append(note)
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
|
@ -106,7 +106,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
|
|||
donateTextView = view.findViewById(R.id.donate_label_textview);
|
||||
donateTextView.setOnClickListener(view1 -> addressEditText.setText(Constants.DONATE_ADDRESS));
|
||||
if (uriData != null) {
|
||||
addressEditText.setText(uriData.getAddress());
|
||||
addressEditText.setText(uriData.address);
|
||||
if (uriData.hasAmount()) {
|
||||
amountEditText.setText(uriData.getAmount());
|
||||
}
|
||||
|
@ -116,8 +116,8 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
|
|||
long selectedValue = 0;
|
||||
|
||||
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
||||
if (selectedUtxos.contains(coinsInfo.getKeyImage())) {
|
||||
selectedValue += coinsInfo.getAmount();
|
||||
if (selectedUtxos.contains(coinsInfo.keyImage)) {
|
||||
selectedValue += coinsInfo.amount;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,7 +338,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
|
|||
private void pasteAddress(String address) {
|
||||
UriData uriData = UriData.parse(address);
|
||||
if (uriData != null) {
|
||||
addressEditText.setText(uriData.getAddress());
|
||||
addressEditText.setText(uriData.address);
|
||||
if (uriData.hasAmount()) {
|
||||
amountEditText.setText(uriData.getAmount());
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ public class OnboardingViewModel extends ViewModel {
|
|||
} else {
|
||||
mainActivity.runOnUiThread(() -> {
|
||||
_enableCreateButton.postValue(true);
|
||||
Toast.makeText(mainActivity, application.getString(R.string.create_wallet_failed, walletStatus.getErrorString()), Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(mainActivity, application.getString(R.string.create_wallet_failed, walletStatus.errorString), Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -91,7 +91,7 @@ public class ReceiveFragment extends Fragment {
|
|||
|
||||
private void editAddressLabel(Subaddress subaddress) {
|
||||
EditAddressLabelBottomSheetDialog dialog = new EditAddressLabelBottomSheetDialog();
|
||||
dialog.addressIndex = subaddress.getAddressIndex();
|
||||
dialog.addressIndex = subaddress.addressIndex;
|
||||
dialog.listener = () -> mViewModel.init();
|
||||
dialog.show(getParentFragmentManager(), "edit_address_dialog");
|
||||
}
|
||||
|
@ -99,11 +99,11 @@ public class ReceiveFragment extends Fragment {
|
|||
private void setAddress(Subaddress subaddress) {
|
||||
final String label = subaddress.getDisplayLabel();
|
||||
final String address = getContext().getString(R.string.subbaddress_info_subtitle,
|
||||
subaddress.getAddressIndex(), subaddress.getSquashedAddress());
|
||||
subaddress.addressIndex, subaddress.getSquashedAddress());
|
||||
addressLabelTextView.setText(label.isEmpty() ? address : label);
|
||||
addressTextView.setText(subaddress.getAddress());
|
||||
addressImageView.setImageBitmap(generate(subaddress.getAddress(), 256, 256));
|
||||
copyAddressImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "address", subaddress.getAddress()));
|
||||
addressTextView.setText(subaddress.address);
|
||||
addressImageView.setImageBitmap(generate(subaddress.address, 256, 256));
|
||||
copyAddressImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "address", subaddress.address));
|
||||
addressLabelTextView.setOnLongClickListener(v -> {
|
||||
editAddressLabel(subaddress);
|
||||
return true;
|
||||
|
|
|
@ -411,7 +411,7 @@ public class SendFragment extends Fragment {
|
|||
mViewModel.setShowAddOutputButton(false);
|
||||
}
|
||||
EditText addressField = entryView.findViewById(R.id.address_edittext);
|
||||
addressField.setText(uriData.getAddress());
|
||||
addressField.setText(uriData.address);
|
||||
if (uriData.hasAmount()) {
|
||||
setAmount(entryView, uriData.getAmount());
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
|
|||
sendUtxosButton.setOnClickListener(view1 -> {
|
||||
ArrayList<String> selectedKeyImages = new ArrayList<>();
|
||||
for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) {
|
||||
selectedKeyImages.add(coinsInfo.getKeyImage());
|
||||
selectedKeyImages.add(coinsInfo.keyImage);
|
||||
}
|
||||
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
|
||||
sendDialog.listener = this;
|
||||
|
@ -82,12 +82,12 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
|
|||
churnUtxosButton.setOnClickListener(view1 -> {
|
||||
ArrayList<String> selectedKeyImages = new ArrayList<>();
|
||||
for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) {
|
||||
selectedKeyImages.add(coinsInfo.getKeyImage());
|
||||
selectedKeyImages.add(coinsInfo.keyImage);
|
||||
}
|
||||
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
|
||||
sendDialog.listener = this;
|
||||
sendDialog.isChurning = true;
|
||||
sendDialog.uriData = UriData.parse(AddressService.getInstance().currentSubaddress().getAddress());
|
||||
sendDialog.uriData = UriData.parse(AddressService.getInstance().currentSubaddress().address);
|
||||
sendDialog.selectedUtxos = selectedKeyImages;
|
||||
sendDialog.show(getActivity().getSupportFragmentManager(), null);
|
||||
});
|
||||
|
@ -103,7 +103,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
|
|||
HashMap<String, CoinsInfo> filteredUtxos = new HashMap<>();
|
||||
for (CoinsInfo coinsInfo : utxos) {
|
||||
if (!coinsInfo.isSpent()) {
|
||||
filteredUtxos.put(coinsInfo.getPubKey(), coinsInfo);
|
||||
filteredUtxos.put(coinsInfo.pubKey, coinsInfo);
|
||||
}
|
||||
}
|
||||
if (filteredUtxos.isEmpty()) {
|
||||
|
|
|
@ -13,65 +13,58 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.livedata
|
||||
|
||||
package net.mynero.wallet.livedata;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import android.util.Log
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
|
||||
* navigation and Snackbar messages.
|
||||
* <p>
|
||||
*
|
||||
*
|
||||
* This avoids a common problem with events: on configuration change (like rotation) an update
|
||||
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
|
||||
* explicit call to setValue() or call().
|
||||
* <p>
|
||||
*
|
||||
*
|
||||
* Note that only one observer is going to be notified of changes.
|
||||
*/
|
||||
public class SingleLiveEvent<T> extends MutableLiveData<T> {
|
||||
|
||||
private static final String TAG = "SingleLiveEvent";
|
||||
|
||||
private final AtomicBoolean mPending = new AtomicBoolean(false);
|
||||
|
||||
class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
private val mPending = AtomicBoolean(false)
|
||||
@MainThread
|
||||
@Override
|
||||
public void observe(LifecycleOwner owner, final Observer<? super T> observer) {
|
||||
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||
if (hasActiveObservers()) {
|
||||
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
|
||||
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
|
||||
}
|
||||
|
||||
// Observe the internal MutableLiveData
|
||||
super.observe(owner, new Observer<T>() {
|
||||
@Override
|
||||
public void onChanged(@Nullable T t) {
|
||||
super.observe(owner) { value ->
|
||||
if (mPending.compareAndSet(true, false)) {
|
||||
observer.onChanged(t);
|
||||
observer.onChanged(value)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void setValue(@Nullable T t) {
|
||||
mPending.set(true);
|
||||
super.setValue(t);
|
||||
override fun setValue(t: T?) {
|
||||
mPending.set(true)
|
||||
super.setValue(t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for cases where T is Void, to make calls cleaner.
|
||||
*/
|
||||
@MainThread
|
||||
public void call() {
|
||||
setValue(null);
|
||||
fun call() {
|
||||
value = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SingleLiveEvent"
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package net.mynero.wallet.model;
|
||||
|
||||
import net.mynero.wallet.service.PrefService;
|
||||
import net.mynero.wallet.util.Constants;
|
||||
|
||||
public class BalanceInfo {
|
||||
private final long rawUnlocked;
|
||||
private final long rawLocked;
|
||||
|
||||
public BalanceInfo(long rawUnlocked, long rawLocked) {
|
||||
this.rawUnlocked = rawUnlocked;
|
||||
this.rawLocked = rawLocked;
|
||||
}
|
||||
|
||||
public long getRawLocked() {
|
||||
return rawLocked;
|
||||
}
|
||||
|
||||
public long getRawUnlocked() {
|
||||
return rawUnlocked;
|
||||
}
|
||||
|
||||
public boolean isUnlockedBalanceZero() {
|
||||
return rawUnlocked == 0;
|
||||
}
|
||||
|
||||
public boolean isLockedBalanceZero() {
|
||||
return rawLocked == 0;
|
||||
}
|
||||
|
||||
public String getUnlockedDisplay() {
|
||||
boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
|
||||
if(streetModeEnabled) {
|
||||
return Constants.STREET_MODE_BALANCE;
|
||||
} else {
|
||||
return Wallet.getDisplayAmount(rawUnlocked);
|
||||
}
|
||||
}
|
||||
|
||||
public String getLockedDisplay() {
|
||||
boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
|
||||
if(streetModeEnabled) {
|
||||
return Constants.STREET_MODE_BALANCE;
|
||||
} else {
|
||||
return Wallet.getDisplayAmount(rawLocked);
|
||||
}
|
||||
}
|
||||
}
|
32
app/src/main/java/net/mynero/wallet/model/BalanceInfo.kt
Normal file
32
app/src/main/java/net/mynero/wallet/model/BalanceInfo.kt
Normal file
|
@ -0,0 +1,32 @@
|
|||
package net.mynero.wallet.model
|
||||
|
||||
import net.mynero.wallet.service.PrefService
|
||||
import net.mynero.wallet.util.Constants
|
||||
|
||||
class BalanceInfo(val rawUnlocked: Long, val rawLocked: Long) {
|
||||
|
||||
val isUnlockedBalanceZero: Boolean
|
||||
get() = rawUnlocked == 0L
|
||||
val isLockedBalanceZero: Boolean
|
||||
get() = rawLocked == 0L
|
||||
val unlockedDisplay: String
|
||||
get() {
|
||||
val streetModeEnabled =
|
||||
PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false)
|
||||
return if (streetModeEnabled) {
|
||||
Constants.STREET_MODE_BALANCE
|
||||
} else {
|
||||
Wallet.getDisplayAmount(rawUnlocked)
|
||||
}
|
||||
}
|
||||
val lockedDisplay: String
|
||||
get() {
|
||||
val streetModeEnabled =
|
||||
PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false)
|
||||
return if (streetModeEnabled) {
|
||||
Constants.STREET_MODE_BALANCE
|
||||
} else {
|
||||
Wallet.getDisplayAmount(rawLocked)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +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 net.mynero.wallet.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Coins {
|
||||
static {
|
||||
System.loadLibrary("monerujo");
|
||||
}
|
||||
|
||||
private final long handle;
|
||||
|
||||
private List<CoinsInfo> coins = new ArrayList<>();
|
||||
|
||||
public Coins(long handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
public native int getCount(); // over all accounts/subaddresses
|
||||
|
||||
public List<CoinsInfo> getAll() {
|
||||
return coins;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
List<CoinsInfo> transactionInfos = refreshJ();
|
||||
Timber.d("refresh size=%d", transactionInfos.size());
|
||||
coins = transactionInfos;
|
||||
}
|
||||
|
||||
public native void setFrozen(String publicKey, boolean frozen);
|
||||
|
||||
private native List<CoinsInfo> refreshJ();
|
||||
}
|
39
app/src/main/java/net/mynero/wallet/model/Coins.kt
Normal file
39
app/src/main/java/net/mynero/wallet/model/Coins.kt
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.model
|
||||
|
||||
import timber.log.Timber
|
||||
|
||||
class Coins(private val handle: Long) {
|
||||
var all: List<CoinsInfo> = ArrayList()
|
||||
private set
|
||||
|
||||
fun refresh() {
|
||||
val transactionInfos = refreshJ()
|
||||
Timber.d("refresh size=%d", transactionInfos.size)
|
||||
all = transactionInfos
|
||||
}
|
||||
|
||||
external fun setFrozen(publicKey: String?, frozen: Boolean)
|
||||
private external fun refreshJ(): List<CoinsInfo>
|
||||
external fun getCount(): Int
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("monerujo")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,131 +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 net.mynero.wallet.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class CoinsInfo implements Parcelable, Comparable<CoinsInfo> {
|
||||
public static final Creator<CoinsInfo> CREATOR = new Creator<CoinsInfo>() {
|
||||
@Override
|
||||
public CoinsInfo createFromParcel(Parcel in) {
|
||||
return new CoinsInfo(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CoinsInfo[] newArray(int size) {
|
||||
return new CoinsInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
static {
|
||||
System.loadLibrary("monerujo");
|
||||
}
|
||||
|
||||
long globalOutputIndex;
|
||||
boolean spent;
|
||||
String keyImage;
|
||||
long amount;
|
||||
String hash;
|
||||
String pubKey;
|
||||
boolean unlocked;
|
||||
long localOutputIndex;
|
||||
boolean frozen;
|
||||
String address;
|
||||
|
||||
public CoinsInfo(long globalOutputIndex, boolean spent, String keyImage, long amount, String hash, String pubKey, boolean unlocked, long localOutputIndex, boolean frozen, String address) {
|
||||
this.globalOutputIndex = globalOutputIndex;
|
||||
this.spent = spent;
|
||||
this.keyImage = keyImage;
|
||||
this.amount = amount;
|
||||
this.hash = hash;
|
||||
this.pubKey = pubKey;
|
||||
this.unlocked = unlocked;
|
||||
this.localOutputIndex = localOutputIndex;
|
||||
this.frozen = frozen;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
protected CoinsInfo(Parcel in) {
|
||||
globalOutputIndex = in.readLong();
|
||||
}
|
||||
|
||||
public long getGlobalOutputIndex() {
|
||||
return globalOutputIndex;
|
||||
}
|
||||
|
||||
public boolean isSpent() {
|
||||
return spent;
|
||||
}
|
||||
|
||||
public String getKeyImage() {
|
||||
return keyImage;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public String getPubKey() {
|
||||
return pubKey;
|
||||
}
|
||||
|
||||
public boolean isUnlocked() {
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
public long getLocalOutputIndex() {
|
||||
return localOutputIndex;
|
||||
}
|
||||
|
||||
public boolean isFrozen() {
|
||||
return frozen;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
||||
parcel.writeLong(globalOutputIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CoinsInfo another) {
|
||||
long b1 = this.amount;
|
||||
long b2 = another.amount;
|
||||
if (b1 > b2) {
|
||||
return -1;
|
||||
} else if (b1 < b2) {
|
||||
return 1;
|
||||
} else {
|
||||
return this.hash.compareTo(another.hash);
|
||||
}
|
||||
}
|
||||
}
|
105
app/src/main/java/net/mynero/wallet/model/CoinsInfo.kt
Normal file
105
app/src/main/java/net/mynero/wallet/model/CoinsInfo.kt
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.model
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.os.Parcelable.Creator
|
||||
|
||||
class CoinsInfo : Parcelable, Comparable<CoinsInfo> {
|
||||
@JvmField
|
||||
var globalOutputIndex: Long
|
||||
var isSpent = false
|
||||
@JvmField
|
||||
var keyImage: String? = null
|
||||
@JvmField
|
||||
var amount: Long = 0
|
||||
@JvmField
|
||||
var hash: String? = null
|
||||
@JvmField
|
||||
var pubKey: String? = null
|
||||
var isUnlocked = false
|
||||
@JvmField
|
||||
var localOutputIndex: Long = 0
|
||||
var isFrozen = false
|
||||
@JvmField
|
||||
var address: String? = null
|
||||
|
||||
constructor(
|
||||
globalOutputIndex: Long,
|
||||
spent: Boolean,
|
||||
keyImage: String?,
|
||||
amount: Long,
|
||||
hash: String?,
|
||||
pubKey: String?,
|
||||
unlocked: Boolean,
|
||||
localOutputIndex: Long,
|
||||
frozen: Boolean,
|
||||
address: String?
|
||||
) {
|
||||
this.globalOutputIndex = globalOutputIndex
|
||||
isSpent = spent
|
||||
this.keyImage = keyImage
|
||||
this.amount = amount
|
||||
this.hash = hash
|
||||
this.pubKey = pubKey
|
||||
isUnlocked = unlocked
|
||||
this.localOutputIndex = localOutputIndex
|
||||
isFrozen = frozen
|
||||
this.address = address
|
||||
}
|
||||
|
||||
protected constructor(`in`: Parcel) {
|
||||
globalOutputIndex = `in`.readLong()
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, i: Int) {
|
||||
parcel.writeLong(globalOutputIndex)
|
||||
}
|
||||
|
||||
override fun compareTo(another: CoinsInfo): Int {
|
||||
val b1 = amount
|
||||
val b2 = another.amount
|
||||
return if (b1 > b2) {
|
||||
-1
|
||||
} else if (b1 < b2) {
|
||||
1
|
||||
} else {
|
||||
hash!!.compareTo(another.hash!!)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val CREATOR: Creator<CoinsInfo?> = object : Creator<CoinsInfo?> {
|
||||
override fun createFromParcel(`in`: Parcel): CoinsInfo? {
|
||||
return CoinsInfo(`in`)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<CoinsInfo?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
System.loadLibrary("monerujo")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,33 +13,20 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.model
|
||||
|
||||
package net.mynero.wallet.model;
|
||||
enum class NetworkType(@JvmField val value: Int) {
|
||||
NetworkType_Mainnet(0), NetworkType_Testnet(1), NetworkType_Stagenet(2);
|
||||
|
||||
public enum NetworkType {
|
||||
NetworkType_Mainnet(0),
|
||||
NetworkType_Testnet(1),
|
||||
NetworkType_Stagenet(2);
|
||||
|
||||
private final int value;
|
||||
|
||||
NetworkType(int value) {
|
||||
this.value = value;
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun fromInteger(n: Int): NetworkType? {
|
||||
when (n) {
|
||||
0 -> return NetworkType_Mainnet
|
||||
1 -> return NetworkType_Testnet
|
||||
2 -> return NetworkType_Stagenet
|
||||
}
|
||||
|
||||
public static NetworkType fromInteger(int n) {
|
||||
switch (n) {
|
||||
case 0:
|
||||
return NetworkType_Mainnet;
|
||||
case 1:
|
||||
return NetworkType_Testnet;
|
||||
case 2:
|
||||
return NetworkType_Stagenet;
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -1,98 +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 net.mynero.wallet.model;
|
||||
|
||||
public class PendingTransaction {
|
||||
static {
|
||||
System.loadLibrary("monerujo");
|
||||
}
|
||||
|
||||
public long handle;
|
||||
|
||||
PendingTransaction(long handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return Status.values()[getStatusJ()];
|
||||
}
|
||||
|
||||
public native int getStatusJ();
|
||||
|
||||
public native String getErrorString();
|
||||
|
||||
// commit transaction or save to file if filename is provided.
|
||||
public native boolean commit(String filename, boolean overwrite);
|
||||
|
||||
public native long getAmount();
|
||||
|
||||
public native long getDust();
|
||||
|
||||
public native long getFee();
|
||||
|
||||
public String getFirstTxId() {
|
||||
String id = getFirstTxIdJ();
|
||||
if (id == null)
|
||||
throw new IndexOutOfBoundsException();
|
||||
return id;
|
||||
}
|
||||
|
||||
public native String getFirstTxIdJ();
|
||||
|
||||
public native long getTxCount();
|
||||
|
||||
public enum Status {
|
||||
Status_Ok,
|
||||
Status_Error,
|
||||
Status_Critical
|
||||
}
|
||||
|
||||
public enum Priority {
|
||||
Priority_Default(0),
|
||||
Priority_Low(1),
|
||||
Priority_Medium(2),
|
||||
Priority_High(3),
|
||||
Priority_Last(4);
|
||||
|
||||
private final int value;
|
||||
|
||||
Priority(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static Priority fromInteger(int n) {
|
||||
switch (n) {
|
||||
case 0:
|
||||
return Priority_Default;
|
||||
case 1:
|
||||
return Priority_Low;
|
||||
case 2:
|
||||
return Priority_Medium;
|
||||
case 3:
|
||||
return Priority_High;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.model
|
||||
|
||||
class PendingTransaction internal constructor(var handle: Long) {
|
||||
val status: Status
|
||||
get() = Status.values()[getStatusJ()]
|
||||
external fun getStatusJ(): Int
|
||||
external fun getErrorString(): String?
|
||||
|
||||
// commit transaction or save to file if filename is provided.
|
||||
external fun commit(filename: String?, overwrite: Boolean): Boolean
|
||||
|
||||
external fun getAmount(): Long
|
||||
external fun getDust(): Long
|
||||
external fun getFee(): Long
|
||||
val firstTxId: String
|
||||
get() = getFirstTxIdJ() ?: throw IndexOutOfBoundsException()
|
||||
external fun getFirstTxIdJ(): String?
|
||||
external fun getTxCount(): Long
|
||||
|
||||
enum class Status {
|
||||
Status_Ok, Status_Error, Status_Critical
|
||||
}
|
||||
|
||||
enum class Priority(@JvmField val value: Int) {
|
||||
Priority_Default(0), Priority_Low(1), Priority_Medium(2), Priority_High(3), Priority_Last(4);
|
||||
|
||||
companion object {
|
||||
fun fromInteger(n: Int): Priority? {
|
||||
when (n) {
|
||||
0 -> return Priority_Default
|
||||
1 -> return Priority_Low
|
||||
2 -> return Priority_Medium
|
||||
3 -> return Priority_High
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("monerujo")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,69 +13,52 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.model
|
||||
|
||||
package net.mynero.wallet.model;
|
||||
import timber.log.Timber
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
class TransactionHistory(private val handle: Long, var accountIndex: Int) {
|
||||
var all: List<TransactionInfo> = ArrayList()
|
||||
private set
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class TransactionHistory {
|
||||
static {
|
||||
System.loadLibrary("monerujo");
|
||||
}
|
||||
|
||||
private final long handle;
|
||||
|
||||
int accountIndex;
|
||||
private List<TransactionInfo> transactions = new ArrayList<>();
|
||||
|
||||
public TransactionHistory(long handle, int accountIndex) {
|
||||
this.handle = handle;
|
||||
this.accountIndex = accountIndex;
|
||||
}
|
||||
|
||||
public void setAccountFor(Wallet wallet) {
|
||||
fun setAccountFor(wallet: Wallet) {
|
||||
if (accountIndex != wallet.getAccountIndex()) {
|
||||
this.accountIndex = wallet.getAccountIndex();
|
||||
refreshWithNotes(wallet);
|
||||
accountIndex = wallet.getAccountIndex()
|
||||
refreshWithNotes(wallet)
|
||||
}
|
||||
}
|
||||
|
||||
private void loadNotes(Wallet wallet) {
|
||||
for (TransactionInfo info : transactions) {
|
||||
info.notes = wallet.getUserNote(info.hash);
|
||||
private fun loadNotes(wallet: Wallet) {
|
||||
for (info in all) {
|
||||
info.notes = wallet.getUserNote(info.hash)
|
||||
}
|
||||
}
|
||||
|
||||
//private native long getTransactionByIndexJ(int i);
|
||||
external fun getCount(): Int
|
||||
|
||||
//private native long getTransactionByIdJ(String id);
|
||||
|
||||
public native int getCount(); // over all accounts/subaddresses
|
||||
|
||||
public List<TransactionInfo> getAll() {
|
||||
return transactions;
|
||||
fun refreshWithNotes(wallet: Wallet) {
|
||||
refresh()
|
||||
loadNotes(wallet)
|
||||
}
|
||||
|
||||
void refreshWithNotes(Wallet wallet) {
|
||||
refresh();
|
||||
loadNotes(wallet);
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
List<TransactionInfo> transactionInfos = refreshJ();
|
||||
Timber.d("refresh size=%d", transactionInfos.size());
|
||||
for (Iterator<TransactionInfo> iterator = transactionInfos.iterator(); iterator.hasNext(); ) {
|
||||
TransactionInfo info = iterator.next();
|
||||
private fun refresh() {
|
||||
val transactionInfos = refreshJ()
|
||||
Timber.d("refresh size=%d", transactionInfos.size)
|
||||
val iterator = transactionInfos.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val info = iterator.next()
|
||||
if (info.accountIndex != accountIndex) {
|
||||
iterator.remove();
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
transactions = transactionInfos;
|
||||
all = transactionInfos
|
||||
}
|
||||
|
||||
private native List<TransactionInfo> refreshJ();
|
||||
private external fun refreshJ(): MutableList<TransactionInfo>
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("monerujo")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package net.mynero.wallet.model;
|
||||
|
||||
import net.mynero.wallet.service.PrefService;
|
||||
import net.mynero.wallet.util.Constants;
|
||||
|
||||
public class TransactionOutput {
|
||||
private final String destination;
|
||||
private final long amount;
|
||||
|
||||
public TransactionOutput(String destination, long amount) {
|
||||
this.destination = destination;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package net.mynero.wallet.model
|
||||
|
||||
class TransactionOutput(@JvmField val destination: String, @JvmField val amount: Long)
|
|
@ -1,56 +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 net.mynero.wallet.model;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class Transfer implements Parcelable {
|
||||
public static final Parcelable.Creator<Transfer> CREATOR = new Parcelable.Creator<Transfer>() {
|
||||
public Transfer createFromParcel(Parcel in) {
|
||||
return new Transfer(in);
|
||||
}
|
||||
|
||||
public Transfer[] newArray(int size) {
|
||||
return new Transfer[size];
|
||||
}
|
||||
};
|
||||
public long amount;
|
||||
public String address;
|
||||
|
||||
public Transfer(long amount, String address) {
|
||||
this.amount = amount;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
private Transfer(Parcel in) {
|
||||
amount = in.readLong();
|
||||
address = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
out.writeLong(amount);
|
||||
out.writeString(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
55
app/src/main/java/net/mynero/wallet/model/Transfer.kt
Normal file
55
app/src/main/java/net/mynero/wallet/model/Transfer.kt
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.model
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.os.Parcelable.Creator
|
||||
|
||||
class Transfer : Parcelable {
|
||||
var amount: Long
|
||||
@JvmField
|
||||
var address: String?
|
||||
|
||||
constructor(amount: Long, address: String?) {
|
||||
this.amount = amount
|
||||
this.address = address
|
||||
}
|
||||
|
||||
private constructor(`in`: Parcel) {
|
||||
amount = `in`.readLong()
|
||||
address = `in`.readString()
|
||||
}
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
out.writeLong(amount)
|
||||
out.writeString(address)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Creator<Transfer> {
|
||||
override fun createFromParcel(parcel: Parcel): Transfer {
|
||||
return Transfer(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<Transfer?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,566 +13,464 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.model
|
||||
|
||||
package net.mynero.wallet.model;
|
||||
import android.util.Pair
|
||||
import net.mynero.wallet.data.Subaddress
|
||||
import net.mynero.wallet.model.NetworkType.Companion.fromInteger
|
||||
import net.mynero.wallet.model.WalletManager.Companion.instance
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
class Wallet {
|
||||
var isSynchronized = false
|
||||
private var accountIndex = 0
|
||||
private var handle: Long = 0
|
||||
private var listenerHandle: Long = 0
|
||||
private var pendingTransaction: PendingTransaction? = null
|
||||
var history: TransactionHistory? = null
|
||||
get() {
|
||||
if (field == null) {
|
||||
field = TransactionHistory(getHistoryJ(), accountIndex)
|
||||
}
|
||||
return field
|
||||
}
|
||||
private set
|
||||
var coins: Coins? = null
|
||||
get() {
|
||||
if (field == null) {
|
||||
field = Coins(getCoinsJ())
|
||||
}
|
||||
return field
|
||||
}
|
||||
private set
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import net.mynero.wallet.data.Subaddress;
|
||||
import net.mynero.wallet.data.TxData;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Wallet {
|
||||
final static public long SWEEP_ALL = Long.MAX_VALUE;
|
||||
private static final String NEW_ACCOUNT_NAME = "Untitled account"; // src/wallet/wallet2.cpp:941
|
||||
|
||||
static {
|
||||
System.loadLibrary("monerujo");
|
||||
internal constructor(handle: Long) {
|
||||
this.handle = handle
|
||||
}
|
||||
|
||||
boolean synced = false;
|
||||
private int accountIndex = 0;
|
||||
private long handle = 0;
|
||||
private long listenerHandle = 0;
|
||||
private PendingTransaction pendingTransaction = null;
|
||||
private TransactionHistory history = null;
|
||||
private Coins coins = null;
|
||||
|
||||
Wallet(long handle) {
|
||||
this.handle = handle;
|
||||
internal constructor(handle: Long, accountIndex: Int) {
|
||||
this.handle = handle
|
||||
this.accountIndex = accountIndex
|
||||
}
|
||||
|
||||
Wallet(long handle, int accountIndex) {
|
||||
this.handle = handle;
|
||||
this.accountIndex = accountIndex;
|
||||
fun getAccountIndex(): Int {
|
||||
return accountIndex
|
||||
}
|
||||
|
||||
public static native String getDisplayAmount(long amount);
|
||||
|
||||
public static native long getAmountFromString(String amount);
|
||||
|
||||
public static native long getAmountFromDouble(double amount);
|
||||
|
||||
public static native String generatePaymentId();
|
||||
|
||||
public static native boolean isPaymentIdValid(String payment_id);
|
||||
|
||||
public static boolean isAddressValid(String address) {
|
||||
return isAddressValid(address, WalletManager.getInstance().getNetworkType().getValue());
|
||||
fun setAccountIndex(accountIndex: Int) {
|
||||
Timber.d("setAccountIndex(%d)", accountIndex)
|
||||
this.accountIndex = accountIndex
|
||||
history?.setAccountFor(this)
|
||||
}
|
||||
|
||||
public static native boolean isAddressValid(String address, int networkType);
|
||||
val name: String
|
||||
get() = getPath()?.let { File(it).name }.toString()
|
||||
|
||||
public static native String getPaymentIdFromAddress(String address, int networkType);
|
||||
external fun getSeed(offset: String?): String?
|
||||
external fun getLegacySeed(offset: String?): String?
|
||||
external fun isPolyseedSupported(offset: String?): Boolean
|
||||
external fun getSeedLanguage(): String?
|
||||
|
||||
public static native long getMaximumAllowedAmount();
|
||||
|
||||
public int getAccountIndex() {
|
||||
return accountIndex;
|
||||
val status: Status
|
||||
get() = statusWithErrorString()
|
||||
val fullStatus: Status
|
||||
get() {
|
||||
val walletStatus = statusWithErrorString()
|
||||
walletStatus.connectionStatus = connectionStatus
|
||||
return walletStatus
|
||||
}
|
||||
|
||||
public void setAccountIndex(int accountIndex) {
|
||||
Timber.d("setAccountIndex(%d)", accountIndex);
|
||||
this.accountIndex = accountIndex;
|
||||
getHistory().setAccountFor(this);
|
||||
private external fun statusWithErrorString(): Status
|
||||
@Synchronized
|
||||
external fun setPassword(password: String?): Boolean
|
||||
val address: String
|
||||
get() = getAddress(accountIndex)
|
||||
|
||||
//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
|
||||
//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
|
||||
fun getAddress(accountIndex: Int): String {
|
||||
return getAddressJ(accountIndex, 0)
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return new File(getPath()).getName();
|
||||
private fun getSubaddress(addressIndex: Int): String {
|
||||
return getAddressJ(accountIndex, addressIndex)
|
||||
}
|
||||
|
||||
public native String getSeed(String offset);
|
||||
|
||||
public native String getLegacySeed(String offset);
|
||||
|
||||
public native boolean isPolyseedSupported(String offset);
|
||||
|
||||
public native String getSeedLanguage();
|
||||
|
||||
public native void setSeedLanguage(String language);
|
||||
|
||||
public Status getStatus() {
|
||||
return statusWithErrorString();
|
||||
fun getSubaddress(accountIndex: Int, addressIndex: Int): String {
|
||||
return getAddressJ(accountIndex, addressIndex)
|
||||
}
|
||||
|
||||
public Status getFullStatus() {
|
||||
Wallet.Status walletStatus = statusWithErrorString();
|
||||
walletStatus.setConnectionStatus(getConnectionStatus());
|
||||
return walletStatus;
|
||||
private external fun getAddressJ(accountIndex: Int, addressIndex: Int): String
|
||||
private fun getSubaddressObject(accountIndex: Int, subAddressIndex: Int): Subaddress {
|
||||
return Subaddress(
|
||||
accountIndex, subAddressIndex,
|
||||
getSubaddress(subAddressIndex), getSubaddressLabel(subAddressIndex)
|
||||
)
|
||||
}
|
||||
|
||||
private native Status statusWithErrorString();
|
||||
|
||||
public native synchronized boolean setPassword(String password);
|
||||
|
||||
public String getAddress() {
|
||||
return getAddress(accountIndex);
|
||||
}
|
||||
|
||||
//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
|
||||
//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
|
||||
|
||||
public String getAddress(int accountIndex) {
|
||||
return getAddressJ(accountIndex, 0);
|
||||
}
|
||||
|
||||
public String getSubaddress(int addressIndex) {
|
||||
return getAddressJ(accountIndex, addressIndex);
|
||||
}
|
||||
|
||||
public String getSubaddress(int accountIndex, int addressIndex) {
|
||||
return getAddressJ(accountIndex, addressIndex);
|
||||
}
|
||||
|
||||
private native String getAddressJ(int accountIndex, int addressIndex);
|
||||
|
||||
public Subaddress getSubaddressObject(int accountIndex, int subAddressIndex) {
|
||||
return new Subaddress(accountIndex, subAddressIndex,
|
||||
getSubaddress(subAddressIndex), getSubaddressLabel(subAddressIndex));
|
||||
}
|
||||
|
||||
public Subaddress getSubaddressObject(int subAddressIndex) {
|
||||
Subaddress subaddress = getSubaddressObject(accountIndex, subAddressIndex);
|
||||
long amount = 0;
|
||||
for (TransactionInfo info : getHistory().getAll()) {
|
||||
if ((info.addressIndex == subAddressIndex)
|
||||
&& (info.direction == TransactionInfo.Direction.Direction_In)) {
|
||||
amount += info.amount;
|
||||
fun getSubaddressObject(subAddressIndex: Int): Subaddress {
|
||||
val subaddress = getSubaddressObject(accountIndex, subAddressIndex)
|
||||
var amount: Long = 0
|
||||
history?.let { history ->
|
||||
for (info in history.all) {
|
||||
if (info.addressIndex == subAddressIndex && info.direction == TransactionInfo.Direction.Direction_In) {
|
||||
amount += info.amount
|
||||
}
|
||||
}
|
||||
subaddress.setAmount(amount);
|
||||
return subaddress;
|
||||
}
|
||||
|
||||
public native String getPath();
|
||||
subaddress.amount = amount
|
||||
return subaddress
|
||||
}
|
||||
external fun getPath(): String?
|
||||
val networkType: NetworkType?
|
||||
get() = fromInteger(nettype())
|
||||
|
||||
public NetworkType getNetworkType() {
|
||||
return NetworkType.fromInteger(nettype());
|
||||
external fun nettype(): Int
|
||||
|
||||
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
|
||||
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
|
||||
external fun getIntegratedAddress(paymentId: String?): String?
|
||||
external fun getSecretViewKey(): String
|
||||
external fun getSecretSpendKey(): String
|
||||
|
||||
fun store(): Boolean {
|
||||
return store("")
|
||||
}
|
||||
|
||||
public native int nettype();
|
||||
|
||||
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
|
||||
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
|
||||
|
||||
public native String getIntegratedAddress(String payment_id);
|
||||
|
||||
public native String getSecretViewKey();
|
||||
|
||||
// virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0;
|
||||
// virtual bool connectToDaemon() = 0;
|
||||
|
||||
public native String getSecretSpendKey();
|
||||
|
||||
public boolean store() {
|
||||
return store("");
|
||||
//TODO virtual void setTrustedDaemon(bool arg) = 0;
|
||||
//TODO virtual bool trustedDaemon() const = 0;
|
||||
@Synchronized
|
||||
external fun store(path: String?): Boolean
|
||||
fun close(): Boolean {
|
||||
disposePendingTransaction()
|
||||
return instance?.close(this) == true
|
||||
}
|
||||
|
||||
//TODO virtual void setTrustedDaemon(bool arg) = 0;
|
||||
//TODO virtual bool trustedDaemon() const = 0;
|
||||
|
||||
public native synchronized boolean store(String path);
|
||||
|
||||
public boolean close() {
|
||||
disposePendingTransaction();
|
||||
return WalletManager.getInstance().close(this);
|
||||
}
|
||||
|
||||
public native String getFilename();
|
||||
external fun getFilename(): String
|
||||
|
||||
// virtual std::string keysFilename() const = 0;
|
||||
public boolean init(long upper_transaction_size_limit) {
|
||||
String daemon_address = WalletManager.getInstance().getDaemonAddress();
|
||||
String daemon_username = WalletManager.getInstance().getDaemonUsername();
|
||||
String daemon_password = WalletManager.getInstance().getDaemonPassword();
|
||||
String proxy_address = WalletManager.getInstance().getProxy();
|
||||
Log.d("Wallet.java", "init(");
|
||||
if (daemon_address != null) {
|
||||
Log.d("Wallet.java", daemon_address.toString());
|
||||
fun init(upperTransactionSizeLimit: Long): Boolean {
|
||||
var daemonAddress = instance?.getDaemonAddress()
|
||||
var daemonUsername = instance?.daemonUsername
|
||||
var daemonPassword = instance?.daemonPassword
|
||||
var proxyAddress = instance?.proxy
|
||||
Timber.d("init(")
|
||||
if (daemonAddress != null) {
|
||||
Timber.d(daemonAddress.toString())
|
||||
} else {
|
||||
Log.d("Wallet.java", "daemon_address == null");
|
||||
daemon_address = "";
|
||||
Timber.d("daemon_address == null")
|
||||
daemonAddress = ""
|
||||
}
|
||||
Log.d("Wallet.java", "upper_transaction_size_limit = 0 (probably)");
|
||||
if (daemon_username != null) {
|
||||
Log.d("Wallet.java", daemon_username.toString());
|
||||
Timber.d("upper_transaction_size_limit = 0 (probably)")
|
||||
if (daemonUsername != null) {
|
||||
Timber.d(daemonUsername)
|
||||
} else {
|
||||
Log.d("Wallet.java", "daemon_username == null");
|
||||
daemon_username = "";
|
||||
Timber.d("daemon_username == null")
|
||||
daemonUsername = ""
|
||||
}
|
||||
if (daemon_password != null) {
|
||||
Log.d("Wallet.java", daemon_password.toString());
|
||||
if (daemonPassword != null) {
|
||||
Timber.d(daemonPassword)
|
||||
} else {
|
||||
Log.d("Wallet.java", "daemon_password == null");
|
||||
daemon_password = "";
|
||||
Timber.d("daemon_password == null")
|
||||
daemonPassword = ""
|
||||
}
|
||||
if (proxy_address != null) {
|
||||
Log.d("Wallet.java", proxy_address.toString());
|
||||
if (proxyAddress != null) {
|
||||
Timber.d(proxyAddress)
|
||||
} else {
|
||||
Log.d("Wallet.java", "proxy_address == null");
|
||||
proxy_address = "";
|
||||
Timber.d("proxy_address == null")
|
||||
proxyAddress = ""
|
||||
}
|
||||
Log.d("Wallet.java", ");");
|
||||
return initJ(daemon_address, upper_transaction_size_limit,
|
||||
daemon_username, daemon_password,
|
||||
proxy_address);
|
||||
Timber.d(");")
|
||||
return initJ(
|
||||
daemonAddress, upperTransactionSizeLimit,
|
||||
daemonUsername, daemonPassword,
|
||||
proxyAddress
|
||||
)
|
||||
}
|
||||
|
||||
private external fun initJ(
|
||||
daemonAddress: String, upperTransactionSizeLimit: Long,
|
||||
daemonUsername: String, daemonPassword: String, proxyAddress: String
|
||||
): Boolean
|
||||
|
||||
private native boolean initJ(String daemon_address, long upper_transaction_size_limit,
|
||||
String daemon_username, String daemon_password, String proxy);
|
||||
external fun getRestoreHeight(): Long
|
||||
external fun setRestoreHeight(height: Long)
|
||||
|
||||
public native long getRestoreHeight();
|
||||
|
||||
public native void setRestoreHeight(long height);
|
||||
|
||||
public ConnectionStatus getConnectionStatus() {
|
||||
int s = getConnectionStatusJ();
|
||||
return Wallet.ConnectionStatus.values()[s];
|
||||
private val connectionStatus: ConnectionStatus
|
||||
get() {
|
||||
val s = getConnectionStatusJ()
|
||||
return ConnectionStatus.values()[s]
|
||||
}
|
||||
|
||||
private native int getConnectionStatusJ();
|
||||
private external fun getConnectionStatusJ(): Int
|
||||
|
||||
public native boolean setProxy(String address);
|
||||
external fun setProxy(address: String?): Boolean
|
||||
val balance: Long
|
||||
get() = getBalance(accountIndex)
|
||||
|
||||
public long getBalance() {
|
||||
return getBalance(accountIndex);
|
||||
private external fun getBalance(accountIndex: Int): Long
|
||||
external fun getBalanceAll(): Long
|
||||
val unlockedBalance: Long
|
||||
get() = getUnlockedBalance(accountIndex)
|
||||
external fun getUnlockedBalanceAll(): Long
|
||||
|
||||
external fun getUnlockedBalance(accountIndex: Int): Long
|
||||
external fun isWatchOnly(): Boolean
|
||||
external fun getBlockChainHeight(): Long
|
||||
external fun getApproximateBlockChainHeight(): Long
|
||||
external fun getDaemonBlockChainHeight(): Long
|
||||
external fun getDaemonBlockChainTargetHeight(): Long
|
||||
|
||||
fun setSynchronized() {
|
||||
isSynchronized = true
|
||||
}
|
||||
|
||||
public native long getBalance(int accountIndex);
|
||||
|
||||
public native long getBalanceAll();
|
||||
|
||||
public long getUnlockedBalance() {
|
||||
return getUnlockedBalance(accountIndex);
|
||||
external fun startRefresh()
|
||||
external fun pauseRefresh()
|
||||
external fun refresh(): Boolean
|
||||
external fun refreshAsync()
|
||||
private external fun rescanBlockchainAsyncJ()
|
||||
fun rescanBlockchainAsync() {
|
||||
isSynchronized = false
|
||||
rescanBlockchainAsyncJ()
|
||||
}
|
||||
|
||||
public native long getUnlockedBalanceAll();
|
||||
|
||||
public native long getUnlockedBalance(int accountIndex);
|
||||
|
||||
public native boolean isWatchOnly();
|
||||
|
||||
public native long getBlockChainHeight();
|
||||
|
||||
public native long getApproximateBlockChainHeight();
|
||||
|
||||
public native long getDaemonBlockChainHeight();
|
||||
|
||||
public native long getDaemonBlockChainTargetHeight();
|
||||
|
||||
public boolean isSynchronized() {
|
||||
return synced;
|
||||
}
|
||||
|
||||
public void setSynchronized() {
|
||||
this.synced = true;
|
||||
}
|
||||
|
||||
public native void startRefresh();
|
||||
|
||||
public native void pauseRefresh();
|
||||
|
||||
public native boolean refresh();
|
||||
|
||||
public native void refreshAsync();
|
||||
|
||||
public native void rescanBlockchainAsyncJ();
|
||||
|
||||
public void rescanBlockchainAsync() {
|
||||
synced = false;
|
||||
rescanBlockchainAsyncJ();
|
||||
}
|
||||
|
||||
public PendingTransaction getPendingTransaction() {
|
||||
return pendingTransaction;
|
||||
}
|
||||
|
||||
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
|
||||
//TODO virtual int autoRefreshInterval() const = 0;
|
||||
|
||||
public void disposePendingTransaction() {
|
||||
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
|
||||
//TODO virtual int autoRefreshInterval() const = 0;
|
||||
private fun disposePendingTransaction() {
|
||||
if (pendingTransaction != null) {
|
||||
disposeTransaction(pendingTransaction);
|
||||
pendingTransaction = null;
|
||||
disposeTransaction(pendingTransaction)
|
||||
pendingTransaction = null
|
||||
}
|
||||
}
|
||||
|
||||
public long estimateTransactionFee(List<Pair<String, Long>> destinations, PendingTransaction.Priority priority) {
|
||||
int _priority = priority.getValue();
|
||||
return estimateTransactionFee(destinations, _priority);
|
||||
fun estimateTransactionFee(
|
||||
destinations: List<Pair<String, Long>>,
|
||||
priority: PendingTransaction.Priority
|
||||
): Long {
|
||||
val _priority = priority.value
|
||||
return estimateTransactionFee(destinations, _priority)
|
||||
}
|
||||
|
||||
private native long estimateTransactionFee(List<Pair<String, Long>> destinations, int priority);
|
||||
private external fun estimateTransactionFee(
|
||||
destinations: List<Pair<String, Long>>,
|
||||
priority: Int
|
||||
): Long
|
||||
|
||||
public PendingTransaction createSweepTransaction(String dst_addr, PendingTransaction.Priority priority, ArrayList<String> key_images) {
|
||||
disposePendingTransaction();
|
||||
int _priority = priority.getValue();
|
||||
long txHandle = createSweepTransaction(dst_addr, "", 0, _priority, accountIndex, key_images);
|
||||
pendingTransaction = new PendingTransaction(txHandle);
|
||||
return pendingTransaction;
|
||||
fun createSweepTransaction(
|
||||
dstAddr: String,
|
||||
priority: PendingTransaction.Priority,
|
||||
keyImages: ArrayList<String>
|
||||
): PendingTransaction? {
|
||||
disposePendingTransaction()
|
||||
val _priority = priority.value
|
||||
val txHandle = createSweepTransaction(dstAddr, "", 0, _priority, accountIndex, keyImages)
|
||||
pendingTransaction = PendingTransaction(txHandle)
|
||||
return pendingTransaction
|
||||
}
|
||||
|
||||
public PendingTransaction createTransactionMultDest(List<TransactionOutput> outputs, PendingTransaction.Priority priority, ArrayList<String> key_images) {
|
||||
disposePendingTransaction();
|
||||
int _priority = priority.getValue();
|
||||
ArrayList<String> destinations = new ArrayList<>();
|
||||
long[] amounts = new long[outputs.size()];
|
||||
for(int i = 0; i < outputs.size(); i++) {
|
||||
TransactionOutput output = outputs.get(i);
|
||||
destinations.add(output.getDestination());
|
||||
amounts[i] = output.getAmount();
|
||||
fun createTransactionMultDest(
|
||||
outputs: List<TransactionOutput>,
|
||||
priority: PendingTransaction.Priority,
|
||||
keyImages: ArrayList<String>
|
||||
): PendingTransaction? {
|
||||
disposePendingTransaction()
|
||||
val _priority = priority.value
|
||||
val destinations = ArrayList<String>()
|
||||
val amounts = LongArray(outputs.size)
|
||||
for (i in outputs.indices) {
|
||||
val output = outputs[i]
|
||||
destinations.add(output.destination)
|
||||
amounts[i] = output.amount
|
||||
}
|
||||
long txHandle = createTransactionMultDestJ(destinations, "", amounts, 0, _priority,
|
||||
accountIndex, key_images);
|
||||
pendingTransaction = new PendingTransaction(txHandle);
|
||||
return pendingTransaction;
|
||||
val txHandle = createTransactionMultDestJ(
|
||||
destinations, "", amounts, 0, _priority,
|
||||
accountIndex, keyImages
|
||||
)
|
||||
pendingTransaction = PendingTransaction(txHandle)
|
||||
return pendingTransaction
|
||||
}
|
||||
|
||||
private native long createTransactionMultDestJ(ArrayList<String> dst_addrs, String payment_id,
|
||||
long[] amount, int mixin_count,
|
||||
int priority, int accountIndex, ArrayList<String> key_images);
|
||||
private external fun createTransactionMultDestJ(
|
||||
dstAddrs: ArrayList<String>, paymentId: String,
|
||||
amount: LongArray, mixinCount: Int,
|
||||
priority: Int, accountIndex: Int, keyImages: ArrayList<String>
|
||||
): Long
|
||||
|
||||
private native long createTransactionJ(String dst_addr, String payment_id,
|
||||
long amount, int mixin_count,
|
||||
int priority, int accountIndex, ArrayList<String> key_images);
|
||||
private external fun createTransactionJ(
|
||||
dstAddr: String, paymentId: String,
|
||||
amount: Long, mixinCount: Int,
|
||||
priority: Int, accountIndex: Int, keyImages: ArrayList<String>
|
||||
): Long
|
||||
|
||||
private native long createSweepTransaction(String dst_addr, String payment_id,
|
||||
int mixin_count,
|
||||
int priority, int accountIndex, ArrayList<String> key_images);
|
||||
private external fun createSweepTransaction(
|
||||
dstAddr: String, paymentId: String,
|
||||
mixinCount: Int,
|
||||
priority: Int, accountIndex: Int, keyImages: ArrayList<String>
|
||||
): Long
|
||||
|
||||
public PendingTransaction createSweepUnmixableTransaction() {
|
||||
disposePendingTransaction();
|
||||
long txHandle = createSweepUnmixableTransactionJ();
|
||||
pendingTransaction = new PendingTransaction(txHandle);
|
||||
return pendingTransaction;
|
||||
fun createSweepUnmixableTransaction(): PendingTransaction? {
|
||||
disposePendingTransaction()
|
||||
val txHandle = createSweepUnmixableTransactionJ()
|
||||
pendingTransaction = PendingTransaction(txHandle)
|
||||
return pendingTransaction
|
||||
}
|
||||
|
||||
private native long createSweepUnmixableTransactionJ();
|
||||
private external fun createSweepUnmixableTransactionJ(): Long
|
||||
private external fun disposeTransaction(pendingTransaction: PendingTransaction?)
|
||||
private external fun getHistoryJ(): Long
|
||||
private external fun getCoinsJ(): Long
|
||||
|
||||
public native void disposeTransaction(PendingTransaction pendingTransaction);
|
||||
|
||||
public TransactionHistory getHistory() {
|
||||
if (history == null) {
|
||||
history = new TransactionHistory(getHistoryJ(), accountIndex);
|
||||
}
|
||||
return history;
|
||||
//virtual bool exportKeyImages(const std::string &filename) = 0;
|
||||
//virtual bool importKeyImages(const std::string &filename) = 0;
|
||||
//virtual TransactionHistory * history() const = 0;
|
||||
fun refreshHistory() {
|
||||
history?.refreshWithNotes(this)
|
||||
}
|
||||
|
||||
//virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0;
|
||||
//virtual bool submitTransaction(const std::string &fileName) = 0;
|
||||
|
||||
private native long getHistoryJ();
|
||||
|
||||
public Coins getCoins() {
|
||||
if (coins == null) {
|
||||
coins = new Coins(getCoinsJ());
|
||||
}
|
||||
return coins;
|
||||
}
|
||||
|
||||
private native long getCoinsJ();
|
||||
|
||||
//virtual bool exportKeyImages(const std::string &filename) = 0;
|
||||
//virtual bool importKeyImages(const std::string &filename) = 0;
|
||||
|
||||
|
||||
//virtual TransactionHistory * history() const = 0;
|
||||
|
||||
public void refreshHistory() {
|
||||
getHistory().refreshWithNotes(this);
|
||||
}
|
||||
|
||||
public void refreshCoins() {
|
||||
if (this.isSynchronized()) {
|
||||
getCoins().refresh();
|
||||
fun refreshCoins() {
|
||||
if (isSynchronized) {
|
||||
coins?.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
private native long setListenerJ(WalletListener listener);
|
||||
|
||||
public void setListener(WalletListener listener) {
|
||||
this.listenerHandle = setListenerJ(listener);
|
||||
private external fun setListenerJ(listener: WalletListener): Long
|
||||
fun setListener(listener: WalletListener) {
|
||||
listenerHandle = setListenerJ(listener)
|
||||
}
|
||||
|
||||
public native int getDefaultMixin();
|
||||
|
||||
//virtual AddressBook * addressBook() const = 0;
|
||||
//virtual void setListener(WalletListener *) = 0;
|
||||
|
||||
public native void setDefaultMixin(int mixin);
|
||||
|
||||
public native boolean setUserNote(String txid, String note);
|
||||
|
||||
public native String getUserNote(String txid);
|
||||
|
||||
public native String getTxKey(String txid);
|
||||
|
||||
public void addAccount() {
|
||||
addAccount(NEW_ACCOUNT_NAME);
|
||||
external fun getDefaultMixin(): Int
|
||||
external fun setDefaultMixin(mixin: Int)
|
||||
external fun setUserNote(txid: String?, note: String?): Boolean
|
||||
external fun getUserNote(txid: String?): String?
|
||||
external fun getTxKey(txid: String?): String?
|
||||
@JvmOverloads
|
||||
external fun addAccount(label: String? = NEW_ACCOUNT_NAME)
|
||||
var accountLabel: String?
|
||||
get() = getAccountLabel(accountIndex)
|
||||
//virtual std::string signMessage(const std::string &message) = 0;
|
||||
set(label) {
|
||||
setAccountLabel(accountIndex, label)
|
||||
}
|
||||
|
||||
public native void addAccount(String label);
|
||||
|
||||
public String getAccountLabel() {
|
||||
return getAccountLabel(accountIndex);
|
||||
}
|
||||
|
||||
//virtual std::string signMessage(const std::string &message) = 0;
|
||||
//virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0;
|
||||
|
||||
//virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0;
|
||||
//virtual bool rescanSpent() = 0;
|
||||
|
||||
public void setAccountLabel(String label) {
|
||||
setAccountLabel(accountIndex, label);
|
||||
}
|
||||
|
||||
public String getAccountLabel(int accountIndex) {
|
||||
String label = getSubaddressLabel(accountIndex, 0);
|
||||
if (label.equals(NEW_ACCOUNT_NAME)) {
|
||||
String address = getAddress(accountIndex);
|
||||
int len = address.length();
|
||||
private fun getAccountLabel(accountIndex: Int): String {
|
||||
var label = getSubaddressLabel(accountIndex, 0)
|
||||
if (label == NEW_ACCOUNT_NAME) {
|
||||
val address = getAddress(accountIndex)
|
||||
val len = address.length
|
||||
label = address.substring(0, 6) +
|
||||
"\u2026" + address.substring(len - 6, len);
|
||||
"\u2026" + address.substring(len - 6, len)
|
||||
}
|
||||
return label;
|
||||
return label
|
||||
}
|
||||
|
||||
public String getSubaddressLabel(int addressIndex) {
|
||||
return getSubaddressLabel(accountIndex, addressIndex);
|
||||
fun getSubaddressLabel(addressIndex: Int): String {
|
||||
return getSubaddressLabel(accountIndex, addressIndex)
|
||||
}
|
||||
|
||||
public native String getSubaddressLabel(int accountIndex, int addressIndex);
|
||||
|
||||
public void setAccountLabel(int accountIndex, String label) {
|
||||
setSubaddressLabel(accountIndex, 0, label);
|
||||
private external fun getSubaddressLabel(accountIndex: Int, addressIndex: Int): String
|
||||
private fun setAccountLabel(accountIndex: Int, label: String?) {
|
||||
setSubaddressLabel(accountIndex, 0, label)
|
||||
}
|
||||
|
||||
public void setSubaddressLabel(int addressIndex, String label) {
|
||||
setSubaddressLabel(accountIndex, addressIndex, label);
|
||||
refreshHistory();
|
||||
fun setSubaddressLabel(addressIndex: Int, label: String?) {
|
||||
setSubaddressLabel(accountIndex, addressIndex, label)
|
||||
refreshHistory()
|
||||
}
|
||||
|
||||
public native void setSubaddressLabel(int accountIndex, int addressIndex, String label);
|
||||
private external fun setSubaddressLabel(accountIndex: Int, addressIndex: Int, label: String?)
|
||||
external fun getNumAccounts(): Int
|
||||
val numSubaddresses: Int
|
||||
get() = getNumSubaddresses(accountIndex)
|
||||
|
||||
public native int getNumAccounts();
|
||||
private external fun getNumSubaddresses(accountIndex: Int): Int
|
||||
val newSubaddress: String
|
||||
get() = getNewSubaddress(accountIndex)
|
||||
|
||||
public int getNumSubaddresses() {
|
||||
return getNumSubaddresses(accountIndex);
|
||||
private fun getNewSubaddress(accountIndex: Int): String {
|
||||
val timeStamp = SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).format(Date())
|
||||
addSubaddress(accountIndex, timeStamp)
|
||||
val subaddress = getLastSubaddress(accountIndex)
|
||||
Timber.d("%d: %s", getNumSubaddresses(accountIndex) - 1, subaddress)
|
||||
return subaddress
|
||||
}
|
||||
|
||||
public native int getNumSubaddresses(int accountIndex);
|
||||
|
||||
public String getNewSubaddress() {
|
||||
return getNewSubaddress(accountIndex);
|
||||
external fun addSubaddress(accountIndex: Int, label: String?)
|
||||
private fun getLastSubaddress(accountIndex: Int): String {
|
||||
return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1)
|
||||
}
|
||||
|
||||
public String getNewSubaddress(int accountIndex) {
|
||||
String timeStamp = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).format(new Date());
|
||||
addSubaddress(accountIndex, timeStamp);
|
||||
String subaddress = getLastSubaddress(accountIndex);
|
||||
Timber.d("%d: %s", getNumSubaddresses(accountIndex) - 1, subaddress);
|
||||
return subaddress;
|
||||
val deviceType: Device
|
||||
get() {
|
||||
val device = getDeviceTypeJ()
|
||||
return Device.values()[device + 1] // mapping is monero+1=android
|
||||
}
|
||||
|
||||
public native void addSubaddress(int accountIndex, String label);
|
||||
private external fun getDeviceTypeJ(): Int
|
||||
|
||||
enum class Device(val accountLookahead: Int, val subaddressLookahead: Int) {
|
||||
Device_Undefined(0, 0), Device_Software(50, 200), Device_Ledger(5, 20)
|
||||
|
||||
public String getLastSubaddress(int accountIndex) {
|
||||
return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1);
|
||||
}
|
||||
|
||||
public Wallet.Device getDeviceType() {
|
||||
int device = getDeviceTypeJ();
|
||||
return Wallet.Device.values()[device + 1]; // mapping is monero+1=android
|
||||
enum class StatusEnum {
|
||||
Status_Ok, Status_Error, Status_Critical
|
||||
}
|
||||
|
||||
private native int getDeviceTypeJ();
|
||||
|
||||
public enum Device {
|
||||
Device_Undefined(0, 0),
|
||||
Device_Software(50, 200),
|
||||
Device_Ledger(5, 20);
|
||||
private final int accountLookahead;
|
||||
private final int subaddressLookahead;
|
||||
|
||||
Device(int accountLookahead, int subaddressLookahead) {
|
||||
this.accountLookahead = accountLookahead;
|
||||
this.subaddressLookahead = subaddressLookahead;
|
||||
enum class ConnectionStatus {
|
||||
ConnectionStatus_Disconnected, ConnectionStatus_Connected, ConnectionStatus_WrongVersion
|
||||
}
|
||||
|
||||
public int getAccountLookahead() {
|
||||
return accountLookahead;
|
||||
class Status internal constructor(status: Int, @JvmField val errorString: String) {
|
||||
val status: StatusEnum
|
||||
@JvmField
|
||||
var connectionStatus: ConnectionStatus? = null // optional
|
||||
|
||||
init {
|
||||
this.status = StatusEnum.values()[status]
|
||||
}
|
||||
|
||||
public int getSubaddressLookahead() {
|
||||
return subaddressLookahead;
|
||||
val isOk: Boolean
|
||||
get() = (status == StatusEnum.Status_Ok
|
||||
&& (connectionStatus == null || connectionStatus == ConnectionStatus.ConnectionStatus_Connected))
|
||||
|
||||
override fun toString(): String {
|
||||
return "Wallet.Status: $status/$errorString/$connectionStatus"
|
||||
}
|
||||
}
|
||||
|
||||
public enum StatusEnum {
|
||||
Status_Ok,
|
||||
Status_Error,
|
||||
Status_Critical
|
||||
companion object {
|
||||
const val SWEEP_ALL = Long.MAX_VALUE
|
||||
private const val NEW_ACCOUNT_NAME = "Untitled account" // src/wallet/wallet2.cpp:941
|
||||
|
||||
init {
|
||||
System.loadLibrary("monerujo")
|
||||
}
|
||||
|
||||
public enum ConnectionStatus {
|
||||
ConnectionStatus_Disconnected,
|
||||
ConnectionStatus_Connected,
|
||||
ConnectionStatus_WrongVersion
|
||||
@JvmStatic
|
||||
external fun getDisplayAmount(amount: Long): String
|
||||
@JvmStatic
|
||||
external fun getAmountFromString(amount: String?): Long
|
||||
@JvmStatic
|
||||
external fun getAmountFromDouble(amount: Double): Long
|
||||
@JvmStatic
|
||||
external fun generatePaymentId(): String
|
||||
@JvmStatic
|
||||
external fun isPaymentIdValid(payment_id: String): Boolean
|
||||
@JvmStatic
|
||||
fun isAddressValid(address: String): Boolean {
|
||||
return instance?.networkType?.value?.let { isAddressValid(address, it) } == true
|
||||
}
|
||||
|
||||
static public class Status {
|
||||
final private StatusEnum status;
|
||||
final private String errorString;
|
||||
@Nullable
|
||||
private ConnectionStatus connectionStatus; // optional
|
||||
|
||||
Status(int status, String errorString) {
|
||||
this.status = StatusEnum.values()[status];
|
||||
this.errorString = errorString;
|
||||
@JvmStatic
|
||||
external fun isAddressValid(address: String?, networkType: Int): Boolean
|
||||
@JvmStatic
|
||||
external fun getPaymentIdFromAddress(address: String?, networkType: Int): String?
|
||||
@JvmStatic
|
||||
external fun getMaximumAllowedAmount(): Long
|
||||
}
|
||||
|
||||
public StatusEnum getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getErrorString() {
|
||||
return errorString;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ConnectionStatus getConnectionStatus() {
|
||||
return connectionStatus;
|
||||
}
|
||||
|
||||
public void setConnectionStatus(@Nullable ConnectionStatus connectionStatus) {
|
||||
this.connectionStatus = connectionStatus;
|
||||
}
|
||||
|
||||
public boolean isOk() {
|
||||
return (getStatus() == StatusEnum.Status_Ok)
|
||||
&& ((getConnectionStatus() == null) ||
|
||||
(getConnectionStatus() == ConnectionStatus.ConnectionStatus_Connected));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return "Wallet.Status: " + status + "/" + errorString + "/" + connectionStatus;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -13,17 +13,16 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.model
|
||||
|
||||
package net.mynero.wallet.model;
|
||||
|
||||
public interface WalletListener {
|
||||
interface WalletListener {
|
||||
/**
|
||||
* moneySpent - called when money spent
|
||||
*
|
||||
* @param txId - transaction id
|
||||
* @param amount - tvAmount
|
||||
*/
|
||||
void moneySpent(String txId, long amount);
|
||||
fun moneySpent(txId: String?, amount: Long)
|
||||
|
||||
/**
|
||||
* moneyReceived - called when money received
|
||||
|
@ -31,7 +30,7 @@ public interface WalletListener {
|
|||
* @param txId - transaction id
|
||||
* @param amount - tvAmount
|
||||
*/
|
||||
void moneyReceived(String txId, long amount);
|
||||
fun moneyReceived(txId: String?, amount: Long)
|
||||
|
||||
/**
|
||||
* unconfirmedMoneyReceived - called when payment arrived in tx pool
|
||||
|
@ -39,23 +38,22 @@ public interface WalletListener {
|
|||
* @param txId - transaction id
|
||||
* @param amount - tvAmount
|
||||
*/
|
||||
void unconfirmedMoneyReceived(String txId, long amount);
|
||||
fun unconfirmedMoneyReceived(txId: String?, amount: Long)
|
||||
|
||||
/**
|
||||
* newBlock - called when new block received
|
||||
*
|
||||
* @param height - block height
|
||||
*/
|
||||
void newBlock(long height);
|
||||
fun newBlock(height: Long)
|
||||
|
||||
/**
|
||||
* updated - generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet;
|
||||
*/
|
||||
void updated();
|
||||
fun updated()
|
||||
|
||||
/**
|
||||
* refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously
|
||||
*/
|
||||
void refreshed();
|
||||
|
||||
fun refreshed()
|
||||
}
|
|
@ -1,366 +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 net.mynero.wallet.model;
|
||||
|
||||
import net.mynero.wallet.data.Node;
|
||||
import net.mynero.wallet.util.RestoreHeight;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class WalletManager {
|
||||
|
||||
//TODO: maybe put these in an enum like in monero core - but why?
|
||||
static public int LOGLEVEL_SILENT = -1;
|
||||
static public int LOGLEVEL_WARN = 0;
|
||||
static public int LOGLEVEL_INFO = 1;
|
||||
static public int LOGLEVEL_DEBUG = 2;
|
||||
static public int LOGLEVEL_TRACE = 3;
|
||||
static public int LOGLEVEL_MAX = 4;
|
||||
// no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it)
|
||||
private static WalletManager Instance = null;
|
||||
|
||||
static {
|
||||
System.loadLibrary("monerujo");
|
||||
}
|
||||
|
||||
private final NetworkType networkType = NetworkType.NetworkType_Mainnet;
|
||||
private Wallet managedWallet = null;
|
||||
private String daemonAddress = null;
|
||||
private String daemonUsername = "";
|
||||
private String daemonPassword = "";
|
||||
private String proxy = "";
|
||||
|
||||
public static synchronized WalletManager getInstance() {
|
||||
if (WalletManager.Instance == null) {
|
||||
WalletManager.Instance = new WalletManager();
|
||||
}
|
||||
|
||||
return WalletManager.Instance;
|
||||
}
|
||||
|
||||
static public String addressPrefix(NetworkType networkType) {
|
||||
switch (networkType) {
|
||||
case NetworkType_Testnet:
|
||||
return "9A-";
|
||||
case NetworkType_Mainnet:
|
||||
return "4-";
|
||||
case NetworkType_Stagenet:
|
||||
return "5-";
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported Network: " + networkType);
|
||||
}
|
||||
}
|
||||
|
||||
static public native void initLogger(String argv0, String defaultLogBaseName);
|
||||
|
||||
static public native void setLogLevel(int level);
|
||||
|
||||
static public native void logDebug(String category, String message);
|
||||
|
||||
static public native void logInfo(String category, String message);
|
||||
|
||||
static public native void logWarning(String category, String message);
|
||||
|
||||
static public native void logError(String category, String message);
|
||||
|
||||
static public native String moneroVersion();
|
||||
|
||||
public String addressPrefix() {
|
||||
return addressPrefix(getNetworkType());
|
||||
}
|
||||
|
||||
public Wallet getWallet() {
|
||||
return managedWallet;
|
||||
}
|
||||
|
||||
private void manageWallet(Wallet wallet) {
|
||||
Timber.d("Managing %s", wallet.getName());
|
||||
managedWallet = wallet;
|
||||
}
|
||||
|
||||
private void unmanageWallet(Wallet wallet) {
|
||||
if (wallet == null) {
|
||||
throw new IllegalArgumentException("Cannot unmanage null!");
|
||||
}
|
||||
if (getWallet() == null) {
|
||||
throw new IllegalStateException("No wallet under management!");
|
||||
}
|
||||
if (getWallet() != wallet) {
|
||||
throw new IllegalStateException(wallet.getName() + " not under management!");
|
||||
}
|
||||
Timber.d("Unmanaging %s", managedWallet.getName());
|
||||
managedWallet = null;
|
||||
}
|
||||
|
||||
public Wallet createWallet(File aFile, String password, String language, long height) {
|
||||
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, getNetworkType().getValue());
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet);
|
||||
if (wallet.getStatus().isOk()) {
|
||||
// (Re-)Estimate restore height based on what we know
|
||||
final long oldHeight = wallet.getRestoreHeight();
|
||||
// Go back 4 days if we don't have a precise restore height
|
||||
Calendar restoreDate = Calendar.getInstance();
|
||||
restoreDate.add(Calendar.DAY_OF_MONTH, 0);
|
||||
final long restoreHeight =
|
||||
(height > -1) ? height : RestoreHeight.getInstance().getHeight(restoreDate.getTime());
|
||||
wallet.setRestoreHeight(restoreHeight);
|
||||
Timber.d("Changed Restore Height from %d to %d", oldHeight, wallet.getRestoreHeight());
|
||||
wallet.setPassword(password); // this rewrites the keys file (which contains the restore height)
|
||||
} else
|
||||
Timber.e(wallet.getStatus().toString());
|
||||
return wallet;
|
||||
}
|
||||
|
||||
//public native List<String> findWallets(String path); // this does not work - some error in boost
|
||||
|
||||
private native long createWalletJ(String path, String password, String language, int networkType);
|
||||
|
||||
public Wallet createWalletPolyseed(File aFile, String password, String passphrase, String language) {
|
||||
long walletHandle = createWalletPolyseedJ(aFile.getAbsolutePath(), password, passphrase, language, getNetworkType().getValue());
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet);
|
||||
if (wallet.getStatus().isOk()) {
|
||||
wallet.setPassword(password); // this rewrites the keys file (which contains the restore height)
|
||||
} else
|
||||
Timber.e(wallet.getStatus().toString());
|
||||
return wallet;
|
||||
}
|
||||
private native long createWalletPolyseedJ(String path, String password, String passphrase, String language, int networkType);
|
||||
|
||||
public Wallet openAccount(String path, int accountIndex, String password) {
|
||||
long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
|
||||
Wallet wallet = new Wallet(walletHandle, accountIndex);
|
||||
manageWallet(wallet);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
|
||||
|
||||
public Wallet openWallet(String path, String password) {
|
||||
long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
private native long openWalletJ(String path, String password, int networkType);
|
||||
|
||||
public Wallet recoveryWallet(File aFile, String password,
|
||||
String mnemonic, String offset,
|
||||
long restoreHeight) {
|
||||
long walletHandle = recoveryWalletJ(aFile.getAbsolutePath(), password,
|
||||
mnemonic, offset,
|
||||
getNetworkType().getValue(), restoreHeight);
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
private native long recoveryWalletJ(String path, String password,
|
||||
String mnemonic, String offset,
|
||||
int networkType, long restoreHeight);
|
||||
|
||||
public Wallet recoveryWalletPolyseed(File aFile, String password,
|
||||
String mnemonic, String offset) {
|
||||
long walletHandle = recoveryWalletPolyseedJ(aFile.getAbsolutePath(), password,
|
||||
mnemonic, offset,
|
||||
getNetworkType().getValue());
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
private native long recoveryWalletPolyseedJ(String path, String password,
|
||||
String mnemonic, String offset,
|
||||
int networkType);
|
||||
|
||||
public Wallet createWalletWithKeys(File aFile, String password, String language, long restoreHeight,
|
||||
String addressString, String viewKeyString, String spendKeyString) {
|
||||
long walletHandle = createWalletFromKeysJ(aFile.getAbsolutePath(), password,
|
||||
language, getNetworkType().getValue(), restoreHeight,
|
||||
addressString, viewKeyString, spendKeyString);
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
private native long createWalletFromKeysJ(String path, String password,
|
||||
String language,
|
||||
int networkType,
|
||||
long restoreHeight,
|
||||
String addressString,
|
||||
String viewKeyString,
|
||||
String spendKeyString);
|
||||
|
||||
public Wallet createWalletFromDevice(File aFile, String password, long restoreHeight,
|
||||
String deviceName) {
|
||||
long walletHandle = createWalletFromDeviceJ(aFile.getAbsolutePath(), password,
|
||||
getNetworkType().getValue(), deviceName, restoreHeight,
|
||||
"5:20");
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
private native long createWalletFromDeviceJ(String path, String password,
|
||||
int networkType,
|
||||
String deviceName,
|
||||
long restoreHeight,
|
||||
String subaddressLookahead);
|
||||
|
||||
public native boolean closeJ(Wallet wallet);
|
||||
|
||||
public boolean close(Wallet wallet) {
|
||||
unmanageWallet(wallet);
|
||||
boolean closed = closeJ(wallet);
|
||||
if (!closed) {
|
||||
// in case we could not close it
|
||||
// we manage it again
|
||||
manageWallet(wallet);
|
||||
}
|
||||
return closed;
|
||||
}
|
||||
|
||||
public boolean walletExists(File aFile) {
|
||||
return walletExists(aFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
public native boolean walletExists(String path);
|
||||
|
||||
public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
|
||||
|
||||
public boolean verifyWalletPasswordOnly(String keys_file_name, String password) {
|
||||
return queryWalletDeviceJ(keys_file_name, password) >= 0;
|
||||
}
|
||||
|
||||
public Wallet.Device queryWalletDevice(String keys_file_name, String password) {
|
||||
int device = queryWalletDeviceJ(keys_file_name, password);
|
||||
return Wallet.Device.values()[device + 1]; // mapping is monero+1=android
|
||||
}
|
||||
|
||||
private native int queryWalletDeviceJ(String keys_file_name, String password);
|
||||
|
||||
public List<WalletInfo> findWallets(File path) {
|
||||
List<WalletInfo> wallets = new ArrayList<>();
|
||||
Timber.d("Scanning: %s", path.getAbsolutePath());
|
||||
File[] found = path.listFiles(new FilenameFilter() {
|
||||
public boolean accept(File dir, String filename) {
|
||||
return filename.endsWith(".keys");
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < found.length; i++) {
|
||||
String filename = found[i].getName();
|
||||
File f = new File(found[i].getParent(), filename.substring(0, filename.length() - 5)); // 5 is length of ".keys"+1
|
||||
wallets.add(new WalletInfo(f));
|
||||
}
|
||||
return wallets;
|
||||
}
|
||||
|
||||
public NetworkType getNetworkType() {
|
||||
return networkType;
|
||||
}
|
||||
|
||||
// this should not be called on the main thread as it connects to the node (and takes a long time)
|
||||
public void setDaemon(Node node) {
|
||||
if (node != null) {
|
||||
this.daemonAddress = node.getAddress();
|
||||
if (networkType != node.getNetworkType())
|
||||
throw new IllegalArgumentException("network type does not match");
|
||||
this.daemonUsername = node.getUsername();
|
||||
this.daemonPassword = node.getPassword();
|
||||
setDaemonAddressJ(daemonAddress);
|
||||
} else {
|
||||
this.daemonAddress = null;
|
||||
this.daemonUsername = "";
|
||||
this.daemonPassword = "";
|
||||
//setDaemonAddressJ(""); // don't disconnect as monero code blocks for many seconds!
|
||||
//TODO: need to do something about that later
|
||||
}
|
||||
}
|
||||
|
||||
public String getDaemonAddress() {
|
||||
if (daemonAddress == null) {
|
||||
throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
|
||||
}
|
||||
return this.daemonAddress;
|
||||
}
|
||||
|
||||
private native void setDaemonAddressJ(String address);
|
||||
|
||||
public String getDaemonUsername() {
|
||||
return daemonUsername;
|
||||
}
|
||||
|
||||
public String getDaemonPassword() {
|
||||
return daemonPassword;
|
||||
}
|
||||
|
||||
public native int getDaemonVersion();
|
||||
|
||||
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
|
||||
|
||||
public native long getBlockchainHeight();
|
||||
|
||||
public native long getBlockchainTargetHeight();
|
||||
|
||||
public native long getNetworkDifficulty();
|
||||
|
||||
public native double getMiningHashRate();
|
||||
|
||||
public native long getBlockTarget();
|
||||
|
||||
public native boolean isMining();
|
||||
|
||||
public native boolean startMining(String address, boolean background_mining, boolean ignore_battery);
|
||||
|
||||
public native boolean stopMining();
|
||||
|
||||
public native String resolveOpenAlias(String address, boolean dnssec_valid);
|
||||
|
||||
public String getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
public boolean setProxy(String address) {
|
||||
this.proxy = address;
|
||||
return setProxyJ(address);
|
||||
}
|
||||
|
||||
public native boolean setProxyJ(String address);
|
||||
|
||||
public class WalletInfo implements Comparable<WalletInfo> {
|
||||
final private File path;
|
||||
final private String name;
|
||||
|
||||
public WalletInfo(File wallet) {
|
||||
path = wallet.getParentFile();
|
||||
name = wallet.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(WalletInfo another) {
|
||||
return name.toLowerCase().compareTo(another.name.toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
370
app/src/main/java/net/mynero/wallet/model/WalletManager.kt
Normal file
370
app/src/main/java/net/mynero/wallet/model/WalletManager.kt
Normal file
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* 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 net.mynero.wallet.model
|
||||
|
||||
import net.mynero.wallet.data.Node
|
||||
import net.mynero.wallet.util.RestoreHeight
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
class WalletManager {
|
||||
@JvmField
|
||||
val networkType = NetworkType.NetworkType_Mainnet
|
||||
var wallet: Wallet? = null
|
||||
private set
|
||||
private var daemonAddress: String? = null
|
||||
var daemonUsername = ""
|
||||
private set
|
||||
var daemonPassword = ""
|
||||
private set
|
||||
var proxy = ""
|
||||
private set
|
||||
|
||||
private fun manageWallet(wallet: Wallet) {
|
||||
Timber.d("Managing %s", wallet.name)
|
||||
this.wallet = wallet
|
||||
}
|
||||
|
||||
private fun unmanageWallet(wallet: Wallet?) {
|
||||
requireNotNull(wallet) { "Cannot unmanage null!" }
|
||||
checkNotNull(this.wallet) { "No wallet under management!" }
|
||||
check(this.wallet === wallet) { wallet.name + " not under management!" }
|
||||
Timber.d("Unmanaging %s", wallet.name)
|
||||
this.wallet = null
|
||||
}
|
||||
|
||||
fun createWallet(aFile: File, password: String, language: String, height: Long): Wallet {
|
||||
val walletHandle = createWalletJ(aFile.absolutePath, password, language, networkType.value)
|
||||
val wallet = Wallet(walletHandle)
|
||||
manageWallet(wallet)
|
||||
if (wallet.status.isOk) {
|
||||
// (Re-)Estimate restore height based on what we know
|
||||
val oldHeight = wallet.getRestoreHeight()
|
||||
// Go back 4 days if we don't have a precise restore height
|
||||
val restoreDate = Calendar.getInstance()
|
||||
restoreDate.add(Calendar.DAY_OF_MONTH, 0)
|
||||
val restoreHeight =
|
||||
if (height > -1) height else RestoreHeight.instance?.getHeight(restoreDate.time)
|
||||
if (restoreHeight != null) {
|
||||
wallet.setRestoreHeight(restoreHeight)
|
||||
}
|
||||
Timber.d("Changed Restore Height from %d to %d", oldHeight, wallet.getRestoreHeight())
|
||||
wallet.setPassword(password) // this rewrites the keys file (which contains the restore height)
|
||||
} else Timber.e(wallet.status.toString())
|
||||
return wallet
|
||||
}
|
||||
|
||||
//public native List<String> findWallets(String path); // this does not work - some error in boost
|
||||
private external fun createWalletJ(
|
||||
path: String,
|
||||
password: String,
|
||||
language: String,
|
||||
networkType: Int
|
||||
): Long
|
||||
|
||||
fun createWalletPolyseed(
|
||||
aFile: File,
|
||||
password: String,
|
||||
passphrase: String,
|
||||
language: String
|
||||
): Wallet {
|
||||
val walletHandle = createWalletPolyseedJ(
|
||||
aFile.absolutePath,
|
||||
password,
|
||||
passphrase,
|
||||
language,
|
||||
networkType.value
|
||||
)
|
||||
val wallet = Wallet(walletHandle)
|
||||
manageWallet(wallet)
|
||||
if (wallet.status.isOk) {
|
||||
wallet.setPassword(password) // this rewrites the keys file (which contains the restore height)
|
||||
} else Timber.e(wallet.status.toString())
|
||||
return wallet
|
||||
}
|
||||
|
||||
private external fun createWalletPolyseedJ(
|
||||
path: String,
|
||||
password: String,
|
||||
passphrase: String,
|
||||
language: String,
|
||||
networkType: Int
|
||||
): Long
|
||||
|
||||
fun openAccount(path: String, accountIndex: Int, password: String): Wallet {
|
||||
val walletHandle = openWalletJ(path, password, networkType.value)
|
||||
val wallet = Wallet(walletHandle, accountIndex)
|
||||
manageWallet(wallet)
|
||||
return wallet
|
||||
}
|
||||
|
||||
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
|
||||
fun openWallet(path: String, password: String): Wallet {
|
||||
val walletHandle = openWalletJ(path, password, networkType.value)
|
||||
val wallet = Wallet(walletHandle)
|
||||
manageWallet(wallet)
|
||||
return wallet
|
||||
}
|
||||
|
||||
private external fun openWalletJ(path: String, password: String, networkType: Int): Long
|
||||
fun recoveryWallet(
|
||||
aFile: File, password: String,
|
||||
mnemonic: String, offset: String,
|
||||
restoreHeight: Long
|
||||
): Wallet {
|
||||
val walletHandle = recoveryWalletJ(
|
||||
aFile.absolutePath, password,
|
||||
mnemonic, offset,
|
||||
networkType.value, restoreHeight
|
||||
)
|
||||
val wallet = Wallet(walletHandle)
|
||||
manageWallet(wallet)
|
||||
return wallet
|
||||
}
|
||||
|
||||
private external fun recoveryWalletJ(
|
||||
path: String, password: String,
|
||||
mnemonic: String, offset: String,
|
||||
networkType: Int, restoreHeight: Long
|
||||
): Long
|
||||
|
||||
fun recoveryWalletPolyseed(
|
||||
aFile: File, password: String,
|
||||
mnemonic: String, offset: String
|
||||
): Wallet {
|
||||
val walletHandle = recoveryWalletPolyseedJ(
|
||||
aFile.absolutePath, password,
|
||||
mnemonic, offset,
|
||||
networkType.value
|
||||
)
|
||||
val wallet = Wallet(walletHandle)
|
||||
manageWallet(wallet)
|
||||
return wallet
|
||||
}
|
||||
|
||||
private external fun recoveryWalletPolyseedJ(
|
||||
path: String, password: String,
|
||||
mnemonic: String, offset: String,
|
||||
networkType: Int
|
||||
): Long
|
||||
|
||||
fun createWalletWithKeys(
|
||||
aFile: File, password: String, language: String, restoreHeight: Long,
|
||||
addressString: String, viewKeyString: String, spendKeyString: String
|
||||
): Wallet {
|
||||
val walletHandle = createWalletFromKeysJ(
|
||||
aFile.absolutePath, password,
|
||||
language, networkType.value, restoreHeight,
|
||||
addressString, viewKeyString, spendKeyString
|
||||
)
|
||||
val wallet = Wallet(walletHandle)
|
||||
manageWallet(wallet)
|
||||
return wallet
|
||||
}
|
||||
|
||||
private external fun createWalletFromKeysJ(
|
||||
path: String, password: String,
|
||||
language: String,
|
||||
networkType: Int,
|
||||
restoreHeight: Long,
|
||||
addressString: String,
|
||||
viewKeyString: String,
|
||||
spendKeyString: String
|
||||
): Long
|
||||
|
||||
fun createWalletFromDevice(
|
||||
aFile: File, password: String, restoreHeight: Long,
|
||||
deviceName: String
|
||||
): Wallet {
|
||||
val walletHandle = createWalletFromDeviceJ(
|
||||
aFile.absolutePath, password,
|
||||
networkType.value, deviceName, restoreHeight,
|
||||
"5:20"
|
||||
)
|
||||
val wallet = Wallet(walletHandle)
|
||||
manageWallet(wallet)
|
||||
return wallet
|
||||
}
|
||||
|
||||
private external fun createWalletFromDeviceJ(
|
||||
path: String, password: String,
|
||||
networkType: Int,
|
||||
deviceName: String,
|
||||
restoreHeight: Long,
|
||||
subaddressLookahead: String
|
||||
): Long
|
||||
|
||||
external fun closeJ(wallet: Wallet?): Boolean
|
||||
fun close(wallet: Wallet): Boolean {
|
||||
unmanageWallet(wallet)
|
||||
val closed = closeJ(wallet)
|
||||
if (!closed) {
|
||||
// in case we could not close it
|
||||
// we manage it again
|
||||
manageWallet(wallet)
|
||||
}
|
||||
return closed
|
||||
}
|
||||
|
||||
fun walletExists(aFile: File): Boolean {
|
||||
return walletExists(aFile.absolutePath)
|
||||
}
|
||||
|
||||
external fun walletExists(path: String?): Boolean
|
||||
external fun verifyWalletPassword(
|
||||
keys_file_name: String?,
|
||||
password: String?,
|
||||
watch_only: Boolean
|
||||
): Boolean
|
||||
|
||||
fun verifyWalletPasswordOnly(keys_file_name: String, password: String): Boolean {
|
||||
return queryWalletDeviceJ(keys_file_name, password) >= 0
|
||||
}
|
||||
|
||||
fun queryWalletDevice(keys_file_name: String, password: String): Wallet.Device {
|
||||
val device = queryWalletDeviceJ(keys_file_name, password)
|
||||
return Wallet.Device.values()[device + 1] // mapping is monero+1=android
|
||||
}
|
||||
|
||||
private external fun queryWalletDeviceJ(keys_file_name: String, password: String): Int
|
||||
fun findWallets(path: File): List<WalletInfo> {
|
||||
val wallets: MutableList<WalletInfo> = ArrayList()
|
||||
Timber.d("Scanning: %s", path.absolutePath)
|
||||
val found = path.listFiles { dir, filename -> filename.endsWith(".keys") } ?: return emptyList()
|
||||
for (i in found.indices) {
|
||||
val filename = found[i].name
|
||||
val f = File(
|
||||
found[i].parent,
|
||||
filename.substring(0, filename.length - 5)
|
||||
) // 5 is length of ".keys"+1
|
||||
wallets.add(WalletInfo(f))
|
||||
}
|
||||
return wallets
|
||||
}
|
||||
|
||||
// this should not be called on the main thread as it connects to the node (and takes a long time)
|
||||
fun setDaemon(node: Node?) {
|
||||
if (node != null) {
|
||||
daemonAddress = node.address
|
||||
require(networkType === node.networkType) { "network type does not match" }
|
||||
daemonUsername = node.username
|
||||
daemonPassword = node.password
|
||||
daemonAddress?.let { addr -> setDaemonAddressJ(addr) }
|
||||
} else {
|
||||
daemonAddress = null
|
||||
daemonUsername = ""
|
||||
daemonPassword = ""
|
||||
//setDaemonAddressJ(""); // don't disconnect as monero code blocks for many seconds!
|
||||
//TODO: need to do something about that later
|
||||
}
|
||||
}
|
||||
|
||||
fun getDaemonAddress(): String? {
|
||||
checkNotNull(daemonAddress) { "use setDaemon() to initialise daemon and net first!" }
|
||||
return daemonAddress
|
||||
}
|
||||
|
||||
private external fun setDaemonAddressJ(address: String)
|
||||
external fun getDaemonVersion(): Int
|
||||
external fun getBlockchainHeight(): Long
|
||||
external fun getBlockchainTargetHeight(): Long
|
||||
external fun getNetworkDifficulty(): Long
|
||||
external fun getMiningHashRate(): Double
|
||||
external fun getBlockTarget(): Long
|
||||
external fun isMining(): Boolean
|
||||
|
||||
external fun startMining(
|
||||
address: String?,
|
||||
background_mining: Boolean,
|
||||
ignore_battery: Boolean
|
||||
): Boolean
|
||||
|
||||
external fun stopMining(): Boolean
|
||||
external fun resolveOpenAlias(address: String?, dnssec_valid: Boolean): String?
|
||||
fun setProxy(address: String): Boolean {
|
||||
proxy = address
|
||||
return setProxyJ(address)
|
||||
}
|
||||
|
||||
private external fun setProxyJ(address: String?): Boolean
|
||||
inner class WalletInfo(wallet: File) : Comparable<WalletInfo> {
|
||||
private val path: File
|
||||
private val name: String
|
||||
|
||||
init {
|
||||
path = wallet.parentFile
|
||||
name = wallet.name
|
||||
}
|
||||
|
||||
override fun compareTo(other: WalletInfo): Int {
|
||||
return name.lowercase(Locale.getDefault())
|
||||
.compareTo(other.name.lowercase(Locale.getDefault()))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
//TODO: maybe put these in an enum like in monero core - but why?
|
||||
@JvmField
|
||||
var LOGLEVEL_SILENT = -1
|
||||
var LOGLEVEL_WARN = 0
|
||||
var LOGLEVEL_INFO = 1
|
||||
var LOGLEVEL_DEBUG = 2
|
||||
var LOGLEVEL_TRACE = 3
|
||||
var LOGLEVEL_MAX = 4
|
||||
|
||||
// no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it)
|
||||
@get:Synchronized
|
||||
@JvmStatic
|
||||
var instance: WalletManager? = null
|
||||
get() {
|
||||
if (field == null) {
|
||||
field = WalletManager()
|
||||
}
|
||||
return field
|
||||
}
|
||||
private set
|
||||
|
||||
init {
|
||||
System.loadLibrary("monerujo")
|
||||
}
|
||||
|
||||
fun addressPrefix(networkType: NetworkType): String {
|
||||
return when (networkType) {
|
||||
NetworkType.NetworkType_Testnet -> "9A-"
|
||||
NetworkType.NetworkType_Mainnet -> "4-"
|
||||
NetworkType.NetworkType_Stagenet -> "5-"
|
||||
else -> throw IllegalStateException("Unsupported Network: $networkType")
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
external fun initLogger(argv0: String?, defaultLogBaseName: String?)
|
||||
@JvmStatic
|
||||
external fun setLogLevel(level: Int)
|
||||
@JvmStatic
|
||||
external fun logDebug(category: String?, message: String?)
|
||||
@JvmStatic
|
||||
external fun logInfo(category: String?, message: String?)
|
||||
@JvmStatic
|
||||
external fun logWarning(category: String?, message: String?)
|
||||
@JvmStatic
|
||||
external fun logError(category: String?, message: String?)
|
||||
@JvmStatic
|
||||
external fun moneroVersion(): String?
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ public class BalanceService extends ServiceBase {
|
|||
long unlocked = 0;
|
||||
for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
||||
if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && coinsInfo.isUnlocked() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) {
|
||||
unlocked += coinsInfo.getAmount();
|
||||
unlocked += coinsInfo.amount;
|
||||
}
|
||||
}
|
||||
return unlocked;
|
||||
|
@ -43,7 +43,7 @@ public class BalanceService extends ServiceBase {
|
|||
long total = 0;
|
||||
for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
||||
if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) {
|
||||
total += coinsInfo.getAmount();
|
||||
total += coinsInfo.amount;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
|
|
|
@ -100,7 +100,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
|
|||
|
||||
@Override
|
||||
public void refreshed() {
|
||||
Wallet.ConnectionStatus status = wallet.getFullStatus().getConnectionStatus();
|
||||
Wallet.ConnectionStatus status = wallet.getFullStatus().connectionStatus;
|
||||
if (status == Wallet.ConnectionStatus.ConnectionStatus_Disconnected || status == null) {
|
||||
if (triesLeft > 0) {
|
||||
wallet.startRefresh();
|
||||
|
@ -162,7 +162,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
|
|||
|
||||
private List<TransactionOutput> maybeAddDonationOutputs(long amount, List<TransactionOutput> outputs, List<String> preferredInputs) throws Exception {
|
||||
TransactionOutput mainDestination = outputs.get(0); // at this point, for now, we should only have one item in the list. TODO: add multi-dest/pay-to-many feature in the UI
|
||||
String paymentId = Wallet.getPaymentIdFromAddress(mainDestination.getDestination(), WalletManager.getInstance().getNetworkType().getValue());
|
||||
String paymentId = Wallet.getPaymentIdFromAddress(mainDestination.destination, WalletManager.getInstance().networkType.value);
|
||||
ArrayList<TransactionOutput> newOutputs = new ArrayList<>(outputs);
|
||||
boolean donatePerTx = PrefService.getInstance().getBoolean(Constants.PREF_DONATE_PER_TX, false);
|
||||
if(donatePerTx && paymentId.isEmpty()) { // only attach donation when no payment id is needed (i.e. integrated address)
|
||||
|
@ -205,8 +205,8 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
|
|||
if (!sendAll) {
|
||||
long amountSelected = 0;
|
||||
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
||||
if (selectedUtxos.contains(coinsInfo.getKeyImage())) {
|
||||
amountSelected += coinsInfo.getAmount();
|
||||
if (selectedUtxos.contains(coinsInfo.keyImage)) {
|
||||
amountSelected += coinsInfo.amount;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,10 +57,10 @@ public class UTXOService extends ServiceBase {
|
|||
public void toggleFrozen(HashMap<String, CoinsInfo> selectedCoins) {
|
||||
ArrayList<String> frozenCoinsCopy = new ArrayList<>(frozenCoins);
|
||||
for(CoinsInfo coin : selectedCoins.values()) {
|
||||
if(frozenCoinsCopy.contains(coin.getPubKey())) {
|
||||
frozenCoinsCopy.remove(coin.getPubKey());
|
||||
if(frozenCoinsCopy.contains(coin.pubKey)) {
|
||||
frozenCoinsCopy.remove(coin.pubKey);
|
||||
} else {
|
||||
frozenCoinsCopy.add(coin.getPubKey());
|
||||
frozenCoinsCopy.add(coin.pubKey);
|
||||
}
|
||||
}
|
||||
this.frozenCoins = frozenCoinsCopy;
|
||||
|
@ -70,7 +70,7 @@ public class UTXOService extends ServiceBase {
|
|||
}
|
||||
|
||||
public boolean isCoinFrozen(CoinsInfo coinsInfo) {
|
||||
return frozenCoins.contains(coinsInfo.getPubKey());
|
||||
return frozenCoins.contains(coinsInfo.pubKey);
|
||||
}
|
||||
|
||||
private void loadFrozenCoins() throws JSONException {
|
||||
|
@ -104,18 +104,18 @@ public class UTXOService extends ServiceBase {
|
|||
Collections.sort(utxos);
|
||||
//loop through each utxo
|
||||
for (CoinsInfo coinsInfo : utxos) {
|
||||
if (!coinsInfo.isSpent() && coinsInfo.isUnlocked() && !coinsInfo.isFrozen() && !frozenCoins.contains(coinsInfo.getPubKey())) { //filter out spent, locked, and frozen outputs
|
||||
if (!coinsInfo.isSpent() && coinsInfo.isUnlocked() && !coinsInfo.isFrozen() && !frozenCoins.contains(coinsInfo.pubKey)) { //filter out spent, locked, and frozen outputs
|
||||
if (sendAll) {
|
||||
// if send all, add all utxos and set amount to send all
|
||||
selectedUtxos.add(coinsInfo.getKeyImage());
|
||||
selectedUtxos.add(coinsInfo.keyImage);
|
||||
amountSelected = Wallet.SWEEP_ALL;
|
||||
} else {
|
||||
//if amount selected is still less than amount needed, and the utxos tx hash hasn't already been seen, add utxo
|
||||
if (amountSelected <= amountWithBasicFee && !seenTxs.contains(coinsInfo.getHash())) {
|
||||
selectedUtxos.add(coinsInfo.getKeyImage());
|
||||
if (amountSelected <= amountWithBasicFee && !seenTxs.contains(coinsInfo.hash)) {
|
||||
selectedUtxos.add(coinsInfo.keyImage);
|
||||
// we don't want to spend multiple utxos from the same transaction, so we prevent that from happening here.
|
||||
seenTxs.add(coinsInfo.getHash());
|
||||
amountSelected += coinsInfo.getAmount();
|
||||
seenTxs.add(coinsInfo.hash);
|
||||
amountSelected += coinsInfo.amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
package net.mynero.wallet.util;
|
||||
|
||||
public class Constants {
|
||||
public static final String WALLET_NAME = "xmr_wallet";
|
||||
public static final String MNEMONIC_LANGUAGE = "English";
|
||||
public static final String PREF_USES_PASSWORD = "pref_uses_password";
|
||||
public static final String PREF_USES_TOR = "pref_uses_tor";
|
||||
public static final String PREF_PROXY = "pref_proxy";
|
||||
public static final String PREF_NODE_2 = "pref_node_2";
|
||||
public static final String PREF_CUSTOM_NODES = "pref_custom_nodes";
|
||||
public static final String PREF_USES_OFFSET = "pref_uses_offset";
|
||||
public static final String PREF_STREET_MODE = "pref_street_mode";
|
||||
public static final String PREF_MONEROCHAN = "pref_monerochan";
|
||||
public static final String PREF_DONATE_PER_TX = "pref_donate_per_tx";
|
||||
public static final String PREF_FROZEN_COINS = "pref_frozen_coins";
|
||||
|
||||
public static final String URI_PREFIX = "monero:";
|
||||
public static final String URI_ARG_AMOUNT = "tx_amount";
|
||||
public static final String URI_ARG_AMOUNT2 = "amount";
|
||||
public static final String NAV_ARG_TXINFO = "nav_arg_txinfo";
|
||||
|
||||
public static final String STREET_MODE_BALANCE = "#.############";
|
||||
public static final String[] DONATION_ADDRESSES = new String[] {
|
||||
"87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC", // primary Mysu Donation address
|
||||
"89QoPxs4cQSGbJrJddwzV3Ca7s2gVYHE1Xd1hGZafuVJVyNKt2LCQhxUdBF57PemxQiX3dmGUZLRRAzfeYyh9pq3GiWsDVo", // second Mysu Donation address
|
||||
"855acibBehTQJdC1f41BHWGq1FiQ5ztiAU7LiJgDUmmyJfDtRpJoo6Mc1en73duUScdeUYjLirACnZpv2C6pPbcZKgumdCS" // third Mysu Donation address
|
||||
};
|
||||
public static final String DONATE_ADDRESS = "87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC";
|
||||
}
|
29
app/src/main/java/net/mynero/wallet/util/Constants.kt
Normal file
29
app/src/main/java/net/mynero/wallet/util/Constants.kt
Normal file
|
@ -0,0 +1,29 @@
|
|||
package net.mynero.wallet.util
|
||||
|
||||
object Constants {
|
||||
const val WALLET_NAME = "xmr_wallet"
|
||||
const val MNEMONIC_LANGUAGE = "English"
|
||||
const val PREF_USES_PASSWORD = "pref_uses_password"
|
||||
const val PREF_USES_TOR = "pref_uses_tor"
|
||||
const val PREF_PROXY = "pref_proxy"
|
||||
const val PREF_NODE_2 = "pref_node_2"
|
||||
const val PREF_CUSTOM_NODES = "pref_custom_nodes"
|
||||
const val PREF_USES_OFFSET = "pref_uses_offset"
|
||||
const val PREF_STREET_MODE = "pref_street_mode"
|
||||
const val PREF_MONEROCHAN = "pref_monerochan"
|
||||
const val PREF_DONATE_PER_TX = "pref_donate_per_tx"
|
||||
const val PREF_FROZEN_COINS = "pref_frozen_coins"
|
||||
const val URI_PREFIX = "monero:"
|
||||
const val URI_ARG_AMOUNT = "tx_amount"
|
||||
const val URI_ARG_AMOUNT2 = "amount"
|
||||
const val NAV_ARG_TXINFO = "nav_arg_txinfo"
|
||||
const val STREET_MODE_BALANCE = "#.############"
|
||||
@JvmField
|
||||
val DONATION_ADDRESSES = arrayOf(
|
||||
"87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC", // primary Mysu Donation address
|
||||
"89QoPxs4cQSGbJrJddwzV3Ca7s2gVYHE1Xd1hGZafuVJVyNKt2LCQhxUdBF57PemxQiX3dmGUZLRRAzfeYyh9pq3GiWsDVo", // second Mysu Donation address
|
||||
"855acibBehTQJdC1f41BHWGq1FiQ5ztiAU7LiJgDUmmyJfDtRpJoo6Mc1en73duUScdeUYjLirACnZpv2C6pPbcZKgumdCS" // third Mysu Donation address
|
||||
)
|
||||
const val DONATE_ADDRESS =
|
||||
"87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC"
|
||||
}
|
|
@ -13,17 +13,17 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.util
|
||||
|
||||
package net.mynero.wallet.util;
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class DateHelper {
|
||||
public static final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public static Date parse(String dateString) throws ParseException {
|
||||
return DATETIME_FORMATTER.parse(dateString.replaceAll("Z$", "+0000"));
|
||||
object DateHelper {
|
||||
@JvmField
|
||||
val DATETIME_FORMATTER = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
||||
@Throws(ParseException::class)
|
||||
fun parse(dateString: String): Date {
|
||||
return DATETIME_FORMATTER.parse(dateString.replace("Z$".toRegex(), "+0000"))
|
||||
}
|
||||
}
|
|
@ -13,33 +13,23 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.util
|
||||
|
||||
package net.mynero.wallet.util;
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
public enum DayNightMode {
|
||||
enum class DayNightMode(val nightMode: Int) {
|
||||
// order must match R.array.daynight_themes
|
||||
AUTO(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM),
|
||||
DAY(AppCompatDelegate.MODE_NIGHT_NO),
|
||||
NIGHT(AppCompatDelegate.MODE_NIGHT_YES),
|
||||
AUTO(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM), DAY(AppCompatDelegate.MODE_NIGHT_NO), NIGHT(
|
||||
AppCompatDelegate.MODE_NIGHT_YES
|
||||
),
|
||||
UNKNOWN(AppCompatDelegate.MODE_NIGHT_UNSPECIFIED);
|
||||
|
||||
final private int nightMode;
|
||||
|
||||
DayNightMode(int nightMode) {
|
||||
this.nightMode = nightMode;
|
||||
companion object {
|
||||
fun getValue(nightMode: Int): DayNightMode {
|
||||
for (mode in values()) {
|
||||
if (mode.nightMode == nightMode) return mode
|
||||
}
|
||||
|
||||
static public DayNightMode getValue(int nightMode) {
|
||||
for (DayNightMode mode : DayNightMode.values()) {
|
||||
if (mode.nightMode == nightMode)
|
||||
return mode;
|
||||
return UNKNOWN
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
public int getNightMode() {
|
||||
return nightMode;
|
||||
}
|
||||
}
|
|
@ -13,299 +13,294 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.util
|
||||
|
||||
package net.mynero.wallet.util;
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.ClipData
|
||||
import android.content.ClipDescription
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.VectorDrawable
|
||||
import android.os.Build
|
||||
import android.view.WindowManager
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.core.content.ContextCompat
|
||||
import net.mynero.wallet.R
|
||||
import net.mynero.wallet.model.WalletManager
|
||||
import net.mynero.wallet.model.WalletManager.Companion.initLogger
|
||||
import net.mynero.wallet.model.WalletManager.Companion.setLogLevel
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.net.MalformedURLException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.URL
|
||||
import java.util.Locale
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import net.mynero.wallet.R;
|
||||
import net.mynero.wallet.model.WalletManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Helper {
|
||||
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
|
||||
|
||||
static public final int XMR_DECIMALS = 12;
|
||||
static public final long ONE_XMR = Math.round(Math.pow(10, Helper.XMR_DECIMALS));
|
||||
|
||||
static public final boolean SHOW_EXCHANGERATES = true;
|
||||
static public final int PERMISSIONS_REQUEST_CAMERA = 7;
|
||||
static final int HTTP_TIMEOUT = 5000;
|
||||
static private final String WALLET_DIR = "wallets";
|
||||
static private final String MONERO_DIR = "monero";
|
||||
private final static char[] HexArray = "0123456789ABCDEF".toCharArray();
|
||||
static public boolean ALLOW_SHIFT = false;
|
||||
static public int DISPLAY_DIGITS_INFO = 5;
|
||||
static private Animation ShakeAnimation;
|
||||
|
||||
static public File getWalletRoot(Context context) {
|
||||
return getStorage(context, WALLET_DIR);
|
||||
object Helper {
|
||||
const val NOCRAZYPASS_FLAGFILE = ".nocrazypass"
|
||||
const val XMR_DECIMALS = 12
|
||||
val ONE_XMR = Math.round(Math.pow(10.0, XMR_DECIMALS.toDouble()))
|
||||
const val SHOW_EXCHANGERATES = true
|
||||
const val PERMISSIONS_REQUEST_CAMERA = 7
|
||||
const val HTTP_TIMEOUT = 5000
|
||||
private const val WALLET_DIR = "wallets"
|
||||
private const val MONERO_DIR = "monero"
|
||||
private val HexArray = "0123456789ABCDEF".toCharArray()
|
||||
var ALLOW_SHIFT = false
|
||||
@JvmField
|
||||
var DISPLAY_DIGITS_INFO = 5
|
||||
private var ShakeAnimation: Animation? = null
|
||||
fun getWalletRoot(context: Context): File {
|
||||
return getStorage(context, WALLET_DIR)
|
||||
}
|
||||
|
||||
static public File getStorage(Context context, String folderName) {
|
||||
File dir = new File(context.getFilesDir(), folderName);
|
||||
fun getStorage(context: Context, folderName: String?): File {
|
||||
val dir = File(context.filesDir, folderName)
|
||||
if (!dir.exists()) {
|
||||
Timber.i("Creating %s", dir.getAbsolutePath());
|
||||
dir.mkdirs(); // try to make it
|
||||
Timber.i("Creating %s", dir.absolutePath)
|
||||
dir.mkdirs() // try to make it
|
||||
}
|
||||
if (!dir.isDirectory()) {
|
||||
String msg = "Directory " + dir.getAbsolutePath() + " does not exist.";
|
||||
Timber.e(msg);
|
||||
throw new IllegalStateException(msg);
|
||||
if (!dir.isDirectory) {
|
||||
val msg = "Directory " + dir.absolutePath + " does not exist."
|
||||
Timber.e(msg)
|
||||
throw IllegalStateException(msg)
|
||||
}
|
||||
return dir;
|
||||
return dir
|
||||
}
|
||||
|
||||
static public boolean getCameraPermission(Activity context) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
fun getCameraPermission(context: Activity): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (context.checkSelfPermission(Manifest.permission.CAMERA)
|
||||
== PackageManager.PERMISSION_DENIED) {
|
||||
Timber.w("Permission denied for CAMERA - requesting it");
|
||||
String[] permissions = {Manifest.permission.CAMERA};
|
||||
context.requestPermissions(permissions, PERMISSIONS_REQUEST_CAMERA);
|
||||
return false;
|
||||
== PackageManager.PERMISSION_DENIED
|
||||
) {
|
||||
Timber.w("Permission denied for CAMERA - requesting it")
|
||||
val permissions = arrayOf(Manifest.permission.CAMERA)
|
||||
context.requestPermissions(
|
||||
permissions,
|
||||
PERMISSIONS_REQUEST_CAMERA
|
||||
)
|
||||
false
|
||||
} else {
|
||||
return true;
|
||||
true
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
static public boolean getCameraPermission(Activity context, ActivityResultLauncher<String> launcher) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
@JvmStatic
|
||||
fun getCameraPermission(context: Activity, launcher: ActivityResultLauncher<String?>): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (context.checkSelfPermission(Manifest.permission.CAMERA)
|
||||
== PackageManager.PERMISSION_DENIED) {
|
||||
Timber.w("Permission denied for CAMERA - requesting it");
|
||||
launcher.launch(Manifest.permission.CAMERA);
|
||||
return false;
|
||||
== PackageManager.PERMISSION_DENIED
|
||||
) {
|
||||
Timber.w("Permission denied for CAMERA - requesting it")
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
false
|
||||
} else {
|
||||
return true;
|
||||
true
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
static public File getWalletFile(Context context, String aWalletName) {
|
||||
File walletDir = getWalletRoot(context);
|
||||
File f = new File(walletDir, aWalletName);
|
||||
Timber.d("wallet=%s size= %d", f.getAbsolutePath(), f.length());
|
||||
return f;
|
||||
fun getWalletFile(context: Context, aWalletName: String?): File {
|
||||
val walletDir = getWalletRoot(context)
|
||||
val f = File(walletDir, aWalletName)
|
||||
Timber.d("wallet=%s size= %d", f.absolutePath, f.length())
|
||||
return f
|
||||
}
|
||||
|
||||
static public void showKeyboard(Activity act) {
|
||||
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
final View focus = act.getCurrentFocus();
|
||||
if (focus != null)
|
||||
imm.showSoftInput(focus, InputMethodManager.SHOW_IMPLICIT);
|
||||
fun showKeyboard(act: Activity) {
|
||||
val imm = act.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
val focus = act.currentFocus
|
||||
if (focus != null) imm.showSoftInput(focus, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
|
||||
static public void hideKeyboard(Activity act) {
|
||||
if (act == null) return;
|
||||
if (act.getCurrentFocus() == null) {
|
||||
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
|
||||
fun hideKeyboard(act: Activity?) {
|
||||
if (act == null) return
|
||||
if (act.currentFocus == null) {
|
||||
act.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
|
||||
} else {
|
||||
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow((null == act.getCurrentFocus()) ? null : act.getCurrentFocus().getWindowToken(),
|
||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
val imm = act.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
act.currentFocus?.let {
|
||||
imm.hideSoftInputFromWindow(
|
||||
it.windowToken,
|
||||
InputMethodManager.HIDE_NOT_ALWAYS
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static public void showKeyboard(Dialog dialog) {
|
||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
fun showKeyboard(dialog: Dialog) {
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||
}
|
||||
|
||||
static public void hideKeyboardAlways(Activity act) {
|
||||
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
fun hideKeyboardAlways(act: Activity) {
|
||||
act.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
|
||||
}
|
||||
|
||||
static public BigDecimal getDecimalAmount(long amount) {
|
||||
return new BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS);
|
||||
fun getDecimalAmount(amount: Long): BigDecimal {
|
||||
return BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS)
|
||||
}
|
||||
|
||||
static public String getDisplayAmount(long amount) {
|
||||
return getDisplayAmount(amount, XMR_DECIMALS);
|
||||
@JvmStatic
|
||||
fun getDisplayAmount(amount: Long): String {
|
||||
return getDisplayAmount(amount, XMR_DECIMALS)
|
||||
}
|
||||
|
||||
static public String getDisplayAmount(long amount, int maxDecimals) {
|
||||
@JvmStatic
|
||||
fun getDisplayAmount(amount: Long, maxDecimals: Int): String {
|
||||
// a Java bug does not strip zeros properly if the value is 0
|
||||
if (amount == 0) return "0.00";
|
||||
BigDecimal d = getDecimalAmount(amount)
|
||||
if (amount == 0L) return "0.00"
|
||||
var d = getDecimalAmount(amount)
|
||||
.setScale(maxDecimals, BigDecimal.ROUND_HALF_UP)
|
||||
.stripTrailingZeros();
|
||||
if (d.scale() < 2)
|
||||
d = d.setScale(2, BigDecimal.ROUND_UNNECESSARY);
|
||||
return d.toPlainString();
|
||||
.stripTrailingZeros()
|
||||
if (d.scale() < 2) d = d.setScale(2, BigDecimal.ROUND_UNNECESSARY)
|
||||
return d.toPlainString()
|
||||
}
|
||||
|
||||
static public String getFormattedAmount(double amount, boolean isCrypto) {
|
||||
fun getFormattedAmount(amount: Double, isCrypto: Boolean): String? {
|
||||
// at this point selection is XMR in case of error
|
||||
String displayB;
|
||||
if (isCrypto) {
|
||||
if ((amount >= 0) || (amount == 0)) {
|
||||
displayB = String.format(Locale.US, "%,.5f", amount);
|
||||
val displayB: String?
|
||||
displayB = if (isCrypto) {
|
||||
if (amount >= 0 || amount == 0.0) {
|
||||
String.format(Locale.US, "%,.5f", amount)
|
||||
} else {
|
||||
displayB = null;
|
||||
null
|
||||
}
|
||||
} else { // not crypto
|
||||
displayB = String.format(Locale.US, "%,.2f", amount);
|
||||
String.format(Locale.US, "%,.2f", amount)
|
||||
}
|
||||
return displayB;
|
||||
return displayB
|
||||
}
|
||||
|
||||
static public String getDisplayAmount(double amount) {
|
||||
@JvmStatic
|
||||
fun getDisplayAmount(amount: Double): String {
|
||||
// a Java bug does not strip zeros properly if the value is 0
|
||||
BigDecimal d = new BigDecimal(amount)
|
||||
var d = BigDecimal(amount)
|
||||
.setScale(XMR_DECIMALS, BigDecimal.ROUND_HALF_UP)
|
||||
.stripTrailingZeros();
|
||||
if (d.scale() < 1)
|
||||
d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY);
|
||||
return d.toPlainString();
|
||||
.stripTrailingZeros()
|
||||
if (d.scale() < 1) d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY)
|
||||
return d.toPlainString()
|
||||
}
|
||||
|
||||
static public Bitmap getBitmap(Context context, int drawableId) {
|
||||
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return BitmapFactory.decodeResource(context.getResources(), drawableId);
|
||||
} else if (drawable instanceof VectorDrawable) {
|
||||
return getBitmap((VectorDrawable) drawable);
|
||||
fun getBitmap(context: Context, drawableId: Int): Bitmap {
|
||||
val drawable = ContextCompat.getDrawable(context, drawableId)
|
||||
return if (drawable is BitmapDrawable) {
|
||||
BitmapFactory.decodeResource(context.resources, drawableId)
|
||||
} else if (drawable is VectorDrawable) {
|
||||
getBitmap(drawable)
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported drawable type");
|
||||
throw IllegalArgumentException("unsupported drawable type")
|
||||
}
|
||||
}
|
||||
|
||||
static private Bitmap getBitmap(VectorDrawable vectorDrawable) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
|
||||
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
vectorDrawable.draw(canvas);
|
||||
return bitmap;
|
||||
private fun getBitmap(vectorDrawable: VectorDrawable): Bitmap {
|
||||
val bitmap = Bitmap.createBitmap(
|
||||
vectorDrawable.intrinsicWidth,
|
||||
vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val canvas = Canvas(bitmap)
|
||||
vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||
vectorDrawable.draw(canvas)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
static public String getUrl(String httpsUrl) {
|
||||
HttpsURLConnection urlConnection = null;
|
||||
fun getUrl(httpsUrl: String?): String? {
|
||||
var urlConnection: HttpsURLConnection? = null
|
||||
try {
|
||||
URL url = new URL(httpsUrl);
|
||||
urlConnection = (HttpsURLConnection) url.openConnection();
|
||||
urlConnection.setConnectTimeout(HTTP_TIMEOUT);
|
||||
urlConnection.setReadTimeout(HTTP_TIMEOUT);
|
||||
InputStreamReader in = new InputStreamReader(urlConnection.getInputStream());
|
||||
StringBuffer sb = new StringBuffer();
|
||||
final int BUFFER_SIZE = 512;
|
||||
char[] buffer = new char[BUFFER_SIZE];
|
||||
int length = in.read(buffer, 0, BUFFER_SIZE);
|
||||
val url = URL(httpsUrl)
|
||||
urlConnection = url.openConnection() as HttpsURLConnection
|
||||
urlConnection.connectTimeout = HTTP_TIMEOUT
|
||||
urlConnection.readTimeout = HTTP_TIMEOUT
|
||||
val `in` = InputStreamReader(urlConnection.inputStream)
|
||||
val sb = StringBuffer()
|
||||
val BUFFER_SIZE = 512
|
||||
val buffer = CharArray(BUFFER_SIZE)
|
||||
var length = `in`.read(buffer, 0, BUFFER_SIZE)
|
||||
while (length >= 0) {
|
||||
sb.append(buffer, 0, length);
|
||||
length = in.read(buffer, 0, BUFFER_SIZE);
|
||||
sb.append(buffer, 0, length)
|
||||
length = `in`.read(buffer, 0, BUFFER_SIZE)
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (SocketTimeoutException ex) {
|
||||
Timber.w("C %s", ex.getLocalizedMessage());
|
||||
} catch (MalformedURLException ex) {
|
||||
Timber.e("A %s", ex.getLocalizedMessage());
|
||||
} catch (IOException ex) {
|
||||
Timber.e("B %s", ex.getLocalizedMessage());
|
||||
return sb.toString()
|
||||
} catch (ex: SocketTimeoutException) {
|
||||
Timber.w("C %s", ex.localizedMessage)
|
||||
} catch (ex: MalformedURLException) {
|
||||
Timber.e("A %s", ex.localizedMessage)
|
||||
} catch (ex: IOException) {
|
||||
Timber.e("B %s", ex.localizedMessage)
|
||||
} finally {
|
||||
if (urlConnection != null) {
|
||||
urlConnection.disconnect();
|
||||
urlConnection?.disconnect()
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
static public void clipBoardCopy(Context context, String label, String text) {
|
||||
@JvmStatic
|
||||
fun clipBoardCopy(context: Context?, label: String?, text: String?) {
|
||||
if (context != null) {
|
||||
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(label, text);
|
||||
clipboardManager.setPrimaryClip(clip);
|
||||
Toast.makeText(context, context.getText(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show();
|
||||
val clipboardManager =
|
||||
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText(label, text)
|
||||
clipboardManager.setPrimaryClip(clip)
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getText(R.string.copied_to_clipboard),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
static public String getClipBoardText(Context context) {
|
||||
final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
@JvmStatic
|
||||
fun getClipBoardText(context: Context): String? {
|
||||
val clipboardManager =
|
||||
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
try {
|
||||
if (clipboardManager.hasPrimaryClip()
|
||||
&& clipboardManager.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
|
||||
final ClipData.Item item = clipboardManager.getPrimaryClip().getItemAt(0);
|
||||
return item.getText().toString();
|
||||
&& clipboardManager.primaryClipDescription?.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) == true
|
||||
) {
|
||||
val item = clipboardManager.primaryClip?.getItemAt(0)
|
||||
return item?.text.toString()
|
||||
}
|
||||
} catch (NullPointerException ex) {
|
||||
} catch (ex: NullPointerException) {
|
||||
// if we have don't find a text in the clipboard
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
static public Animation getShakeAnimation(Context context) {
|
||||
fun getShakeAnimation(context: Context?): Animation? {
|
||||
if (ShakeAnimation == null) {
|
||||
synchronized (Helper.class) {
|
||||
synchronized(Helper::class.java) {
|
||||
if (ShakeAnimation == null) {
|
||||
ShakeAnimation = AnimationUtils.loadAnimation(context, R.anim.shake);
|
||||
ShakeAnimation = AnimationUtils.loadAnimation(context, R.anim.shake)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ShakeAnimation;
|
||||
}
|
||||
|
||||
public static String bytesToHex(byte[] data) {
|
||||
if ((data != null) && (data.length > 0))
|
||||
return String.format("%0" + (data.length * 2) + "X", new BigInteger(1, data));
|
||||
else return "";
|
||||
}
|
||||
|
||||
public static byte[] hexToBytes(String hex) {
|
||||
final int len = hex.length();
|
||||
final byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
|
||||
+ Character.digit(hex.charAt(i + 1), 16));
|
||||
}
|
||||
return data;
|
||||
return ShakeAnimation
|
||||
}
|
||||
|
||||
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
|
||||
static public void initLogger(Context context, int level) {
|
||||
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
|
||||
WalletManager.initLogger(home + "/monerujo", "monerujo.log");
|
||||
if (level >= WalletManager.LOGLEVEL_SILENT)
|
||||
WalletManager.setLogLevel(level);
|
||||
fun initLogger(context: Context, level: Int) {
|
||||
val home = getStorage(context, MONERO_DIR).absolutePath
|
||||
initLogger("$home/monerujo", "monerujo.log")
|
||||
if (level >= WalletManager.LOGLEVEL_SILENT) setLogLevel(level)
|
||||
}
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018 m2049r
|
||||
* Copyright 2013 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.mynero.wallet.util;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.security.KeyPairGeneratorSpec;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.UnrecoverableEntryException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class KeyStoreHelper {
|
||||
|
||||
static {
|
||||
System.loadLibrary("monerujo");
|
||||
}
|
||||
|
||||
public static boolean saveWalletUserPass(@NonNull Context context, String wallet, String password) {
|
||||
String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet;
|
||||
byte[] data = password.getBytes(StandardCharsets.UTF_8);
|
||||
try {
|
||||
KeyStoreHelper.createKeys(context, walletKeyAlias);
|
||||
byte[] encrypted = KeyStoreHelper.encrypt(walletKeyAlias, data);
|
||||
SharedPreferences.Editor e = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE).edit();
|
||||
if (encrypted == null) {
|
||||
e.remove(wallet).apply();
|
||||
return false;
|
||||
}
|
||||
e.putString(wallet, Base64.encodeToString(encrypted, Base64.DEFAULT)).apply();
|
||||
return true;
|
||||
} catch (NoSuchProviderException | NoSuchAlgorithmException |
|
||||
InvalidAlgorithmParameterException | KeyStoreException ex) {
|
||||
Timber.w(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a public and private key and stores it using the Android Key
|
||||
* Store, so that only this application will be able to access the keys.
|
||||
*/
|
||||
private static void createKeys(Context context, String alias) throws NoSuchProviderException,
|
||||
NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyStoreException {
|
||||
KeyStore keyStore = KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
||||
try {
|
||||
keyStore.load(null);
|
||||
} catch (IOException | CertificateException ex) {
|
||||
throw new IllegalStateException("Could not load KeySotre", ex);
|
||||
}
|
||||
if (!keyStore.containsAlias(alias)) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
createKeysJBMR2(context, alias);
|
||||
} else {
|
||||
createKeysM(alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean deleteKeys(String alias) throws KeyStoreException {
|
||||
KeyStore keyStore = KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
||||
try {
|
||||
keyStore.load(null);
|
||||
keyStore.deleteEntry(alias);
|
||||
return true;
|
||||
} catch (IOException | NoSuchAlgorithmException | CertificateException ex) {
|
||||
Timber.w(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean keyExists(String wallet) throws BrokenPasswordStoreException {
|
||||
try {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStoreHelper.SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
||||
keyStore.load(null);
|
||||
return keyStore.containsAlias(KeyStoreHelper.SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet);
|
||||
} catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException ex) {
|
||||
throw new BrokenPasswordStoreException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
private static void createKeysJBMR2(Context context, String alias) throws NoSuchProviderException,
|
||||
NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
||||
|
||||
Calendar start = new GregorianCalendar();
|
||||
Calendar end = new GregorianCalendar();
|
||||
end.add(Calendar.YEAR, 300);
|
||||
|
||||
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
|
||||
.setAlias(alias)
|
||||
.setSubject(new X500Principal("CN=" + alias))
|
||||
.setSerialNumber(BigInteger.valueOf(Math.abs(alias.hashCode())))
|
||||
.setStartDate(start.getTime()).setEndDate(end.getTime())
|
||||
.build();
|
||||
// defaults to 2048 bit modulus
|
||||
KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(
|
||||
SecurityConstants.TYPE_RSA,
|
||||
SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
||||
kpGenerator.initialize(spec);
|
||||
KeyPair kp = kpGenerator.generateKeyPair();
|
||||
Timber.d("preM Keys created");
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private static void createKeysM(String alias) throws NoSuchProviderException,
|
||||
NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
|
||||
KeyProperties.KEY_ALGORITHM_RSA, SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
||||
keyPairGenerator.initialize(
|
||||
new KeyGenParameterSpec.Builder(
|
||||
alias, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
||||
.setDigests(KeyProperties.DIGEST_SHA256)
|
||||
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
|
||||
.build());
|
||||
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
||||
Timber.d("M Keys created");
|
||||
}
|
||||
|
||||
private static PrivateKey getPrivateKey(String alias) {
|
||||
try {
|
||||
KeyStore ks = KeyStore
|
||||
.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
||||
ks.load(null);
|
||||
//KeyStore.Entry entry = ks.getEntry(alias, null);
|
||||
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, null);
|
||||
|
||||
if (privateKey == null) {
|
||||
Timber.w("No key found under alias: %s", alias);
|
||||
return null;
|
||||
}
|
||||
|
||||
return privateKey;
|
||||
} catch (IOException | NoSuchAlgorithmException | CertificateException
|
||||
| UnrecoverableEntryException | KeyStoreException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static PublicKey getPublicKey(String alias) {
|
||||
try {
|
||||
KeyStore ks = KeyStore
|
||||
.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
||||
ks.load(null);
|
||||
|
||||
PublicKey publicKey = ks.getCertificate(alias).getPublicKey();
|
||||
|
||||
if (publicKey == null) {
|
||||
Timber.w("No public key");
|
||||
return null;
|
||||
}
|
||||
return publicKey;
|
||||
} catch (IOException | NoSuchAlgorithmException | CertificateException
|
||||
| KeyStoreException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] encrypt(String alias, byte[] data) {
|
||||
try {
|
||||
PublicKey publicKey = getPublicKey(alias);
|
||||
Cipher cipher = Cipher.getInstance(SecurityConstants.CIPHER_RSA_ECB_PKCS1);
|
||||
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
return cipher.doFinal(data);
|
||||
} catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException
|
||||
| NoSuchAlgorithmException | NoSuchPaddingException ex) {
|
||||
Timber.e(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] decrypt(String alias, byte[] data) {
|
||||
try {
|
||||
PrivateKey privateKey = getPrivateKey(alias);
|
||||
if (privateKey == null) return null;
|
||||
Cipher cipher = Cipher.getInstance(SecurityConstants.CIPHER_RSA_ECB_PKCS1);
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
return cipher.doFinal(data);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
|
||||
IllegalBlockSizeException | BadPaddingException ex) {
|
||||
Timber.e(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the data using the key pair stored in the Android Key Store. This
|
||||
* signature can be used with the data later to verify it was signed by this
|
||||
* application.
|
||||
*
|
||||
* @return The data signature generated
|
||||
*/
|
||||
private static byte[] signData(String alias, byte[] data) throws NoSuchAlgorithmException,
|
||||
InvalidKeyException, SignatureException {
|
||||
PrivateKey privateKey = getPrivateKey(alias);
|
||||
if (privateKey == null) return null;
|
||||
Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA);
|
||||
s.initSign(privateKey);
|
||||
s.update(data);
|
||||
return s.sign();
|
||||
}
|
||||
|
||||
public interface SecurityConstants {
|
||||
String KEYSTORE_PROVIDER_ANDROID_KEYSTORE = "AndroidKeyStore";
|
||||
String TYPE_RSA = "RSA";
|
||||
String SIGNATURE_SHA256withRSA = "SHA256withRSA";
|
||||
String CIPHER_RSA_ECB_PKCS1 = "RSA/ECB/PKCS1Padding";
|
||||
String WALLET_PASS_PREFS_NAME = "wallet";
|
||||
String WALLET_PASS_KEY_PREFIX = "walletKey-";
|
||||
}
|
||||
|
||||
static public class BrokenPasswordStoreException extends Exception {
|
||||
BrokenPasswordStoreException() {
|
||||
super();
|
||||
}
|
||||
|
||||
BrokenPasswordStoreException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,41 +13,43 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.util
|
||||
|
||||
package net.mynero.wallet.util;
|
||||
import net.mynero.wallet.service.MoneroHandlerThread
|
||||
import java.util.concurrent.BlockingQueue
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.ThreadFactory
|
||||
import java.util.concurrent.ThreadPoolExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
import net.mynero.wallet.service.MoneroHandlerThread;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
||||
public class MoneroThreadPoolExecutor {
|
||||
public static final Executor MONERO_THREAD_POOL_EXECUTOR;
|
||||
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
|
||||
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
|
||||
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
|
||||
private static final int KEEP_ALIVE_SECONDS = 30;
|
||||
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
|
||||
private final AtomicInteger mCount = new AtomicInteger(1);
|
||||
|
||||
public Thread newThread(Runnable r) {
|
||||
return new Thread(null, r, "MoneroTask #" + mCount.getAndIncrement(), MoneroHandlerThread.THREAD_STACK_SIZE);
|
||||
object MoneroThreadPoolExecutor {
|
||||
@JvmField
|
||||
var MONERO_THREAD_POOL_EXECUTOR: Executor? = null
|
||||
private val CPU_COUNT = Runtime.getRuntime().availableProcessors()
|
||||
private val CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4))
|
||||
private val MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1
|
||||
private const val KEEP_ALIVE_SECONDS = 30
|
||||
private val sThreadFactory: ThreadFactory = object : ThreadFactory {
|
||||
private val mCount = AtomicInteger(1)
|
||||
override fun newThread(r: Runnable): Thread {
|
||||
return Thread(
|
||||
null,
|
||||
r,
|
||||
"MoneroTask #" + mCount.getAndIncrement(),
|
||||
MoneroHandlerThread.THREAD_STACK_SIZE
|
||||
)
|
||||
}
|
||||
};
|
||||
private static final BlockingQueue<Runnable> sPoolWorkQueue =
|
||||
new LinkedBlockingQueue<>(128);
|
||||
}
|
||||
private val sPoolWorkQueue: BlockingQueue<Runnable> = LinkedBlockingQueue(128)
|
||||
|
||||
static {
|
||||
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
|
||||
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
|
||||
sPoolWorkQueue, sThreadFactory);
|
||||
threadPoolExecutor.allowCoreThreadTimeOut(true);
|
||||
MONERO_THREAD_POOL_EXECUTOR = threadPoolExecutor;
|
||||
init {
|
||||
val threadPoolExecutor = ThreadPoolExecutor(
|
||||
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS.toLong(), TimeUnit.SECONDS,
|
||||
sPoolWorkQueue, sThreadFactory
|
||||
)
|
||||
threadPoolExecutor.allowCoreThreadTimeOut(true)
|
||||
MONERO_THREAD_POOL_EXECUTOR = threadPoolExecutor
|
||||
}
|
||||
}
|
|
@ -13,22 +13,19 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.util
|
||||
|
||||
package net.mynero.wallet.util;
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
import net.mynero.wallet.service.PrefService;
|
||||
|
||||
public class NightmodeHelper {
|
||||
public static void getAndSetPreferredNightmode() {
|
||||
setNightMode(DayNightMode.NIGHT);
|
||||
object NightmodeHelper {
|
||||
val preferredNightmode: Unit
|
||||
get() {
|
||||
setNightMode(DayNightMode.NIGHT)
|
||||
}
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
public static void setNightMode(DayNightMode mode) {
|
||||
AppCompatDelegate.setDefaultNightMode(mode.getNightMode());
|
||||
fun setNightMode(mode: DayNightMode) {
|
||||
AppCompatDelegate.setDefaultNightMode(mode.nightMode)
|
||||
}
|
||||
}
|
|
@ -13,16 +13,14 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.util
|
||||
|
||||
package net.mynero.wallet.util;
|
||||
|
||||
public class OnionHelper {
|
||||
|
||||
public static boolean isOnionHost(String hostname) {
|
||||
return hostname.endsWith(".onion");
|
||||
object OnionHelper {
|
||||
fun isOnionHost(hostname: String): Boolean {
|
||||
return hostname.endsWith(".onion")
|
||||
}
|
||||
|
||||
public static boolean isI2PHost(String hostname) {
|
||||
return hostname.endsWith(".i2p");
|
||||
fun isI2PHost(hostname: String): Boolean {
|
||||
return hostname.endsWith(".i2p")
|
||||
}
|
||||
}
|
|
@ -13,198 +13,198 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.util
|
||||
|
||||
package net.mynero.wallet.util;
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
class RestoreHeight internal constructor() {
|
||||
private val blockheight: MutableMap<String, Long> = HashMap()
|
||||
|
||||
public class RestoreHeight {
|
||||
static final int DIFFICULTY_TARGET = 120; // seconds
|
||||
|
||||
static private RestoreHeight Singleton = null;
|
||||
private final Map<String, Long> blockheight = new HashMap<>();
|
||||
|
||||
RestoreHeight() {
|
||||
blockheight.put("2014-05-01", 18844L);
|
||||
blockheight.put("2014-06-01", 65406L);
|
||||
blockheight.put("2014-07-01", 108882L);
|
||||
blockheight.put("2014-08-01", 153594L);
|
||||
blockheight.put("2014-09-01", 198072L);
|
||||
blockheight.put("2014-10-01", 241088L);
|
||||
blockheight.put("2014-11-01", 285305L);
|
||||
blockheight.put("2014-12-01", 328069L);
|
||||
blockheight.put("2015-01-01", 372369L);
|
||||
blockheight.put("2015-02-01", 416505L);
|
||||
blockheight.put("2015-03-01", 456631L);
|
||||
blockheight.put("2015-04-01", 501084L);
|
||||
blockheight.put("2015-05-01", 543973L);
|
||||
blockheight.put("2015-06-01", 588326L);
|
||||
blockheight.put("2015-07-01", 631187L);
|
||||
blockheight.put("2015-08-01", 675484L);
|
||||
blockheight.put("2015-09-01", 719725L);
|
||||
blockheight.put("2015-10-01", 762463L);
|
||||
blockheight.put("2015-11-01", 806528L);
|
||||
blockheight.put("2015-12-01", 849041L);
|
||||
blockheight.put("2016-01-01", 892866L);
|
||||
blockheight.put("2016-02-01", 936736L);
|
||||
blockheight.put("2016-03-01", 977691L);
|
||||
blockheight.put("2016-04-01", 1015848L);
|
||||
blockheight.put("2016-05-01", 1037417L);
|
||||
blockheight.put("2016-06-01", 1059651L);
|
||||
blockheight.put("2016-07-01", 1081269L);
|
||||
blockheight.put("2016-08-01", 1103630L);
|
||||
blockheight.put("2016-09-01", 1125983L);
|
||||
blockheight.put("2016-10-01", 1147617L);
|
||||
blockheight.put("2016-11-01", 1169779L);
|
||||
blockheight.put("2016-12-01", 1191402L);
|
||||
blockheight.put("2017-01-01", 1213861L);
|
||||
blockheight.put("2017-02-01", 1236197L);
|
||||
blockheight.put("2017-03-01", 1256358L);
|
||||
blockheight.put("2017-04-01", 1278622L);
|
||||
blockheight.put("2017-05-01", 1300239L);
|
||||
blockheight.put("2017-06-01", 1322564L);
|
||||
blockheight.put("2017-07-01", 1344225L);
|
||||
blockheight.put("2017-08-01", 1366664L);
|
||||
blockheight.put("2017-09-01", 1389113L);
|
||||
blockheight.put("2017-10-01", 1410738L);
|
||||
blockheight.put("2017-11-01", 1433039L);
|
||||
blockheight.put("2017-12-01", 1454639L);
|
||||
blockheight.put("2018-01-01", 1477201L);
|
||||
blockheight.put("2018-02-01", 1499599L);
|
||||
blockheight.put("2018-03-01", 1519796L);
|
||||
blockheight.put("2018-04-01", 1542067L);
|
||||
blockheight.put("2018-05-01", 1562861L);
|
||||
blockheight.put("2018-06-01", 1585135L);
|
||||
blockheight.put("2018-07-01", 1606715L);
|
||||
blockheight.put("2018-08-01", 1629017L);
|
||||
blockheight.put("2018-09-01", 1651347L);
|
||||
blockheight.put("2018-10-01", 1673031L);
|
||||
blockheight.put("2018-11-01", 1695128L);
|
||||
blockheight.put("2018-12-01", 1716687L);
|
||||
blockheight.put("2019-01-01", 1738923L);
|
||||
blockheight.put("2019-02-01", 1761435L);
|
||||
blockheight.put("2019-03-01", 1781681L);
|
||||
blockheight.put("2019-04-01", 1803081L);
|
||||
blockheight.put("2019-05-01", 1824671L);
|
||||
blockheight.put("2019-06-01", 1847005L);
|
||||
blockheight.put("2019-07-01", 1868590L);
|
||||
blockheight.put("2019-08-01", 1890878L);
|
||||
blockheight.put("2019-09-01", 1913201L);
|
||||
blockheight.put("2019-10-01", 1934732L);
|
||||
blockheight.put("2019-11-01", 1957051L);
|
||||
blockheight.put("2019-12-01", 1978433L);
|
||||
blockheight.put("2020-01-01", 2001315L);
|
||||
blockheight.put("2020-02-01", 2023656L);
|
||||
blockheight.put("2020-03-01", 2044552L);
|
||||
blockheight.put("2020-04-01", 2066806L);
|
||||
blockheight.put("2020-05-01", 2088411L);
|
||||
blockheight.put("2020-06-01", 2110702L);
|
||||
blockheight.put("2020-07-01", 2132318L);
|
||||
blockheight.put("2020-08-01", 2154590L);
|
||||
blockheight.put("2020-09-01", 2176790L);
|
||||
blockheight.put("2020-10-01", 2198370L);
|
||||
blockheight.put("2020-11-01", 2220670L);
|
||||
blockheight.put("2020-12-01", 2242241L);
|
||||
blockheight.put("2021-01-01", 2264584L);
|
||||
blockheight.put("2021-02-01", 2286892L);
|
||||
blockheight.put("2021-03-01", 2307079L);
|
||||
blockheight.put("2021-04-01", 2329385L);
|
||||
blockheight.put("2021-05-01", 2351004L);
|
||||
blockheight.put("2021-06-01", 2373306L);
|
||||
blockheight.put("2021-07-01", 2394882L);
|
||||
blockheight.put("2021-08-01", 2417162L);
|
||||
blockheight.put("2021-09-01", 2439490L);
|
||||
blockheight.put("2021-10-01", 2461020L);
|
||||
blockheight.put("2021-11-01", 2483377L);
|
||||
blockheight.put("2021-12-01", 2504932L);
|
||||
blockheight.put("2022-01-01", 2527316L);
|
||||
blockheight.put("2022-02-01", 2549605L);
|
||||
blockheight.put("2022-03-01", 2569711L);
|
||||
init {
|
||||
blockheight["2014-05-01"] = 18844L
|
||||
blockheight["2014-06-01"] = 65406L
|
||||
blockheight["2014-07-01"] = 108882L
|
||||
blockheight["2014-08-01"] = 153594L
|
||||
blockheight["2014-09-01"] = 198072L
|
||||
blockheight["2014-10-01"] = 241088L
|
||||
blockheight["2014-11-01"] = 285305L
|
||||
blockheight["2014-12-01"] = 328069L
|
||||
blockheight["2015-01-01"] = 372369L
|
||||
blockheight["2015-02-01"] = 416505L
|
||||
blockheight["2015-03-01"] = 456631L
|
||||
blockheight["2015-04-01"] = 501084L
|
||||
blockheight["2015-05-01"] = 543973L
|
||||
blockheight["2015-06-01"] = 588326L
|
||||
blockheight["2015-07-01"] = 631187L
|
||||
blockheight["2015-08-01"] = 675484L
|
||||
blockheight["2015-09-01"] = 719725L
|
||||
blockheight["2015-10-01"] = 762463L
|
||||
blockheight["2015-11-01"] = 806528L
|
||||
blockheight["2015-12-01"] = 849041L
|
||||
blockheight["2016-01-01"] = 892866L
|
||||
blockheight["2016-02-01"] = 936736L
|
||||
blockheight["2016-03-01"] = 977691L
|
||||
blockheight["2016-04-01"] = 1015848L
|
||||
blockheight["2016-05-01"] = 1037417L
|
||||
blockheight["2016-06-01"] = 1059651L
|
||||
blockheight["2016-07-01"] = 1081269L
|
||||
blockheight["2016-08-01"] = 1103630L
|
||||
blockheight["2016-09-01"] = 1125983L
|
||||
blockheight["2016-10-01"] = 1147617L
|
||||
blockheight["2016-11-01"] = 1169779L
|
||||
blockheight["2016-12-01"] = 1191402L
|
||||
blockheight["2017-01-01"] = 1213861L
|
||||
blockheight["2017-02-01"] = 1236197L
|
||||
blockheight["2017-03-01"] = 1256358L
|
||||
blockheight["2017-04-01"] = 1278622L
|
||||
blockheight["2017-05-01"] = 1300239L
|
||||
blockheight["2017-06-01"] = 1322564L
|
||||
blockheight["2017-07-01"] = 1344225L
|
||||
blockheight["2017-08-01"] = 1366664L
|
||||
blockheight["2017-09-01"] = 1389113L
|
||||
blockheight["2017-10-01"] = 1410738L
|
||||
blockheight["2017-11-01"] = 1433039L
|
||||
blockheight["2017-12-01"] = 1454639L
|
||||
blockheight["2018-01-01"] = 1477201L
|
||||
blockheight["2018-02-01"] = 1499599L
|
||||
blockheight["2018-03-01"] = 1519796L
|
||||
blockheight["2018-04-01"] = 1542067L
|
||||
blockheight["2018-05-01"] = 1562861L
|
||||
blockheight["2018-06-01"] = 1585135L
|
||||
blockheight["2018-07-01"] = 1606715L
|
||||
blockheight["2018-08-01"] = 1629017L
|
||||
blockheight["2018-09-01"] = 1651347L
|
||||
blockheight["2018-10-01"] = 1673031L
|
||||
blockheight["2018-11-01"] = 1695128L
|
||||
blockheight["2018-12-01"] = 1716687L
|
||||
blockheight["2019-01-01"] = 1738923L
|
||||
blockheight["2019-02-01"] = 1761435L
|
||||
blockheight["2019-03-01"] = 1781681L
|
||||
blockheight["2019-04-01"] = 1803081L
|
||||
blockheight["2019-05-01"] = 1824671L
|
||||
blockheight["2019-06-01"] = 1847005L
|
||||
blockheight["2019-07-01"] = 1868590L
|
||||
blockheight["2019-08-01"] = 1890878L
|
||||
blockheight["2019-09-01"] = 1913201L
|
||||
blockheight["2019-10-01"] = 1934732L
|
||||
blockheight["2019-11-01"] = 1957051L
|
||||
blockheight["2019-12-01"] = 1978433L
|
||||
blockheight["2020-01-01"] = 2001315L
|
||||
blockheight["2020-02-01"] = 2023656L
|
||||
blockheight["2020-03-01"] = 2044552L
|
||||
blockheight["2020-04-01"] = 2066806L
|
||||
blockheight["2020-05-01"] = 2088411L
|
||||
blockheight["2020-06-01"] = 2110702L
|
||||
blockheight["2020-07-01"] = 2132318L
|
||||
blockheight["2020-08-01"] = 2154590L
|
||||
blockheight["2020-09-01"] = 2176790L
|
||||
blockheight["2020-10-01"] = 2198370L
|
||||
blockheight["2020-11-01"] = 2220670L
|
||||
blockheight["2020-12-01"] = 2242241L
|
||||
blockheight["2021-01-01"] = 2264584L
|
||||
blockheight["2021-02-01"] = 2286892L
|
||||
blockheight["2021-03-01"] = 2307079L
|
||||
blockheight["2021-04-01"] = 2329385L
|
||||
blockheight["2021-05-01"] = 2351004L
|
||||
blockheight["2021-06-01"] = 2373306L
|
||||
blockheight["2021-07-01"] = 2394882L
|
||||
blockheight["2021-08-01"] = 2417162L
|
||||
blockheight["2021-09-01"] = 2439490L
|
||||
blockheight["2021-10-01"] = 2461020L
|
||||
blockheight["2021-11-01"] = 2483377L
|
||||
blockheight["2021-12-01"] = 2504932L
|
||||
blockheight["2022-01-01"] = 2527316L
|
||||
blockheight["2022-02-01"] = 2549605L
|
||||
blockheight["2022-03-01"] = 2569711L
|
||||
}
|
||||
|
||||
static public RestoreHeight getInstance() {
|
||||
if (Singleton == null) {
|
||||
synchronized (RestoreHeight.class) {
|
||||
if (Singleton == null) {
|
||||
Singleton = new RestoreHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Singleton;
|
||||
}
|
||||
|
||||
public long getHeight(String date) {
|
||||
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
|
||||
parser.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
parser.setLenient(false);
|
||||
try {
|
||||
return getHeight(parser.parse(date));
|
||||
} catch (ParseException ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
fun getHeight(date: String?): Long {
|
||||
val parser = SimpleDateFormat("yyyy-MM-dd")
|
||||
parser.timeZone = TimeZone.getTimeZone("UTC")
|
||||
parser.isLenient = false
|
||||
return try {
|
||||
getHeight(parser.parse(date))
|
||||
} catch (ex: ParseException) {
|
||||
throw IllegalArgumentException(ex)
|
||||
}
|
||||
}
|
||||
|
||||
public long getHeight(final Date date) {
|
||||
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
cal.set(Calendar.DST_OFFSET, 0);
|
||||
cal.setTime(date);
|
||||
cal.add(Calendar.DAY_OF_MONTH, -4); // give it some leeway
|
||||
if (cal.get(Calendar.YEAR) < 2014)
|
||||
return 0;
|
||||
if ((cal.get(Calendar.YEAR) == 2014) && (cal.get(Calendar.MONTH) <= 3))
|
||||
// before May 2014
|
||||
return 0;
|
||||
|
||||
Calendar query = (Calendar) cal.clone();
|
||||
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
||||
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
||||
String queryDate = formatter.format(date);
|
||||
|
||||
cal.set(Calendar.DAY_OF_MONTH, 1);
|
||||
long prevTime = cal.getTimeInMillis();
|
||||
String prevDate = formatter.format(prevTime);
|
||||
fun getHeight(date: Date?): Long {
|
||||
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
cal[Calendar.DST_OFFSET] = 0
|
||||
cal.time = date
|
||||
cal.add(Calendar.DAY_OF_MONTH, -4) // give it some leeway
|
||||
if (cal[Calendar.YEAR] < 2014) return 0
|
||||
if (cal[Calendar.YEAR] == 2014 && cal[Calendar.MONTH] <= 3) // before May 2014
|
||||
return 0
|
||||
val query = cal.clone() as Calendar
|
||||
val formatter = SimpleDateFormat("yyyy-MM-dd")
|
||||
formatter.timeZone = TimeZone.getTimeZone("UTC")
|
||||
val queryDate = formatter.format(date)
|
||||
cal[Calendar.DAY_OF_MONTH] = 1
|
||||
var prevTime = cal.timeInMillis
|
||||
var prevDate = formatter.format(prevTime)
|
||||
// lookup blockheight at first of the month
|
||||
Long prevBc = blockheight.get(prevDate);
|
||||
var prevBc = blockheight[prevDate]
|
||||
if (prevBc == null) {
|
||||
// if too recent, go back in time and find latest one we have
|
||||
while (prevBc == null) {
|
||||
cal.add(Calendar.MONTH, -1);
|
||||
if (cal.get(Calendar.YEAR) < 2014) {
|
||||
throw new IllegalStateException("endless loop looking for blockheight");
|
||||
}
|
||||
prevTime = cal.getTimeInMillis();
|
||||
prevDate = formatter.format(prevTime);
|
||||
prevBc = blockheight.get(prevDate);
|
||||
cal.add(Calendar.MONTH, -1)
|
||||
check(cal[Calendar.YEAR] >= 2014) { "endless loop looking for blockheight" }
|
||||
prevTime = cal.timeInMillis
|
||||
prevDate = formatter.format(prevTime)
|
||||
prevBc = blockheight[prevDate]
|
||||
}
|
||||
}
|
||||
long height = prevBc;
|
||||
var height: Long = prevBc
|
||||
// now we have a blockheight & a date ON or BEFORE the restore date requested
|
||||
if (queryDate.equals(prevDate)) return height;
|
||||
if (queryDate == prevDate) return height
|
||||
// see if we have a blockheight after this date
|
||||
cal.add(Calendar.MONTH, 1);
|
||||
long nextTime = cal.getTimeInMillis();
|
||||
String nextDate = formatter.format(nextTime);
|
||||
Long nextBc = blockheight.get(nextDate);
|
||||
cal.add(Calendar.MONTH, 1)
|
||||
val nextTime = cal.timeInMillis
|
||||
val nextDate = formatter.format(nextTime)
|
||||
val nextBc = blockheight[nextDate]
|
||||
height =
|
||||
if (nextBc != null) { // we have a range - interpolate the blockheight we are looking for
|
||||
long diff = nextBc - prevBc;
|
||||
long diffDays = TimeUnit.DAYS.convert(nextTime - prevTime, TimeUnit.MILLISECONDS);
|
||||
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime,
|
||||
TimeUnit.MILLISECONDS);
|
||||
height = Math.round(prevBc + diff * (1.0 * days / diffDays));
|
||||
val diff = nextBc - prevBc
|
||||
val diffDays = TimeUnit.DAYS.convert(
|
||||
nextTime - prevTime,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
val days = TimeUnit.DAYS.convert(
|
||||
query.timeInMillis - prevTime,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
Math.round(prevBc + diff * (1.0 * days / diffDays))
|
||||
} else {
|
||||
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime,
|
||||
TimeUnit.MILLISECONDS);
|
||||
height = Math.round(prevBc + 1.0 * days * (24f * 60 * 60 / DIFFICULTY_TARGET));
|
||||
val days = TimeUnit.DAYS.convert(
|
||||
query.timeInMillis - prevTime,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
Math.round(prevBc + 1.0 * days * (24f * 60 * 60 / DIFFICULTY_TARGET))
|
||||
}
|
||||
return height
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DIFFICULTY_TARGET = 120 // seconds
|
||||
private var Singleton: RestoreHeight? = null
|
||||
@JvmStatic
|
||||
val instance: RestoreHeight?
|
||||
get() {
|
||||
if (Singleton == null) {
|
||||
synchronized(RestoreHeight::class.java) {
|
||||
if (Singleton == null) {
|
||||
Singleton = RestoreHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
return Singleton
|
||||
}
|
||||
return height;
|
||||
}
|
||||
}
|
|
@ -13,46 +13,46 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package net.mynero.wallet.util
|
||||
|
||||
package net.mynero.wallet.util;
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.ColorInt
|
||||
import net.mynero.wallet.R
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import net.mynero.wallet.R;
|
||||
|
||||
public class ThemeHelper {
|
||||
static public int getThemedResourceId(Context ctx, int attrId) {
|
||||
final TypedValue typedValue = new TypedValue();
|
||||
if (ctx.getTheme().resolveAttribute(attrId, typedValue, true))
|
||||
return typedValue.resourceId;
|
||||
else
|
||||
return 0;
|
||||
object ThemeHelper {
|
||||
fun getThemedResourceId(ctx: Context, attrId: Int): Int {
|
||||
val typedValue = TypedValue()
|
||||
return if (ctx.theme.resolveAttribute(
|
||||
attrId,
|
||||
typedValue,
|
||||
true
|
||||
)
|
||||
) typedValue.resourceId else 0
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@ColorInt
|
||||
static public int getThemedColor(Context ctx, int attrId) {
|
||||
final TypedValue typedValue = new TypedValue();
|
||||
if (ctx.getTheme().resolveAttribute(attrId, typedValue, true))
|
||||
return typedValue.data;
|
||||
else
|
||||
return Color.BLACK;
|
||||
fun getThemedColor(ctx: Context, attrId: Int): Int {
|
||||
val typedValue = TypedValue()
|
||||
return if (ctx.theme.resolveAttribute(
|
||||
attrId,
|
||||
typedValue,
|
||||
true
|
||||
)
|
||||
) typedValue.data else Color.BLACK
|
||||
}
|
||||
|
||||
public static void setTheme(@NonNull Activity activity, @NonNull String theme) {
|
||||
activity.setTheme(R.style.MyMaterialThemeOled);
|
||||
fun setTheme(activity: Activity, theme: String) {
|
||||
activity.setTheme(R.style.MyMaterialThemeOled)
|
||||
}
|
||||
|
||||
public static void setPreferred(Activity activity) {
|
||||
final String theme = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getString(activity.getString(R.string.preferred_theme), "Classic");
|
||||
setTheme(activity, theme);
|
||||
fun setPreferred(activity: Activity) {
|
||||
val theme = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getString(activity.getString(R.string.preferred_theme), "Classic")
|
||||
theme?.let { setTheme(activity, it) }
|
||||
}
|
||||
|
||||
}
|
|
@ -1,65 +1,55 @@
|
|||
package net.mynero.wallet.util;
|
||||
package net.mynero.wallet.util
|
||||
|
||||
import net.mynero.wallet.model.Wallet;
|
||||
import net.mynero.wallet.model.WalletManager;
|
||||
import net.mynero.wallet.model.Wallet.Companion.getPaymentIdFromAddress
|
||||
import net.mynero.wallet.model.Wallet.Companion.isAddressValid
|
||||
import net.mynero.wallet.model.WalletManager.Companion.instance
|
||||
|
||||
import java.util.HashMap;
|
||||
class UriData(@JvmField val address: String, val params: HashMap<String, String>) {
|
||||
|
||||
public class UriData {
|
||||
private final String address;
|
||||
private final HashMap<String, String> params;
|
||||
|
||||
public UriData(String address, HashMap<String, String> params) {
|
||||
this.address = address;
|
||||
this.params = params;
|
||||
fun hasPaymentId(): Boolean {
|
||||
return instance?.wallet?.nettype()?.let { getPaymentIdFromAddress(address, it) }?.isNotEmpty() == true
|
||||
}
|
||||
|
||||
public static UriData parse(String uri) {
|
||||
HashMap<String, String> params = new HashMap<>();
|
||||
String[] uriParts = uri.replace(Constants.URI_PREFIX, "").split("\\?");
|
||||
String finalAddress = uriParts[0];
|
||||
String queryParams = "";
|
||||
if (uriParts.length > 1) {
|
||||
queryParams = uriParts[1];
|
||||
String[] queryParts = queryParams.split("&");
|
||||
for (String param : queryParts) {
|
||||
String[] paramParts = param.split("=");
|
||||
if(paramParts.length == 2) {
|
||||
String variable = paramParts[0];
|
||||
String value = paramParts[1];
|
||||
params.put(variable, value);
|
||||
val amount: String?
|
||||
get() = params[Constants.URI_ARG_AMOUNT]
|
||||
?: params[Constants.URI_ARG_AMOUNT2]
|
||||
|
||||
fun hasAmount(): Boolean {
|
||||
return params.containsKey(Constants.URI_ARG_AMOUNT) || params.containsKey(Constants.URI_ARG_AMOUNT2)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun parse(uri: String): UriData? {
|
||||
if(uri.isEmpty()) return null
|
||||
val params = HashMap<String, String>()
|
||||
val uriParts = uri.replace(Constants.URI_PREFIX, "").split("\\?".toRegex())
|
||||
.dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()
|
||||
|
||||
if(uriParts.isEmpty()) return null
|
||||
val finalAddress = uriParts[0]
|
||||
var queryParams = ""
|
||||
if (uriParts.size > 1) {
|
||||
queryParams = uriParts[1]
|
||||
val queryParts =
|
||||
queryParams.split("&".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
for (param in queryParts) {
|
||||
val paramParts =
|
||||
param.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
if (paramParts.size == 2) {
|
||||
val variable = paramParts[0]
|
||||
val value = paramParts[1]
|
||||
params[variable] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean valid = Wallet.isAddressValid(finalAddress);
|
||||
if (valid) {
|
||||
return new UriData(finalAddress, params);
|
||||
val valid = isAddressValid(finalAddress)
|
||||
return if (valid) {
|
||||
UriData(finalAddress, params)
|
||||
} else {
|
||||
return null;
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
public HashMap<String, String> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public boolean hasPaymentId() {
|
||||
return !Wallet.getPaymentIdFromAddress(this.address, WalletManager.getInstance().getWallet().nettype()).isEmpty();
|
||||
}
|
||||
|
||||
public String getAmount() {
|
||||
String txAmount = params.get(Constants.URI_ARG_AMOUNT);
|
||||
if (txAmount == null) {
|
||||
return params.get(Constants.URI_ARG_AMOUNT2);
|
||||
}
|
||||
return txAmount;
|
||||
}
|
||||
|
||||
public boolean hasAmount() {
|
||||
return params.containsKey(Constants.URI_ARG_AMOUNT) || params.containsKey(Constants.URI_ARG_AMOUNT2);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.9.21'
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.1'
|
||||
classpath 'com.android.tools.build:gradle:8.1.3'
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.2"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,3 +18,6 @@ org.gradle.jvmargs=-Xmx2048m
|
|||
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
android.nonTransitiveRClass=false
|
||||
android.nonFinalResIds=false
|
||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
#Fri Feb 10 17:24:42 CST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
Loading…
Reference in a new issue