Refactoring (WIP): Convert to Kotlin

This commit is contained in:
pokkst 2023-12-06 10:49:36 -06:00
parent de35fbc457
commit c1f4b46b3b
No known key found for this signature in database
GPG key ID: EC4FAAA66859FAA4
61 changed files with 2428 additions and 3257 deletions

View file

@ -1,5 +1,6 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: "androidx.navigation.safeargs" apply plugin: "androidx.navigation.safeargs"
apply plugin: 'kotlin-android'
android { android {
compileSdkVersion 34 compileSdkVersion 34
@ -96,8 +97,8 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_9 sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_1_9 targetCompatibility JavaVersion.VERSION_17
} }
namespace 'net.mynero.wallet' namespace 'net.mynero.wallet'
buildFeatures { buildFeatures {
@ -112,16 +113,16 @@ static def getId(name) {
} }
dependencies { 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.appcompat:appcompat:1.6.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' 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.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 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.ncorti:slidetoact:0.9.0'
implementation 'com.journeyapps:zxing-android-embedded:4.3.0' implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
implementation "com.jakewharton.timber:timber:5.0.1" implementation "com.jakewharton.timber:timber:5.0.1"
@ -129,10 +130,13 @@ dependencies {
implementation fileTree(dir: 'libs/classes', include: ['*.jar']) implementation fileTree(dir: 'libs/classes', include: ['*.jar'])
implementation 'org.slf4j:slf4j-nop:1.7.36' implementation 'org.slf4j:slf4j-nop:1.7.36'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
implementation 'androidx.navigation:navigation-fragment:2.5.3' implementation 'androidx.navigation:navigation-fragment:2.7.5'
implementation 'androidx.navigation:navigation-ui:2.5.3' 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 //noinspection GradleDependency
testImplementation "junit:junit:4.13.2" testImplementation "junit:junit:4.13.2"

View file

@ -1004,33 +1004,6 @@ Java_net_mynero_wallet_model_Wallet_getDeviceTypeJ(JNIEnv *env, jobject instance
return static_cast<jint>(device_type); 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 JNIEXPORT jstring JNICALL
Java_net_mynero_wallet_model_Wallet_getDisplayAmount(JNIEnv *env, jclass clazz, Java_net_mynero_wallet_model_Wallet_getDisplayAmount(JNIEnv *env, jclass clazz,
jlong amount) { jlong amount) {

View file

@ -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());
}
}

View 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()
}
}
}

View file

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

View 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)
}
}

View file

@ -60,7 +60,7 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter<CoinsInfoAdapter.View
} }
public void deselectUtxo(CoinsInfo coinsInfo) { public void deselectUtxo(CoinsInfo coinsInfo) {
this.selectedUtxos.remove(coinsInfo.getPubKey()); this.selectedUtxos.remove(coinsInfo.pubKey);
if(this.selectedUtxos.size() == 0) { if(this.selectedUtxos.size() == 0) {
editing = false; editing = false;
@ -71,12 +71,12 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter<CoinsInfoAdapter.View
public void selectUtxo(CoinsInfo coinsInfo) { public void selectUtxo(CoinsInfo coinsInfo) {
editing = true; editing = true;
this.selectedUtxos.put(coinsInfo.getPubKey(), coinsInfo); this.selectedUtxos.put(coinsInfo.pubKey, coinsInfo);
notifyDataSetChanged(); notifyDataSetChanged();
} }
public boolean contains(CoinsInfo coinsInfo) { public boolean contains(CoinsInfo coinsInfo) {
return selectedUtxos.containsKey(coinsInfo.getPubKey()); return selectedUtxos.containsKey(coinsInfo.pubKey);
} }
public void clear() { 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) { public void bind(boolean editing, CoinsInfo coinsInfo, HashMap<String, CoinsInfo> selectedUtxos) {
this.editing = editing; this.editing = editing;
this.coinsInfo = coinsInfo; 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 pubKeyTextView = itemView.findViewById(R.id.utxo_pub_key_textview);
TextView amountTextView = itemView.findViewById(R.id.utxo_amount_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 globalIdxTextView = itemView.findViewById(R.id.utxo_global_index_textview);
TextView outpointTextView = itemView.findViewById(R.id.utxo_outpoint_textview); TextView outpointTextView = itemView.findViewById(R.id.utxo_outpoint_textview);
boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false); 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)); amountTextView.setText(itemView.getResources().getString(R.string.tx_amount_no_prefix, balanceString));
pubKeyTextView.setText(coinsInfo.getPubKey()); pubKeyTextView.setText(coinsInfo.pubKey);
addressTextView.setText(coinsInfo.getAddress()); addressTextView.setText(coinsInfo.address);
globalIdxTextView.setText(itemView.getResources().getString(R.string.global_index_text, coinsInfo.getGlobalOutputIndex())); globalIdxTextView.setText(itemView.getResources().getString(R.string.global_index_text, coinsInfo.globalOutputIndex));
outpointTextView.setText(itemView.getResources().getString(R.string.outpoint_text, coinsInfo.getHash() + ":" + coinsInfo.getLocalOutputIndex())); outpointTextView.setText(itemView.getResources().getString(R.string.outpoint_text, coinsInfo.hash + ":" + coinsInfo.localOutputIndex));
if (selected) { if (selected) {
itemView.setBackgroundTintList(ContextCompat.getColorStateList(itemView.getContext(), R.color.oled_colorSecondary)); itemView.setBackgroundTintList(ContextCompat.getColorStateList(itemView.getContext(), R.color.oled_colorSecondary));
} else if(coinsInfo.isFrozen() || UTXOService.instance.isCoinFrozen(coinsInfo)) { } else if(coinsInfo.isFrozen() || UTXOService.instance.isCoinFrozen(coinsInfo)) {

View file

@ -99,14 +99,14 @@ public class SubaddressAdapter extends RecyclerView.Adapter<SubaddressAdapter.Vi
TextView addressLabelTextView = itemView.findViewById(R.id.address_label_textview); TextView addressLabelTextView = itemView.findViewById(R.id.address_label_textview);
TextView addressAmountTextView = itemView.findViewById(R.id.address_amount_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 label = subaddress.getDisplayLabel();
final String address = itemView.getContext().getString(R.string.subbaddress_info_subtitle, 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); addressLabelTextView.setText(label.isEmpty() ? address : label);
final long amount = subaddress.getAmount(); final long amount = subaddress.amount;
if (amount > 0) { if (amount > 0) {
boolean streetMode = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false); boolean streetMode = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
if(streetMode) { if(streetMode) {

View file

@ -30,9 +30,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.progressindicator.CircularProgressIndicator; import com.google.android.material.progressindicator.CircularProgressIndicator;
import net.mynero.wallet.R; import net.mynero.wallet.R;
import net.mynero.wallet.data.UserNotes;
import net.mynero.wallet.model.TransactionInfo; import net.mynero.wallet.model.TransactionInfo;
import net.mynero.wallet.model.Wallet;
import net.mynero.wallet.service.PrefService; import net.mynero.wallet.service.PrefService;
import net.mynero.wallet.util.Constants; import net.mynero.wallet.util.Constants;
import net.mynero.wallet.util.Helper; import net.mynero.wallet.util.Helper;

View file

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

View 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
}
}

View file

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

View 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
}
}

View file

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

View 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}$")
}
}

View file

@ -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();
}
}

View 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)
}
}
}

View file

@ -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();
}
}

View 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()
}
}

View file

@ -106,7 +106,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
donateTextView = view.findViewById(R.id.donate_label_textview); donateTextView = view.findViewById(R.id.donate_label_textview);
donateTextView.setOnClickListener(view1 -> addressEditText.setText(Constants.DONATE_ADDRESS)); donateTextView.setOnClickListener(view1 -> addressEditText.setText(Constants.DONATE_ADDRESS));
if (uriData != null) { if (uriData != null) {
addressEditText.setText(uriData.getAddress()); addressEditText.setText(uriData.address);
if (uriData.hasAmount()) { if (uriData.hasAmount()) {
amountEditText.setText(uriData.getAmount()); amountEditText.setText(uriData.getAmount());
} }
@ -116,8 +116,8 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
long selectedValue = 0; long selectedValue = 0;
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) { for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
if (selectedUtxos.contains(coinsInfo.getKeyImage())) { if (selectedUtxos.contains(coinsInfo.keyImage)) {
selectedValue += coinsInfo.getAmount(); selectedValue += coinsInfo.amount;
} }
} }
@ -338,7 +338,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
private void pasteAddress(String address) { private void pasteAddress(String address) {
UriData uriData = UriData.parse(address); UriData uriData = UriData.parse(address);
if (uriData != null) { if (uriData != null) {
addressEditText.setText(uriData.getAddress()); addressEditText.setText(uriData.address);
if (uriData.hasAmount()) { if (uriData.hasAmount()) {
amountEditText.setText(uriData.getAmount()); amountEditText.setText(uriData.getAmount());
} }

View file

@ -132,7 +132,7 @@ public class OnboardingViewModel extends ViewModel {
} else { } else {
mainActivity.runOnUiThread(() -> { mainActivity.runOnUiThread(() -> {
_enableCreateButton.postValue(true); _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();
}); });
} }
}); });

View file

@ -91,7 +91,7 @@ public class ReceiveFragment extends Fragment {
private void editAddressLabel(Subaddress subaddress) { private void editAddressLabel(Subaddress subaddress) {
EditAddressLabelBottomSheetDialog dialog = new EditAddressLabelBottomSheetDialog(); EditAddressLabelBottomSheetDialog dialog = new EditAddressLabelBottomSheetDialog();
dialog.addressIndex = subaddress.getAddressIndex(); dialog.addressIndex = subaddress.addressIndex;
dialog.listener = () -> mViewModel.init(); dialog.listener = () -> mViewModel.init();
dialog.show(getParentFragmentManager(), "edit_address_dialog"); dialog.show(getParentFragmentManager(), "edit_address_dialog");
} }
@ -99,11 +99,11 @@ public class ReceiveFragment extends Fragment {
private void setAddress(Subaddress subaddress) { private void setAddress(Subaddress subaddress) {
final String label = subaddress.getDisplayLabel(); final String label = subaddress.getDisplayLabel();
final String address = getContext().getString(R.string.subbaddress_info_subtitle, final String address = getContext().getString(R.string.subbaddress_info_subtitle,
subaddress.getAddressIndex(), subaddress.getSquashedAddress()); subaddress.addressIndex, subaddress.getSquashedAddress());
addressLabelTextView.setText(label.isEmpty() ? address : label); addressLabelTextView.setText(label.isEmpty() ? address : label);
addressTextView.setText(subaddress.getAddress()); addressTextView.setText(subaddress.address);
addressImageView.setImageBitmap(generate(subaddress.getAddress(), 256, 256)); addressImageView.setImageBitmap(generate(subaddress.address, 256, 256));
copyAddressImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "address", subaddress.getAddress())); copyAddressImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "address", subaddress.address));
addressLabelTextView.setOnLongClickListener(v -> { addressLabelTextView.setOnLongClickListener(v -> {
editAddressLabel(subaddress); editAddressLabel(subaddress);
return true; return true;

View file

@ -411,7 +411,7 @@ public class SendFragment extends Fragment {
mViewModel.setShowAddOutputButton(false); mViewModel.setShowAddOutputButton(false);
} }
EditText addressField = entryView.findViewById(R.id.address_edittext); EditText addressField = entryView.findViewById(R.id.address_edittext);
addressField.setText(uriData.getAddress()); addressField.setText(uriData.address);
if (uriData.hasAmount()) { if (uriData.hasAmount()) {
setAmount(entryView, uriData.getAmount()); setAmount(entryView, uriData.getAmount());
} }

View file

@ -72,7 +72,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
sendUtxosButton.setOnClickListener(view1 -> { sendUtxosButton.setOnClickListener(view1 -> {
ArrayList<String> selectedKeyImages = new ArrayList<>(); ArrayList<String> selectedKeyImages = new ArrayList<>();
for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) { for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) {
selectedKeyImages.add(coinsInfo.getKeyImage()); selectedKeyImages.add(coinsInfo.keyImage);
} }
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog(); SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
sendDialog.listener = this; sendDialog.listener = this;
@ -82,12 +82,12 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
churnUtxosButton.setOnClickListener(view1 -> { churnUtxosButton.setOnClickListener(view1 -> {
ArrayList<String> selectedKeyImages = new ArrayList<>(); ArrayList<String> selectedKeyImages = new ArrayList<>();
for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) { for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) {
selectedKeyImages.add(coinsInfo.getKeyImage()); selectedKeyImages.add(coinsInfo.keyImage);
} }
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog(); SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
sendDialog.listener = this; sendDialog.listener = this;
sendDialog.isChurning = true; sendDialog.isChurning = true;
sendDialog.uriData = UriData.parse(AddressService.getInstance().currentSubaddress().getAddress()); sendDialog.uriData = UriData.parse(AddressService.getInstance().currentSubaddress().address);
sendDialog.selectedUtxos = selectedKeyImages; sendDialog.selectedUtxos = selectedKeyImages;
sendDialog.show(getActivity().getSupportFragmentManager(), null); sendDialog.show(getActivity().getSupportFragmentManager(), null);
}); });
@ -103,7 +103,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
HashMap<String, CoinsInfo> filteredUtxos = new HashMap<>(); HashMap<String, CoinsInfo> filteredUtxos = new HashMap<>();
for (CoinsInfo coinsInfo : utxos) { for (CoinsInfo coinsInfo : utxos) {
if (!coinsInfo.isSpent()) { if (!coinsInfo.isSpent()) {
filteredUtxos.put(coinsInfo.getPubKey(), coinsInfo); filteredUtxos.put(coinsInfo.pubKey, coinsInfo);
} }
} }
if (filteredUtxos.isEmpty()) { if (filteredUtxos.isEmpty()) {

View file

@ -13,65 +13,58 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package net.mynero.wallet.livedata
package net.mynero.wallet.livedata; import android.util.Log
import androidx.annotation.MainThread
import android.util.Log; import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.annotation.MainThread; import androidx.lifecycle.Observer
import androidx.annotation.Nullable; import java.util.concurrent.atomic.AtomicBoolean
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 * A lifecycle-aware observable that sends only new updates after subscription, used for events like
* navigation and Snackbar messages. * navigation and Snackbar messages.
* <p> *
*
* This avoids a common problem with events: on configuration change (like rotation) an update * 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 * can be emitted if the observer is active. This LiveData only calls the observable if there's an
* explicit call to setValue() or call(). * explicit call to setValue() or call().
* <p> *
*
* Note that only one observer is going to be notified of changes. * Note that only one observer is going to be notified of changes.
*/ */
public class SingleLiveEvent<T> extends MutableLiveData<T> { class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread @MainThread
@Override override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
public void observe(LifecycleOwner owner, final Observer<? super T> observer) {
if (hasActiveObservers()) { 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 // Observe the internal MutableLiveData
super.observe(owner, new Observer<T>() { super.observe(owner) { value ->
@Override
public void onChanged(@Nullable T t) {
if (mPending.compareAndSet(true, false)) { if (mPending.compareAndSet(true, false)) {
observer.onChanged(t); observer.onChanged(value)
} }
} }
});
} }
@MainThread @MainThread
public void setValue(@Nullable T t) { override fun setValue(t: T?) {
mPending.set(true); mPending.set(true)
super.setValue(t); super.setValue(t)
} }
/** /**
* Used for cases where T is Void, to make calls cleaner. * Used for cases where T is Void, to make calls cleaner.
*/ */
@MainThread @MainThread
public void call() { fun call() {
setValue(null); value = null
}
companion object {
private const val TAG = "SingleLiveEvent"
} }
} }

View file

@ -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);
}
}
}

View 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)
}
}
}

View file

@ -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();
}

View 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")
}
}
}

View file

@ -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);
}
}
}

View 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")
}
}
}

View file

@ -13,33 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 { companion object {
NetworkType_Mainnet(0), @JvmStatic
NetworkType_Testnet(1), fun fromInteger(n: Int): NetworkType? {
NetworkType_Stagenet(2); when (n) {
0 -> return NetworkType_Mainnet
private final int value; 1 -> return NetworkType_Testnet
2 -> return NetworkType_Stagenet
NetworkType(int value) { }
this.value = value; return null
} }
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;
}
public int getValue() {
return value;
} }
} }

View file

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

View file

@ -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")
}
}
}

View file

@ -13,69 +13,52 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package net.mynero.wallet.model
package net.mynero.wallet.model; import timber.log.Timber
import java.util.ArrayList; class TransactionHistory(private val handle: Long, var accountIndex: Int) {
import java.util.Iterator; var all: List<TransactionInfo> = ArrayList()
import java.util.List; private set
import timber.log.Timber; fun setAccountFor(wallet: Wallet) {
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) {
if (accountIndex != wallet.getAccountIndex()) { if (accountIndex != wallet.getAccountIndex()) {
this.accountIndex = wallet.getAccountIndex(); accountIndex = wallet.getAccountIndex()
refreshWithNotes(wallet); refreshWithNotes(wallet)
} }
} }
private void loadNotes(Wallet wallet) { private fun loadNotes(wallet: Wallet) {
for (TransactionInfo info : transactions) { for (info in all) {
info.notes = wallet.getUserNote(info.hash); info.notes = wallet.getUserNote(info.hash)
} }
} }
//private native long getTransactionByIndexJ(int i); external fun getCount(): Int
//private native long getTransactionByIdJ(String id); fun refreshWithNotes(wallet: Wallet) {
refresh()
public native int getCount(); // over all accounts/subaddresses loadNotes(wallet)
public List<TransactionInfo> getAll() {
return transactions;
} }
void refreshWithNotes(Wallet wallet) { private fun refresh() {
refresh(); val transactionInfos = refreshJ()
loadNotes(wallet); Timber.d("refresh size=%d", transactionInfos.size)
} val iterator = transactionInfos.iterator()
while (iterator.hasNext()) {
private void refresh() { val info = iterator.next()
List<TransactionInfo> transactionInfos = refreshJ();
Timber.d("refresh size=%d", transactionInfos.size());
for (Iterator<TransactionInfo> iterator = transactionInfos.iterator(); iterator.hasNext(); ) {
TransactionInfo info = iterator.next();
if (info.accountIndex != accountIndex) { 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")
}
}
} }

View file

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

View file

@ -0,0 +1,3 @@
package net.mynero.wallet.model
class TransactionOutput(@JvmField val destination: String, @JvmField val amount: Long)

View file

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

View 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)
}
}
}

View file

@ -13,566 +13,464 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; class Wallet {
import android.util.Pair; 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; internal constructor(handle: Long) {
import androidx.annotation.Nullable; this.handle = handle
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");
} }
boolean synced = false; internal constructor(handle: Long, accountIndex: Int) {
private int accountIndex = 0; this.handle = handle
private long handle = 0; this.accountIndex = accountIndex
private long listenerHandle = 0;
private PendingTransaction pendingTransaction = null;
private TransactionHistory history = null;
private Coins coins = null;
Wallet(long handle) {
this.handle = handle;
} }
Wallet(long handle, int accountIndex) { fun getAccountIndex(): Int {
this.handle = handle; return accountIndex
this.accountIndex = accountIndex;
} }
public static native String getDisplayAmount(long amount); fun setAccountIndex(accountIndex: Int) {
Timber.d("setAccountIndex(%d)", accountIndex)
public static native long getAmountFromString(String amount); this.accountIndex = accountIndex
history?.setAccountFor(this)
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());
} }
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(); val status: Status
get() = statusWithErrorString()
public int getAccountIndex() { val fullStatus: Status
return accountIndex; get() {
val walletStatus = statusWithErrorString()
walletStatus.connectionStatus = connectionStatus
return walletStatus
} }
public void setAccountIndex(int accountIndex) { private external fun statusWithErrorString(): Status
Timber.d("setAccountIndex(%d)", accountIndex); @Synchronized
this.accountIndex = accountIndex; external fun setPassword(password: String?): Boolean
getHistory().setAccountFor(this); val address: String
} get() = getAddress(accountIndex)
public String getName() {
return new File(getPath()).getName();
}
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();
}
public Status getFullStatus() {
Wallet.Status walletStatus = statusWithErrorString();
walletStatus.setConnectionStatus(getConnectionStatus());
return walletStatus;
}
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 void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0; //TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
fun getAddress(accountIndex: Int): String {
public String getAddress(int accountIndex) { return getAddressJ(accountIndex, 0)
return getAddressJ(accountIndex, 0);
} }
public String getSubaddress(int addressIndex) { private fun getSubaddress(addressIndex: Int): String {
return getAddressJ(accountIndex, addressIndex); return getAddressJ(accountIndex, addressIndex)
} }
public String getSubaddress(int accountIndex, int addressIndex) { fun getSubaddress(accountIndex: Int, addressIndex: Int): String {
return getAddressJ(accountIndex, addressIndex); return getAddressJ(accountIndex, addressIndex)
} }
private native String getAddressJ(int accountIndex, int addressIndex); private external fun getAddressJ(accountIndex: Int, addressIndex: Int): String
private fun getSubaddressObject(accountIndex: Int, subAddressIndex: Int): Subaddress {
public Subaddress getSubaddressObject(int accountIndex, int subAddressIndex) { return Subaddress(
return new Subaddress(accountIndex, subAddressIndex, accountIndex, subAddressIndex,
getSubaddress(subAddressIndex), getSubaddressLabel(subAddressIndex)); getSubaddress(subAddressIndex), getSubaddressLabel(subAddressIndex)
)
} }
public Subaddress getSubaddressObject(int subAddressIndex) { fun getSubaddressObject(subAddressIndex: Int): Subaddress {
Subaddress subaddress = getSubaddressObject(accountIndex, subAddressIndex); val subaddress = getSubaddressObject(accountIndex, subAddressIndex)
long amount = 0; var amount: Long = 0
for (TransactionInfo info : getHistory().getAll()) { history?.let { history ->
if ((info.addressIndex == subAddressIndex) for (info in history.all) {
&& (info.direction == TransactionInfo.Direction.Direction_In)) { if (info.addressIndex == subAddressIndex && info.direction == TransactionInfo.Direction.Direction_In) {
amount += info.amount; amount += info.amount
} }
} }
subaddress.setAmount(amount);
return subaddress;
} }
public native String getPath(); subaddress.amount = amount
return subaddress
public NetworkType getNetworkType() {
return NetworkType.fromInteger(nettype());
} }
external fun getPath(): String?
val networkType: NetworkType?
get() = fromInteger(nettype())
public native int nettype(); external fun nettype(): Int
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0; // 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; // virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
external fun getIntegratedAddress(paymentId: String?): String?
external fun getSecretViewKey(): String
external fun getSecretSpendKey(): String
public native String getIntegratedAddress(String payment_id); fun store(): Boolean {
return store("")
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 void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0; //TODO virtual bool trustedDaemon() const = 0;
@Synchronized
public native synchronized boolean store(String path); external fun store(path: String?): Boolean
fun close(): Boolean {
public boolean close() { disposePendingTransaction()
disposePendingTransaction(); return instance?.close(this) == true
return WalletManager.getInstance().close(this);
} }
public native String getFilename(); external fun getFilename(): String
// virtual std::string keysFilename() const = 0; // virtual std::string keysFilename() const = 0;
public boolean init(long upper_transaction_size_limit) { fun init(upperTransactionSizeLimit: Long): Boolean {
String daemon_address = WalletManager.getInstance().getDaemonAddress(); var daemonAddress = instance?.getDaemonAddress()
String daemon_username = WalletManager.getInstance().getDaemonUsername(); var daemonUsername = instance?.daemonUsername
String daemon_password = WalletManager.getInstance().getDaemonPassword(); var daemonPassword = instance?.daemonPassword
String proxy_address = WalletManager.getInstance().getProxy(); var proxyAddress = instance?.proxy
Log.d("Wallet.java", "init("); Timber.d("init(")
if (daemon_address != null) { if (daemonAddress != null) {
Log.d("Wallet.java", daemon_address.toString()); Timber.d(daemonAddress.toString())
} else { } else {
Log.d("Wallet.java", "daemon_address == null"); Timber.d("daemon_address == null")
daemon_address = ""; daemonAddress = ""
} }
Log.d("Wallet.java", "upper_transaction_size_limit = 0 (probably)"); Timber.d("upper_transaction_size_limit = 0 (probably)")
if (daemon_username != null) { if (daemonUsername != null) {
Log.d("Wallet.java", daemon_username.toString()); Timber.d(daemonUsername)
} else { } else {
Log.d("Wallet.java", "daemon_username == null"); Timber.d("daemon_username == null")
daemon_username = ""; daemonUsername = ""
} }
if (daemon_password != null) { if (daemonPassword != null) {
Log.d("Wallet.java", daemon_password.toString()); Timber.d(daemonPassword)
} else { } else {
Log.d("Wallet.java", "daemon_password == null"); Timber.d("daemon_password == null")
daemon_password = ""; daemonPassword = ""
} }
if (proxy_address != null) { if (proxyAddress != null) {
Log.d("Wallet.java", proxy_address.toString()); Timber.d(proxyAddress)
} else { } else {
Log.d("Wallet.java", "proxy_address == null"); Timber.d("proxy_address == null")
proxy_address = ""; proxyAddress = ""
} }
Log.d("Wallet.java", ");"); Timber.d(");")
return initJ(daemon_address, upper_transaction_size_limit, return initJ(
daemon_username, daemon_password, daemonAddress, upperTransactionSizeLimit,
proxy_address); 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, external fun getRestoreHeight(): Long
String daemon_username, String daemon_password, String proxy); external fun setRestoreHeight(height: Long)
public native long getRestoreHeight(); private val connectionStatus: ConnectionStatus
get() {
public native void setRestoreHeight(long height); val s = getConnectionStatusJ()
return ConnectionStatus.values()[s]
public ConnectionStatus getConnectionStatus() {
int s = getConnectionStatusJ();
return Wallet.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() { private external fun getBalance(accountIndex: Int): Long
return getBalance(accountIndex); 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); external fun startRefresh()
external fun pauseRefresh()
public native long getBalanceAll(); external fun refresh(): Boolean
external fun refreshAsync()
public long getUnlockedBalance() { private external fun rescanBlockchainAsyncJ()
return getUnlockedBalance(accountIndex); 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 void setAutoRefreshInterval(int millis) = 0;
//TODO virtual int autoRefreshInterval() const = 0; //TODO virtual int autoRefreshInterval() const = 0;
private fun disposePendingTransaction() {
public void disposePendingTransaction() {
if (pendingTransaction != null) { if (pendingTransaction != null) {
disposeTransaction(pendingTransaction); disposeTransaction(pendingTransaction)
pendingTransaction = null; pendingTransaction = null
} }
} }
public long estimateTransactionFee(List<Pair<String, Long>> destinations, PendingTransaction.Priority priority) { fun estimateTransactionFee(
int _priority = priority.getValue(); destinations: List<Pair<String, Long>>,
return estimateTransactionFee(destinations, _priority); 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) { fun createSweepTransaction(
disposePendingTransaction(); dstAddr: String,
int _priority = priority.getValue(); priority: PendingTransaction.Priority,
long txHandle = createSweepTransaction(dst_addr, "", 0, _priority, accountIndex, key_images); keyImages: ArrayList<String>
pendingTransaction = new PendingTransaction(txHandle); ): PendingTransaction? {
return 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) { fun createTransactionMultDest(
disposePendingTransaction(); outputs: List<TransactionOutput>,
int _priority = priority.getValue(); priority: PendingTransaction.Priority,
ArrayList<String> destinations = new ArrayList<>(); keyImages: ArrayList<String>
long[] amounts = new long[outputs.size()]; ): PendingTransaction? {
for(int i = 0; i < outputs.size(); i++) { disposePendingTransaction()
TransactionOutput output = outputs.get(i); val _priority = priority.value
destinations.add(output.getDestination()); val destinations = ArrayList<String>()
amounts[i] = output.getAmount(); 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, val txHandle = createTransactionMultDestJ(
accountIndex, key_images); destinations, "", amounts, 0, _priority,
pendingTransaction = new PendingTransaction(txHandle); accountIndex, keyImages
return pendingTransaction; )
pendingTransaction = PendingTransaction(txHandle)
return pendingTransaction
} }
private native long createTransactionMultDestJ(ArrayList<String> dst_addrs, String payment_id, private external fun createTransactionMultDestJ(
long[] amount, int mixin_count, dstAddrs: ArrayList<String>, paymentId: String,
int priority, int accountIndex, ArrayList<String> key_images); amount: LongArray, mixinCount: Int,
priority: Int, accountIndex: Int, keyImages: ArrayList<String>
): Long
private native long createTransactionJ(String dst_addr, String payment_id, private external fun createTransactionJ(
long amount, int mixin_count, dstAddr: String, paymentId: String,
int priority, int accountIndex, ArrayList<String> key_images); amount: Long, mixinCount: Int,
priority: Int, accountIndex: Int, keyImages: ArrayList<String>
): Long
private native long createSweepTransaction(String dst_addr, String payment_id, private external fun createSweepTransaction(
int mixin_count, dstAddr: String, paymentId: String,
int priority, int accountIndex, ArrayList<String> key_images); mixinCount: Int,
priority: Int, accountIndex: Int, keyImages: ArrayList<String>
): Long
public PendingTransaction createSweepUnmixableTransaction() { fun createSweepUnmixableTransaction(): PendingTransaction? {
disposePendingTransaction(); disposePendingTransaction()
long txHandle = createSweepUnmixableTransactionJ(); val txHandle = createSweepUnmixableTransactionJ()
pendingTransaction = new PendingTransaction(txHandle); pendingTransaction = PendingTransaction(txHandle)
return pendingTransaction; return pendingTransaction
} }
private native long createSweepUnmixableTransactionJ(); private external fun createSweepUnmixableTransactionJ(): Long
private external fun disposeTransaction(pendingTransaction: PendingTransaction?)
public native void disposeTransaction(PendingTransaction pendingTransaction); private external fun getHistoryJ(): Long
private external fun getCoinsJ(): Long
public TransactionHistory getHistory() {
if (history == null) {
history = new TransactionHistory(getHistoryJ(), accountIndex);
}
return history;
}
//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 exportKeyImages(const std::string &filename) = 0;
//virtual bool importKeyImages(const std::string &filename) = 0; //virtual bool importKeyImages(const std::string &filename) = 0;
//virtual TransactionHistory * history() const = 0; //virtual TransactionHistory * history() const = 0;
fun refreshHistory() {
public void refreshHistory() { history?.refreshWithNotes(this)
getHistory().refreshWithNotes(this);
} }
public void refreshCoins() { fun refreshCoins() {
if (this.isSynchronized()) { if (isSynchronized) {
getCoins().refresh(); coins?.refresh()
} }
} }
private native long setListenerJ(WalletListener listener); private external fun setListenerJ(listener: WalletListener): Long
fun setListener(listener: WalletListener) {
public void setListener(WalletListener listener) { listenerHandle = setListenerJ(listener)
this.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);
}
public native void addAccount(String label);
public String getAccountLabel() {
return getAccountLabel(accountIndex);
} }
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; //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; set(label) {
setAccountLabel(accountIndex, label)
//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) { private fun getAccountLabel(accountIndex: Int): String {
String label = getSubaddressLabel(accountIndex, 0); var label = getSubaddressLabel(accountIndex, 0)
if (label.equals(NEW_ACCOUNT_NAME)) { if (label == NEW_ACCOUNT_NAME) {
String address = getAddress(accountIndex); val address = getAddress(accountIndex)
int len = address.length(); val len = address.length
label = address.substring(0, 6) + 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) { fun getSubaddressLabel(addressIndex: Int): String {
return getSubaddressLabel(accountIndex, addressIndex); return getSubaddressLabel(accountIndex, addressIndex)
} }
public native String getSubaddressLabel(int accountIndex, int addressIndex); private external fun getSubaddressLabel(accountIndex: Int, addressIndex: Int): String
private fun setAccountLabel(accountIndex: Int, label: String?) {
public void setAccountLabel(int accountIndex, String label) { setSubaddressLabel(accountIndex, 0, label)
setSubaddressLabel(accountIndex, 0, label);
} }
public void setSubaddressLabel(int addressIndex, String label) { fun setSubaddressLabel(addressIndex: Int, label: String?) {
setSubaddressLabel(accountIndex, addressIndex, label); setSubaddressLabel(accountIndex, addressIndex, label)
refreshHistory(); 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() { private fun getNewSubaddress(accountIndex: Int): String {
return getNumSubaddresses(accountIndex); 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); external fun addSubaddress(accountIndex: Int, label: String?)
private fun getLastSubaddress(accountIndex: Int): String {
public String getNewSubaddress() { return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1)
return getNewSubaddress(accountIndex);
} }
public String getNewSubaddress(int accountIndex) { val deviceType: Device
String timeStamp = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).format(new Date()); get() {
addSubaddress(accountIndex, timeStamp); val device = getDeviceTypeJ()
String subaddress = getLastSubaddress(accountIndex); return Device.values()[device + 1] // mapping is monero+1=android
Timber.d("%d: %s", getNumSubaddresses(accountIndex) - 1, subaddress);
return subaddress;
} }
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() { enum class StatusEnum {
int device = getDeviceTypeJ(); Status_Ok, Status_Error, Status_Critical
return Wallet.Device.values()[device + 1]; // mapping is monero+1=android
} }
private native int getDeviceTypeJ(); enum class ConnectionStatus {
ConnectionStatus_Disconnected, ConnectionStatus_Connected, ConnectionStatus_WrongVersion
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;
} }
public int getAccountLookahead() { class Status internal constructor(status: Int, @JvmField val errorString: String) {
return accountLookahead; val status: StatusEnum
@JvmField
var connectionStatus: ConnectionStatus? = null // optional
init {
this.status = StatusEnum.values()[status]
} }
public int getSubaddressLookahead() { val isOk: Boolean
return subaddressLookahead; get() = (status == StatusEnum.Status_Ok
&& (connectionStatus == null || connectionStatus == ConnectionStatus.ConnectionStatus_Connected))
override fun toString(): String {
return "Wallet.Status: $status/$errorString/$connectionStatus"
} }
} }
public enum StatusEnum { companion object {
Status_Ok, const val SWEEP_ALL = Long.MAX_VALUE
Status_Error, private const val NEW_ACCOUNT_NAME = "Untitled account" // src/wallet/wallet2.cpp:941
Status_Critical
init {
System.loadLibrary("monerujo")
} }
public enum ConnectionStatus { @JvmStatic
ConnectionStatus_Disconnected, external fun getDisplayAmount(amount: Long): String
ConnectionStatus_Connected, @JvmStatic
ConnectionStatus_WrongVersion 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
} }
@JvmStatic
static public class Status { external fun isAddressValid(address: String?, networkType: Int): Boolean
final private StatusEnum status; @JvmStatic
final private String errorString; external fun getPaymentIdFromAddress(address: String?, networkType: Int): String?
@Nullable @JvmStatic
private ConnectionStatus connectionStatus; // optional external fun getMaximumAllowedAmount(): Long
Status(int status, String errorString) {
this.status = StatusEnum.values()[status];
this.errorString = errorString;
}
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;
} }
} }
}

View file

@ -13,17 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package net.mynero.wallet.model
package net.mynero.wallet.model; interface WalletListener {
public interface WalletListener {
/** /**
* moneySpent - called when money spent * moneySpent - called when money spent
* *
* @param txId - transaction id * @param txId - transaction id
* @param amount - tvAmount * @param amount - tvAmount
*/ */
void moneySpent(String txId, long amount); fun moneySpent(txId: String?, amount: Long)
/** /**
* moneyReceived - called when money received * moneyReceived - called when money received
@ -31,7 +30,7 @@ public interface WalletListener {
* @param txId - transaction id * @param txId - transaction id
* @param amount - tvAmount * @param amount - tvAmount
*/ */
void moneyReceived(String txId, long amount); fun moneyReceived(txId: String?, amount: Long)
/** /**
* unconfirmedMoneyReceived - called when payment arrived in tx pool * unconfirmedMoneyReceived - called when payment arrived in tx pool
@ -39,23 +38,22 @@ public interface WalletListener {
* @param txId - transaction id * @param txId - transaction id
* @param amount - tvAmount * @param amount - tvAmount
*/ */
void unconfirmedMoneyReceived(String txId, long amount); fun unconfirmedMoneyReceived(txId: String?, amount: Long)
/** /**
* newBlock - called when new block received * newBlock - called when new block received
* *
* @param height - block height * @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; * 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 * refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously
*/ */
void refreshed(); fun refreshed()
} }

View file

@ -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());
}
}
}

View 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?
}
}

View file

@ -33,7 +33,7 @@ public class BalanceService extends ServiceBase {
long unlocked = 0; long unlocked = 0;
for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) { for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && coinsInfo.isUnlocked() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) { if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && coinsInfo.isUnlocked() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) {
unlocked += coinsInfo.getAmount(); unlocked += coinsInfo.amount;
} }
} }
return unlocked; return unlocked;
@ -43,7 +43,7 @@ public class BalanceService extends ServiceBase {
long total = 0; long total = 0;
for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) { for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) { if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) {
total += coinsInfo.getAmount(); total += coinsInfo.amount;
} }
} }
return total; return total;

View file

@ -100,7 +100,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
@Override @Override
public void refreshed() { public void refreshed() {
Wallet.ConnectionStatus status = wallet.getFullStatus().getConnectionStatus(); Wallet.ConnectionStatus status = wallet.getFullStatus().connectionStatus;
if (status == Wallet.ConnectionStatus.ConnectionStatus_Disconnected || status == null) { if (status == Wallet.ConnectionStatus.ConnectionStatus_Disconnected || status == null) {
if (triesLeft > 0) { if (triesLeft > 0) {
wallet.startRefresh(); 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 { 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 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); ArrayList<TransactionOutput> newOutputs = new ArrayList<>(outputs);
boolean donatePerTx = PrefService.getInstance().getBoolean(Constants.PREF_DONATE_PER_TX, false); 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) 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) { if (!sendAll) {
long amountSelected = 0; long amountSelected = 0;
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) { for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
if (selectedUtxos.contains(coinsInfo.getKeyImage())) { if (selectedUtxos.contains(coinsInfo.keyImage)) {
amountSelected += coinsInfo.getAmount(); amountSelected += coinsInfo.amount;
} }
} }

View file

@ -57,10 +57,10 @@ public class UTXOService extends ServiceBase {
public void toggleFrozen(HashMap<String, CoinsInfo> selectedCoins) { public void toggleFrozen(HashMap<String, CoinsInfo> selectedCoins) {
ArrayList<String> frozenCoinsCopy = new ArrayList<>(frozenCoins); ArrayList<String> frozenCoinsCopy = new ArrayList<>(frozenCoins);
for(CoinsInfo coin : selectedCoins.values()) { for(CoinsInfo coin : selectedCoins.values()) {
if(frozenCoinsCopy.contains(coin.getPubKey())) { if(frozenCoinsCopy.contains(coin.pubKey)) {
frozenCoinsCopy.remove(coin.getPubKey()); frozenCoinsCopy.remove(coin.pubKey);
} else { } else {
frozenCoinsCopy.add(coin.getPubKey()); frozenCoinsCopy.add(coin.pubKey);
} }
} }
this.frozenCoins = frozenCoinsCopy; this.frozenCoins = frozenCoinsCopy;
@ -70,7 +70,7 @@ public class UTXOService extends ServiceBase {
} }
public boolean isCoinFrozen(CoinsInfo coinsInfo) { public boolean isCoinFrozen(CoinsInfo coinsInfo) {
return frozenCoins.contains(coinsInfo.getPubKey()); return frozenCoins.contains(coinsInfo.pubKey);
} }
private void loadFrozenCoins() throws JSONException { private void loadFrozenCoins() throws JSONException {
@ -104,18 +104,18 @@ public class UTXOService extends ServiceBase {
Collections.sort(utxos); Collections.sort(utxos);
//loop through each utxo //loop through each utxo
for (CoinsInfo coinsInfo : utxos) { 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 (sendAll) {
// if send all, add all utxos and set amount to send all // if send all, add all utxos and set amount to send all
selectedUtxos.add(coinsInfo.getKeyImage()); selectedUtxos.add(coinsInfo.keyImage);
amountSelected = Wallet.SWEEP_ALL; amountSelected = Wallet.SWEEP_ALL;
} else { } else {
//if amount selected is still less than amount needed, and the utxos tx hash hasn't already been seen, add utxo //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())) { if (amountSelected <= amountWithBasicFee && !seenTxs.contains(coinsInfo.hash)) {
selectedUtxos.add(coinsInfo.getKeyImage()); selectedUtxos.add(coinsInfo.keyImage);
// we don't want to spend multiple utxos from the same transaction, so we prevent that from happening here. // we don't want to spend multiple utxos from the same transaction, so we prevent that from happening here.
seenTxs.add(coinsInfo.getHash()); seenTxs.add(coinsInfo.hash);
amountSelected += coinsInfo.getAmount(); amountSelected += coinsInfo.amount;
} }
} }
} }

View file

@ -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";
}

View 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"
}

View file

@ -13,17 +13,17 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; object DateHelper {
import java.text.SimpleDateFormat; @JvmField
import java.util.Date; val DATETIME_FORMATTER = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
@Throws(ParseException::class)
public class DateHelper { fun parse(dateString: String): Date {
public static final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return DATETIME_FORMATTER.parse(dateString.replace("Z$".toRegex(), "+0000"))
public static Date parse(String dateString) throws ParseException {
return DATETIME_FORMATTER.parse(dateString.replaceAll("Z$", "+0000"));
} }
} }

View file

@ -13,33 +13,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package net.mynero.wallet.util
package net.mynero.wallet.util; import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate; enum class DayNightMode(val nightMode: Int) {
public enum DayNightMode {
// order must match R.array.daynight_themes // order must match R.array.daynight_themes
AUTO(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM), AUTO(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM), DAY(AppCompatDelegate.MODE_NIGHT_NO), NIGHT(
DAY(AppCompatDelegate.MODE_NIGHT_NO), AppCompatDelegate.MODE_NIGHT_YES
NIGHT(AppCompatDelegate.MODE_NIGHT_YES), ),
UNKNOWN(AppCompatDelegate.MODE_NIGHT_UNSPECIFIED); UNKNOWN(AppCompatDelegate.MODE_NIGHT_UNSPECIFIED);
final private int nightMode; companion object {
fun getValue(nightMode: Int): DayNightMode {
DayNightMode(int nightMode) { for (mode in values()) {
this.nightMode = nightMode; if (mode.nightMode == nightMode) return mode
}
return UNKNOWN
} }
static public DayNightMode getValue(int nightMode) {
for (DayNightMode mode : DayNightMode.values()) {
if (mode.nightMode == nightMode)
return mode;
}
return UNKNOWN;
}
public int getNightMode() {
return nightMode;
} }
} }

View file

@ -13,299 +13,294 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; object Helper {
import android.app.Activity; const val NOCRAZYPASS_FLAGFILE = ".nocrazypass"
import android.app.Dialog; const val XMR_DECIMALS = 12
import android.content.ClipData; val ONE_XMR = Math.round(Math.pow(10.0, XMR_DECIMALS.toDouble()))
import android.content.ClipDescription; const val SHOW_EXCHANGERATES = true
import android.content.ClipboardManager; const val PERMISSIONS_REQUEST_CAMERA = 7
import android.content.Context; const val HTTP_TIMEOUT = 5000
import android.content.pm.PackageManager; private const val WALLET_DIR = "wallets"
import android.graphics.Bitmap; private const val MONERO_DIR = "monero"
import android.graphics.BitmapFactory; private val HexArray = "0123456789ABCDEF".toCharArray()
import android.graphics.Canvas; var ALLOW_SHIFT = false
import android.graphics.drawable.BitmapDrawable; @JvmField
import android.graphics.drawable.Drawable; var DISPLAY_DIGITS_INFO = 5
import android.graphics.drawable.VectorDrawable; private var ShakeAnimation: Animation? = null
import android.view.View; fun getWalletRoot(context: Context): File {
import android.view.WindowManager; return getStorage(context, WALLET_DIR)
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);
} }
static public File getStorage(Context context, String folderName) { fun getStorage(context: Context, folderName: String?): File {
File dir = new File(context.getFilesDir(), folderName); val dir = File(context.filesDir, folderName)
if (!dir.exists()) { if (!dir.exists()) {
Timber.i("Creating %s", dir.getAbsolutePath()); Timber.i("Creating %s", dir.absolutePath)
dir.mkdirs(); // try to make it dir.mkdirs() // try to make it
} }
if (!dir.isDirectory()) { if (!dir.isDirectory) {
String msg = "Directory " + dir.getAbsolutePath() + " does not exist."; val msg = "Directory " + dir.absolutePath + " does not exist."
Timber.e(msg); Timber.e(msg)
throw new IllegalStateException(msg); throw IllegalStateException(msg)
} }
return dir; return dir
} }
static public boolean getCameraPermission(Activity context) { fun getCameraPermission(context: Activity): Boolean {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.CAMERA) if (context.checkSelfPermission(Manifest.permission.CAMERA)
== PackageManager.PERMISSION_DENIED) { == PackageManager.PERMISSION_DENIED
Timber.w("Permission denied for CAMERA - requesting it"); ) {
String[] permissions = {Manifest.permission.CAMERA}; Timber.w("Permission denied for CAMERA - requesting it")
context.requestPermissions(permissions, PERMISSIONS_REQUEST_CAMERA); val permissions = arrayOf(Manifest.permission.CAMERA)
return false; context.requestPermissions(
permissions,
PERMISSIONS_REQUEST_CAMERA
)
false
} else { } else {
return true; true
} }
} else { } else {
return true; true
} }
} }
static public boolean getCameraPermission(Activity context, ActivityResultLauncher<String> launcher) { @JvmStatic
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { fun getCameraPermission(context: Activity, launcher: ActivityResultLauncher<String?>): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.CAMERA) if (context.checkSelfPermission(Manifest.permission.CAMERA)
== PackageManager.PERMISSION_DENIED) { == PackageManager.PERMISSION_DENIED
Timber.w("Permission denied for CAMERA - requesting it"); ) {
launcher.launch(Manifest.permission.CAMERA); Timber.w("Permission denied for CAMERA - requesting it")
return false; launcher.launch(Manifest.permission.CAMERA)
false
} else { } else {
return true; true
} }
} else { } else {
return true; true
} }
} }
static public File getWalletFile(Context context, String aWalletName) { fun getWalletFile(context: Context, aWalletName: String?): File {
File walletDir = getWalletRoot(context); val walletDir = getWalletRoot(context)
File f = new File(walletDir, aWalletName); val f = File(walletDir, aWalletName)
Timber.d("wallet=%s size= %d", f.getAbsolutePath(), f.length()); Timber.d("wallet=%s size= %d", f.absolutePath, f.length())
return f; return f
} }
static public void showKeyboard(Activity act) { fun showKeyboard(act: Activity) {
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); val imm = act.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
final View focus = act.getCurrentFocus(); val focus = act.currentFocus
if (focus != null) if (focus != null) imm.showSoftInput(focus, InputMethodManager.SHOW_IMPLICIT)
imm.showSoftInput(focus, InputMethodManager.SHOW_IMPLICIT);
} }
static public void hideKeyboard(Activity act) { fun hideKeyboard(act: Activity?) {
if (act == null) return; if (act == null) return
if (act.getCurrentFocus() == null) { if (act.currentFocus == null) {
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); act.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
} else { } else {
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); val imm = act.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow((null == act.getCurrentFocus()) ? null : act.getCurrentFocus().getWindowToken(), act.currentFocus?.let {
InputMethodManager.HIDE_NOT_ALWAYS); imm.hideSoftInputFromWindow(
it.windowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
}
} }
} }
static public void showKeyboard(Dialog dialog) { fun showKeyboard(dialog: Dialog) {
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
} }
static public void hideKeyboardAlways(Activity act) { fun hideKeyboardAlways(act: Activity) {
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); act.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
} }
static public BigDecimal getDecimalAmount(long amount) { fun getDecimalAmount(amount: Long): BigDecimal {
return new BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS); return BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS)
} }
static public String getDisplayAmount(long amount) { @JvmStatic
return getDisplayAmount(amount, XMR_DECIMALS); 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 // a Java bug does not strip zeros properly if the value is 0
if (amount == 0) return "0.00"; if (amount == 0L) return "0.00"
BigDecimal d = getDecimalAmount(amount) var d = getDecimalAmount(amount)
.setScale(maxDecimals, BigDecimal.ROUND_HALF_UP) .setScale(maxDecimals, BigDecimal.ROUND_HALF_UP)
.stripTrailingZeros(); .stripTrailingZeros()
if (d.scale() < 2) if (d.scale() < 2) d = d.setScale(2, BigDecimal.ROUND_UNNECESSARY)
d = d.setScale(2, BigDecimal.ROUND_UNNECESSARY); return d.toPlainString()
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 // at this point selection is XMR in case of error
String displayB; val displayB: String?
if (isCrypto) { displayB = if (isCrypto) {
if ((amount >= 0) || (amount == 0)) { if (amount >= 0 || amount == 0.0) {
displayB = String.format(Locale.US, "%,.5f", amount); String.format(Locale.US, "%,.5f", amount)
} else { } else {
displayB = null; null
} }
} else { // not crypto } 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 // 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) .setScale(XMR_DECIMALS, BigDecimal.ROUND_HALF_UP)
.stripTrailingZeros(); .stripTrailingZeros()
if (d.scale() < 1) if (d.scale() < 1) d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY)
d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY); return d.toPlainString()
return d.toPlainString();
} }
static public Bitmap getBitmap(Context context, int drawableId) { fun getBitmap(context: Context, drawableId: Int): Bitmap {
Drawable drawable = ContextCompat.getDrawable(context, drawableId); val drawable = ContextCompat.getDrawable(context, drawableId)
if (drawable instanceof BitmapDrawable) { return if (drawable is BitmapDrawable) {
return BitmapFactory.decodeResource(context.getResources(), drawableId); BitmapFactory.decodeResource(context.resources, drawableId)
} else if (drawable instanceof VectorDrawable) { } else if (drawable is VectorDrawable) {
return getBitmap((VectorDrawable) drawable); getBitmap(drawable)
} else { } else {
throw new IllegalArgumentException("unsupported drawable type"); throw IllegalArgumentException("unsupported drawable type")
} }
} }
static private Bitmap getBitmap(VectorDrawable vectorDrawable) { private fun getBitmap(vectorDrawable: VectorDrawable): Bitmap {
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), val bitmap = Bitmap.createBitmap(
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); vectorDrawable.intrinsicWidth,
Canvas canvas = new Canvas(bitmap); vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); )
vectorDrawable.draw(canvas); val canvas = Canvas(bitmap)
return bitmap; vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
vectorDrawable.draw(canvas)
return bitmap
} }
static public String getUrl(String httpsUrl) { fun getUrl(httpsUrl: String?): String? {
HttpsURLConnection urlConnection = null; var urlConnection: HttpsURLConnection? = null
try { try {
URL url = new URL(httpsUrl); val url = URL(httpsUrl)
urlConnection = (HttpsURLConnection) url.openConnection(); urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.setConnectTimeout(HTTP_TIMEOUT); urlConnection.connectTimeout = HTTP_TIMEOUT
urlConnection.setReadTimeout(HTTP_TIMEOUT); urlConnection.readTimeout = HTTP_TIMEOUT
InputStreamReader in = new InputStreamReader(urlConnection.getInputStream()); val `in` = InputStreamReader(urlConnection.inputStream)
StringBuffer sb = new StringBuffer(); val sb = StringBuffer()
final int BUFFER_SIZE = 512; val BUFFER_SIZE = 512
char[] buffer = new char[BUFFER_SIZE]; val buffer = CharArray(BUFFER_SIZE)
int length = in.read(buffer, 0, BUFFER_SIZE); var length = `in`.read(buffer, 0, BUFFER_SIZE)
while (length >= 0) { while (length >= 0) {
sb.append(buffer, 0, length); sb.append(buffer, 0, length)
length = in.read(buffer, 0, BUFFER_SIZE); length = `in`.read(buffer, 0, BUFFER_SIZE)
} }
return sb.toString(); return sb.toString()
} catch (SocketTimeoutException ex) { } catch (ex: SocketTimeoutException) {
Timber.w("C %s", ex.getLocalizedMessage()); Timber.w("C %s", ex.localizedMessage)
} catch (MalformedURLException ex) { } catch (ex: MalformedURLException) {
Timber.e("A %s", ex.getLocalizedMessage()); Timber.e("A %s", ex.localizedMessage)
} catch (IOException ex) { } catch (ex: IOException) {
Timber.e("B %s", ex.getLocalizedMessage()); Timber.e("B %s", ex.localizedMessage)
} finally { } 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) { if (context != null) {
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); val clipboardManager =
ClipData clip = ClipData.newPlainText(label, text); context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager.setPrimaryClip(clip); val clip = ClipData.newPlainText(label, text)
Toast.makeText(context, context.getText(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show(); clipboardManager.setPrimaryClip(clip)
Toast.makeText(
context,
context.getText(R.string.copied_to_clipboard),
Toast.LENGTH_SHORT
).show()
} }
} }
static public String getClipBoardText(Context context) { @JvmStatic
final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); fun getClipBoardText(context: Context): String? {
val clipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
try { try {
if (clipboardManager.hasPrimaryClip() if (clipboardManager.hasPrimaryClip()
&& clipboardManager.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { && clipboardManager.primaryClipDescription?.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) == true
final ClipData.Item item = clipboardManager.getPrimaryClip().getItemAt(0); ) {
return item.getText().toString(); 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 // 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) { if (ShakeAnimation == null) {
synchronized (Helper.class) { synchronized(Helper::class.java) {
if (ShakeAnimation == null) { if (ShakeAnimation == null) {
ShakeAnimation = AnimationUtils.loadAnimation(context, R.anim.shake); ShakeAnimation = AnimationUtils.loadAnimation(context, R.anim.shake)
} }
} }
} }
return ShakeAnimation; 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;
} }
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ? // TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
static public void initLogger(Context context, int level) { fun initLogger(context: Context, level: Int) {
String home = getStorage(context, MONERO_DIR).getAbsolutePath(); val home = getStorage(context, MONERO_DIR).absolutePath
WalletManager.initLogger(home + "/monerujo", "monerujo.log"); initLogger("$home/monerujo", "monerujo.log")
if (level >= WalletManager.LOGLEVEL_SILENT) if (level >= WalletManager.LOGLEVEL_SILENT) setLogLevel(level)
WalletManager.setLogLevel(level);
} }
} }

View file

@ -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);
}
}
}

View file

@ -13,41 +13,43 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; object MoneroThreadPoolExecutor {
@JvmField
import java.util.concurrent.BlockingQueue; var MONERO_THREAD_POOL_EXECUTOR: Executor? = null
import java.util.concurrent.Executor; private val CPU_COUNT = Runtime.getRuntime().availableProcessors()
import java.util.concurrent.LinkedBlockingQueue; private val CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4))
import java.util.concurrent.ThreadFactory; private val MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1
import java.util.concurrent.ThreadPoolExecutor; private const val KEEP_ALIVE_SECONDS = 30
import java.util.concurrent.TimeUnit; private val sThreadFactory: ThreadFactory = object : ThreadFactory {
import java.util.concurrent.atomic.AtomicInteger; private val mCount = AtomicInteger(1)
override fun newThread(r: Runnable): Thread {
return Thread(
public class MoneroThreadPoolExecutor { null,
public static final Executor MONERO_THREAD_POOL_EXECUTOR; r,
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); "MoneroTask #" + mCount.getAndIncrement(),
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); MoneroHandlerThread.THREAD_STACK_SIZE
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); private val sPoolWorkQueue: BlockingQueue<Runnable> = LinkedBlockingQueue(128)
public Thread newThread(Runnable r) { init {
return new Thread(null, r, "MoneroTask #" + mCount.getAndIncrement(), MoneroHandlerThread.THREAD_STACK_SIZE); val threadPoolExecutor = ThreadPoolExecutor(
} CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS.toLong(), TimeUnit.SECONDS,
}; sPoolWorkQueue, sThreadFactory
private static final BlockingQueue<Runnable> sPoolWorkQueue = )
new LinkedBlockingQueue<>(128); threadPoolExecutor.allowCoreThreadTimeOut(true)
MONERO_THREAD_POOL_EXECUTOR = threadPoolExecutor
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;
} }
} }

View file

@ -13,22 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; object NightmodeHelper {
val preferredNightmode: Unit
import androidx.appcompat.app.AppCompatDelegate; get() {
setNightMode(DayNightMode.NIGHT)
import net.mynero.wallet.service.PrefService;
public class NightmodeHelper {
public static void getAndSetPreferredNightmode() {
setNightMode(DayNightMode.NIGHT);
} }
@SuppressLint("WrongConstant") @SuppressLint("WrongConstant")
public static void setNightMode(DayNightMode mode) { fun setNightMode(mode: DayNightMode) {
AppCompatDelegate.setDefaultNightMode(mode.getNightMode()); AppCompatDelegate.setDefaultNightMode(mode.nightMode)
} }
} }

View file

@ -13,16 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package net.mynero.wallet.util
package net.mynero.wallet.util; object OnionHelper {
fun isOnionHost(hostname: String): Boolean {
public class OnionHelper { return hostname.endsWith(".onion")
public static boolean isOnionHost(String hostname) {
return hostname.endsWith(".onion");
} }
public static boolean isI2PHost(String hostname) { fun isI2PHost(hostname: String): Boolean {
return hostname.endsWith(".i2p"); return hostname.endsWith(".i2p")
} }
} }

View file

@ -13,198 +13,198 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; class RestoreHeight internal constructor() {
import java.text.SimpleDateFormat; private val blockheight: MutableMap<String, Long> = HashMap()
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;
public class RestoreHeight { init {
static final int DIFFICULTY_TARGET = 120; // seconds blockheight["2014-05-01"] = 18844L
blockheight["2014-06-01"] = 65406L
static private RestoreHeight Singleton = null; blockheight["2014-07-01"] = 108882L
private final Map<String, Long> blockheight = new HashMap<>(); blockheight["2014-08-01"] = 153594L
blockheight["2014-09-01"] = 198072L
RestoreHeight() { blockheight["2014-10-01"] = 241088L
blockheight.put("2014-05-01", 18844L); blockheight["2014-11-01"] = 285305L
blockheight.put("2014-06-01", 65406L); blockheight["2014-12-01"] = 328069L
blockheight.put("2014-07-01", 108882L); blockheight["2015-01-01"] = 372369L
blockheight.put("2014-08-01", 153594L); blockheight["2015-02-01"] = 416505L
blockheight.put("2014-09-01", 198072L); blockheight["2015-03-01"] = 456631L
blockheight.put("2014-10-01", 241088L); blockheight["2015-04-01"] = 501084L
blockheight.put("2014-11-01", 285305L); blockheight["2015-05-01"] = 543973L
blockheight.put("2014-12-01", 328069L); blockheight["2015-06-01"] = 588326L
blockheight.put("2015-01-01", 372369L); blockheight["2015-07-01"] = 631187L
blockheight.put("2015-02-01", 416505L); blockheight["2015-08-01"] = 675484L
blockheight.put("2015-03-01", 456631L); blockheight["2015-09-01"] = 719725L
blockheight.put("2015-04-01", 501084L); blockheight["2015-10-01"] = 762463L
blockheight.put("2015-05-01", 543973L); blockheight["2015-11-01"] = 806528L
blockheight.put("2015-06-01", 588326L); blockheight["2015-12-01"] = 849041L
blockheight.put("2015-07-01", 631187L); blockheight["2016-01-01"] = 892866L
blockheight.put("2015-08-01", 675484L); blockheight["2016-02-01"] = 936736L
blockheight.put("2015-09-01", 719725L); blockheight["2016-03-01"] = 977691L
blockheight.put("2015-10-01", 762463L); blockheight["2016-04-01"] = 1015848L
blockheight.put("2015-11-01", 806528L); blockheight["2016-05-01"] = 1037417L
blockheight.put("2015-12-01", 849041L); blockheight["2016-06-01"] = 1059651L
blockheight.put("2016-01-01", 892866L); blockheight["2016-07-01"] = 1081269L
blockheight.put("2016-02-01", 936736L); blockheight["2016-08-01"] = 1103630L
blockheight.put("2016-03-01", 977691L); blockheight["2016-09-01"] = 1125983L
blockheight.put("2016-04-01", 1015848L); blockheight["2016-10-01"] = 1147617L
blockheight.put("2016-05-01", 1037417L); blockheight["2016-11-01"] = 1169779L
blockheight.put("2016-06-01", 1059651L); blockheight["2016-12-01"] = 1191402L
blockheight.put("2016-07-01", 1081269L); blockheight["2017-01-01"] = 1213861L
blockheight.put("2016-08-01", 1103630L); blockheight["2017-02-01"] = 1236197L
blockheight.put("2016-09-01", 1125983L); blockheight["2017-03-01"] = 1256358L
blockheight.put("2016-10-01", 1147617L); blockheight["2017-04-01"] = 1278622L
blockheight.put("2016-11-01", 1169779L); blockheight["2017-05-01"] = 1300239L
blockheight.put("2016-12-01", 1191402L); blockheight["2017-06-01"] = 1322564L
blockheight.put("2017-01-01", 1213861L); blockheight["2017-07-01"] = 1344225L
blockheight.put("2017-02-01", 1236197L); blockheight["2017-08-01"] = 1366664L
blockheight.put("2017-03-01", 1256358L); blockheight["2017-09-01"] = 1389113L
blockheight.put("2017-04-01", 1278622L); blockheight["2017-10-01"] = 1410738L
blockheight.put("2017-05-01", 1300239L); blockheight["2017-11-01"] = 1433039L
blockheight.put("2017-06-01", 1322564L); blockheight["2017-12-01"] = 1454639L
blockheight.put("2017-07-01", 1344225L); blockheight["2018-01-01"] = 1477201L
blockheight.put("2017-08-01", 1366664L); blockheight["2018-02-01"] = 1499599L
blockheight.put("2017-09-01", 1389113L); blockheight["2018-03-01"] = 1519796L
blockheight.put("2017-10-01", 1410738L); blockheight["2018-04-01"] = 1542067L
blockheight.put("2017-11-01", 1433039L); blockheight["2018-05-01"] = 1562861L
blockheight.put("2017-12-01", 1454639L); blockheight["2018-06-01"] = 1585135L
blockheight.put("2018-01-01", 1477201L); blockheight["2018-07-01"] = 1606715L
blockheight.put("2018-02-01", 1499599L); blockheight["2018-08-01"] = 1629017L
blockheight.put("2018-03-01", 1519796L); blockheight["2018-09-01"] = 1651347L
blockheight.put("2018-04-01", 1542067L); blockheight["2018-10-01"] = 1673031L
blockheight.put("2018-05-01", 1562861L); blockheight["2018-11-01"] = 1695128L
blockheight.put("2018-06-01", 1585135L); blockheight["2018-12-01"] = 1716687L
blockheight.put("2018-07-01", 1606715L); blockheight["2019-01-01"] = 1738923L
blockheight.put("2018-08-01", 1629017L); blockheight["2019-02-01"] = 1761435L
blockheight.put("2018-09-01", 1651347L); blockheight["2019-03-01"] = 1781681L
blockheight.put("2018-10-01", 1673031L); blockheight["2019-04-01"] = 1803081L
blockheight.put("2018-11-01", 1695128L); blockheight["2019-05-01"] = 1824671L
blockheight.put("2018-12-01", 1716687L); blockheight["2019-06-01"] = 1847005L
blockheight.put("2019-01-01", 1738923L); blockheight["2019-07-01"] = 1868590L
blockheight.put("2019-02-01", 1761435L); blockheight["2019-08-01"] = 1890878L
blockheight.put("2019-03-01", 1781681L); blockheight["2019-09-01"] = 1913201L
blockheight.put("2019-04-01", 1803081L); blockheight["2019-10-01"] = 1934732L
blockheight.put("2019-05-01", 1824671L); blockheight["2019-11-01"] = 1957051L
blockheight.put("2019-06-01", 1847005L); blockheight["2019-12-01"] = 1978433L
blockheight.put("2019-07-01", 1868590L); blockheight["2020-01-01"] = 2001315L
blockheight.put("2019-08-01", 1890878L); blockheight["2020-02-01"] = 2023656L
blockheight.put("2019-09-01", 1913201L); blockheight["2020-03-01"] = 2044552L
blockheight.put("2019-10-01", 1934732L); blockheight["2020-04-01"] = 2066806L
blockheight.put("2019-11-01", 1957051L); blockheight["2020-05-01"] = 2088411L
blockheight.put("2019-12-01", 1978433L); blockheight["2020-06-01"] = 2110702L
blockheight.put("2020-01-01", 2001315L); blockheight["2020-07-01"] = 2132318L
blockheight.put("2020-02-01", 2023656L); blockheight["2020-08-01"] = 2154590L
blockheight.put("2020-03-01", 2044552L); blockheight["2020-09-01"] = 2176790L
blockheight.put("2020-04-01", 2066806L); blockheight["2020-10-01"] = 2198370L
blockheight.put("2020-05-01", 2088411L); blockheight["2020-11-01"] = 2220670L
blockheight.put("2020-06-01", 2110702L); blockheight["2020-12-01"] = 2242241L
blockheight.put("2020-07-01", 2132318L); blockheight["2021-01-01"] = 2264584L
blockheight.put("2020-08-01", 2154590L); blockheight["2021-02-01"] = 2286892L
blockheight.put("2020-09-01", 2176790L); blockheight["2021-03-01"] = 2307079L
blockheight.put("2020-10-01", 2198370L); blockheight["2021-04-01"] = 2329385L
blockheight.put("2020-11-01", 2220670L); blockheight["2021-05-01"] = 2351004L
blockheight.put("2020-12-01", 2242241L); blockheight["2021-06-01"] = 2373306L
blockheight.put("2021-01-01", 2264584L); blockheight["2021-07-01"] = 2394882L
blockheight.put("2021-02-01", 2286892L); blockheight["2021-08-01"] = 2417162L
blockheight.put("2021-03-01", 2307079L); blockheight["2021-09-01"] = 2439490L
blockheight.put("2021-04-01", 2329385L); blockheight["2021-10-01"] = 2461020L
blockheight.put("2021-05-01", 2351004L); blockheight["2021-11-01"] = 2483377L
blockheight.put("2021-06-01", 2373306L); blockheight["2021-12-01"] = 2504932L
blockheight.put("2021-07-01", 2394882L); blockheight["2022-01-01"] = 2527316L
blockheight.put("2021-08-01", 2417162L); blockheight["2022-02-01"] = 2549605L
blockheight.put("2021-09-01", 2439490L); blockheight["2022-03-01"] = 2569711L
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);
} }
static public RestoreHeight getInstance() { fun getHeight(date: String?): Long {
if (Singleton == null) { val parser = SimpleDateFormat("yyyy-MM-dd")
synchronized (RestoreHeight.class) { parser.timeZone = TimeZone.getTimeZone("UTC")
if (Singleton == null) { parser.isLenient = false
Singleton = new RestoreHeight(); return try {
} getHeight(parser.parse(date))
} } catch (ex: ParseException) {
} throw IllegalArgumentException(ex)
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);
} }
} }
public long getHeight(final Date date) { fun getHeight(date: Date?): Long {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
cal.set(Calendar.DST_OFFSET, 0); cal[Calendar.DST_OFFSET] = 0
cal.setTime(date); cal.time = date
cal.add(Calendar.DAY_OF_MONTH, -4); // give it some leeway cal.add(Calendar.DAY_OF_MONTH, -4) // give it some leeway
if (cal.get(Calendar.YEAR) < 2014) if (cal[Calendar.YEAR] < 2014) return 0
return 0; if (cal[Calendar.YEAR] == 2014 && cal[Calendar.MONTH] <= 3) // before May 2014
if ((cal.get(Calendar.YEAR) == 2014) && (cal.get(Calendar.MONTH) <= 3)) return 0
// before May 2014 val query = cal.clone() as Calendar
return 0; val formatter = SimpleDateFormat("yyyy-MM-dd")
formatter.timeZone = TimeZone.getTimeZone("UTC")
Calendar query = (Calendar) cal.clone(); val queryDate = formatter.format(date)
cal[Calendar.DAY_OF_MONTH] = 1
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); var prevTime = cal.timeInMillis
formatter.setTimeZone(TimeZone.getTimeZone("UTC")); var prevDate = formatter.format(prevTime)
String queryDate = formatter.format(date);
cal.set(Calendar.DAY_OF_MONTH, 1);
long prevTime = cal.getTimeInMillis();
String prevDate = formatter.format(prevTime);
// lookup blockheight at first of the month // lookup blockheight at first of the month
Long prevBc = blockheight.get(prevDate); var prevBc = blockheight[prevDate]
if (prevBc == null) { if (prevBc == null) {
// if too recent, go back in time and find latest one we have // if too recent, go back in time and find latest one we have
while (prevBc == null) { while (prevBc == null) {
cal.add(Calendar.MONTH, -1); cal.add(Calendar.MONTH, -1)
if (cal.get(Calendar.YEAR) < 2014) { check(cal[Calendar.YEAR] >= 2014) { "endless loop looking for blockheight" }
throw new IllegalStateException("endless loop looking for blockheight"); prevTime = cal.timeInMillis
} prevDate = formatter.format(prevTime)
prevTime = cal.getTimeInMillis(); prevBc = blockheight[prevDate]
prevDate = formatter.format(prevTime);
prevBc = blockheight.get(prevDate);
} }
} }
long height = prevBc; var height: Long = prevBc
// now we have a blockheight & a date ON or BEFORE the restore date requested // 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 // see if we have a blockheight after this date
cal.add(Calendar.MONTH, 1); cal.add(Calendar.MONTH, 1)
long nextTime = cal.getTimeInMillis(); val nextTime = cal.timeInMillis
String nextDate = formatter.format(nextTime); val nextDate = formatter.format(nextTime)
Long nextBc = blockheight.get(nextDate); val nextBc = blockheight[nextDate]
height =
if (nextBc != null) { // we have a range - interpolate the blockheight we are looking for if (nextBc != null) { // we have a range - interpolate the blockheight we are looking for
long diff = nextBc - prevBc; val diff = nextBc - prevBc
long diffDays = TimeUnit.DAYS.convert(nextTime - prevTime, TimeUnit.MILLISECONDS); val diffDays = TimeUnit.DAYS.convert(
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime, nextTime - prevTime,
TimeUnit.MILLISECONDS); TimeUnit.MILLISECONDS
height = Math.round(prevBc + diff * (1.0 * days / diffDays)); )
val days = TimeUnit.DAYS.convert(
query.timeInMillis - prevTime,
TimeUnit.MILLISECONDS
)
Math.round(prevBc + diff * (1.0 * days / diffDays))
} else { } else {
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime, val days = TimeUnit.DAYS.convert(
TimeUnit.MILLISECONDS); query.timeInMillis - prevTime,
height = Math.round(prevBc + 1.0 * days * (24f * 60 * 60 / DIFFICULTY_TARGET)); 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;
} }
} }

View file

@ -13,46 +13,46 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; object ThemeHelper {
import android.content.Context; fun getThemedResourceId(ctx: Context, attrId: Int): Int {
import android.graphics.Color; val typedValue = TypedValue()
import android.preference.PreferenceManager; return if (ctx.theme.resolveAttribute(
import android.util.TypedValue; attrId,
typedValue,
import androidx.annotation.ColorInt; true
import androidx.annotation.NonNull; )
) typedValue.resourceId else 0
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;
} }
@JvmStatic
@ColorInt @ColorInt
static public int getThemedColor(Context ctx, int attrId) { fun getThemedColor(ctx: Context, attrId: Int): Int {
final TypedValue typedValue = new TypedValue(); val typedValue = TypedValue()
if (ctx.getTheme().resolveAttribute(attrId, typedValue, true)) return if (ctx.theme.resolveAttribute(
return typedValue.data; attrId,
else typedValue,
return Color.BLACK; true
)
) typedValue.data else Color.BLACK
} }
public static void setTheme(@NonNull Activity activity, @NonNull String theme) { fun setTheme(activity: Activity, theme: String) {
activity.setTheme(R.style.MyMaterialThemeOled); activity.setTheme(R.style.MyMaterialThemeOled)
} }
public static void setPreferred(Activity activity) { fun setPreferred(activity: Activity) {
final String theme = PreferenceManager.getDefaultSharedPreferences(activity) val theme = PreferenceManager.getDefaultSharedPreferences(activity)
.getString(activity.getString(R.string.preferred_theme), "Classic"); .getString(activity.getString(R.string.preferred_theme), "Classic")
setTheme(activity, theme); theme?.let { setTheme(activity, it) }
} }
} }

View file

@ -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.Wallet.Companion.getPaymentIdFromAddress
import net.mynero.wallet.model.WalletManager; 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 { fun hasPaymentId(): Boolean {
private final String address; return instance?.wallet?.nettype()?.let { getPaymentIdFromAddress(address, it) }?.isNotEmpty() == true
private final HashMap<String, String> params;
public UriData(String address, HashMap<String, String> params) {
this.address = address;
this.params = params;
} }
public static UriData parse(String uri) { val amount: String?
HashMap<String, String> params = new HashMap<>(); get() = params[Constants.URI_ARG_AMOUNT]
String[] uriParts = uri.replace(Constants.URI_PREFIX, "").split("\\?"); ?: params[Constants.URI_ARG_AMOUNT2]
String finalAddress = uriParts[0];
String queryParams = ""; fun hasAmount(): Boolean {
if (uriParts.length > 1) { return params.containsKey(Constants.URI_ARG_AMOUNT) || params.containsKey(Constants.URI_ARG_AMOUNT2)
queryParams = uriParts[1]; }
String[] queryParts = queryParams.split("&");
for (String param : queryParts) { companion object {
String[] paramParts = param.split("="); @JvmStatic
if(paramParts.length == 2) { fun parse(uri: String): UriData? {
String variable = paramParts[0]; if(uri.isEmpty()) return null
String value = paramParts[1]; val params = HashMap<String, String>()
params.put(variable, value); 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); val valid = isAddressValid(finalAddress)
if (valid) { return if (valid) {
return new UriData(finalAddress, params); UriData(finalAddress, params)
} else { } 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);
} }
} }

View file

@ -1,12 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.9.21'
repositories { repositories {
mavenCentral() mavenCentral()
google() google()
} }
dependencies { 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 "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }

View file

@ -18,3 +18,6 @@ org.gradle.jvmargs=-Xmx2048m
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

View file

@ -1,6 +1,6 @@
#Fri Feb 10 17:24:42 CST 2023 #Fri Feb 10 17:24:42 CST 2023
distributionBase=GRADLE_USER_HOME 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 distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME