migrate to scoped storage and API 30 (#741)

merge-requests/3/head
wow nero 3 years ago committed by GitHub
parent 24fc27b09e
commit d2f07ba3b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,14 +1,14 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
compileSdkVersion 30
buildToolsVersion '29.0.3'
defaultConfig {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 29
versionCode 801
versionName "1.18.1 'ChAdOx1'"
targetSdkVersion 30
versionCode 900
versionName "1.19.0.2 'XXX'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {

@ -8,7 +8,6 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

@ -23,10 +23,8 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.media.MediaScannerConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.KeyEvent;
@ -39,6 +37,7 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.fragment.app.Fragment;
@ -62,10 +61,13 @@ import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.DayNightMode;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.LegacyStorageHelper;
import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.util.NightmodeHelper;
import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.util.ZipBackup;
import com.m2049r.xmrwallet.util.ZipRestore;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.io.File;
@ -290,9 +292,6 @@ public class LoginActivity extends BaseActivity
protected void onCreate(Bundle savedInstanceState) {
Timber.d("onCreate()");
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
// we don't store anything ourselves
}
setContentView(R.layout.activity_login);
toolbar = findViewById(R.id.toolbar);
@ -319,11 +318,9 @@ public class LoginActivity extends BaseActivity
loadFavouritesWithNetwork();
if (Helper.getWritePermission(this)) {
if (savedInstanceState == null) startLoginFragment();
} else {
Timber.i("Waiting for permissions");
}
LegacyStorageHelper.migrateWallets(this);
if (savedInstanceState == null) startLoginFragment();
// try intents
Intent intent = getIntent();
@ -396,53 +393,25 @@ public class LoginActivity extends BaseActivity
.show();
}
private class AsyncRename extends AsyncTask<String, Void, Boolean> {
@Override
protected void onPreExecute() {
super.onPreExecute();
showProgressDialog(R.string.rename_progress);
private void renameWallet(String oldName, String newName) {
File walletFile = Helper.getWalletFile(this, oldName);
boolean success = renameWallet(walletFile, newName);
if (success) {
reloadWalletList();
} else {
Toast.makeText(LoginActivity.this, getString(R.string.rename_failed), Toast.LENGTH_LONG).show();
}
}
@Override
protected Boolean doInBackground(String... params) {
if (params.length != 2) return false;
String oldName = params[0];
String newName = params[1];
File walletFile = Helper.getWalletFile(LoginActivity.this, oldName);
boolean success = renameWallet(walletFile, newName);
// copy + delete seems safer than rename because we can rollback easily
boolean renameWallet(File walletFile, String newName) {
if (copyWallet(walletFile, new File(walletFile.getParentFile(), newName), false, true)) {
try {
if (success) {
String savedPass = KeyStoreHelper.loadWalletUserPass(LoginActivity.this, oldName);
KeyStoreHelper.saveWalletUserPass(LoginActivity.this, newName, savedPass);
}
KeyStoreHelper.copyWalletUserPass(this, walletFile.getName(), newName);
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
Timber.w(ex);
} finally {
// we have either set a new password or it is broken - kill the old one either way
KeyStoreHelper.removeWalletUserPass(LoginActivity.this, oldName);
}
return success;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (isDestroyed()) {
return;
}
dismissProgressDialog();
if (result) {
reloadWalletList();
} else {
Toast.makeText(LoginActivity.this, getString(R.string.rename_failed), Toast.LENGTH_LONG).show();
}
}
}
// copy + delete seems safer than rename because we call rollback easily
boolean renameWallet(File walletFile, String newName) {
if (copyWallet(walletFile, new File(walletFile.getParentFile(), newName), false, true)) {
deleteWallet(walletFile);
deleteWallet(walletFile); // also deletes the keystore entry
return true;
} else {
return false;
@ -468,154 +437,123 @@ public class LoginActivity extends BaseActivity
alertDialogBuilder
.setCancelable(false)
.setPositiveButton(getString(R.string.label_ok),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Helper.hideKeyboardAlways(LoginActivity.this);
String newName = etRename.getText().toString();
new AsyncRename().execute(walletName, newName);
}
(dialog, id) -> {
Helper.hideKeyboardAlways(LoginActivity.this);
String newName = etRename.getText().toString();
renameWallet(walletName, newName);
})
.setNegativeButton(getString(R.string.label_cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Helper.hideKeyboardAlways(LoginActivity.this);
dialog.cancel();
}
(dialog, id) -> {
Helper.hideKeyboardAlways(LoginActivity.this);
dialog.cancel();
});
final AlertDialog dialog = alertDialogBuilder.create();
Helper.showKeyboard(dialog);
// accept keyboard "ok"
etRename.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboardAlways(LoginActivity.this);
String newName = etRename.getText().toString();
dialog.cancel();
new AsyncRename().execute(walletName, newName);
return false;
}
etRename.setOnEditorActionListener((v, actionId, event) -> {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboardAlways(LoginActivity.this);
String newName = etRename.getText().toString();
dialog.cancel();
renameWallet(walletName, newName);
return false;
}
return false;
});
dialog.show();
}
private class AsyncBackup extends AsyncTask<String, Void, Boolean> {
@Override
protected void onPreExecute() {
super.onPreExecute();
showProgressDialog(R.string.backup_progress);
}
@Override
protected Boolean doInBackground(String... params) {
if (params.length != 1) return false;
return backupWallet(params[0]);
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (isDestroyed()) {
return;
}
dismissProgressDialog();
if (!result) {
Toast.makeText(LoginActivity.this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(LoginActivity.this, getString(R.string.backup_success), Toast.LENGTH_SHORT).show();
}
}
}
private boolean backupWallet(String walletName) {
File backupFolder = new File(getStorageRoot(), "backups");
if (!backupFolder.exists()) {
if (!backupFolder.mkdir()) {
Timber.e("Cannot create backup dir %s", backupFolder.getAbsolutePath());
return false;
}
// make folder visible over USB/MTP
MediaScannerConnection.scanFile(this, new String[]{backupFolder.toString()}, null, null);
}
File walletFile = Helper.getWalletFile(LoginActivity.this, walletName);
File backupFile = new File(backupFolder, walletName);
Timber.d("backup " + walletFile.getAbsolutePath() + " to " + backupFile.getAbsolutePath());
// TODO probably better to copy to a new file and then rename
// then if something fails we have the old backup at least
// or just create a new backup every time and keep n old backups
boolean success = copyWallet(walletFile, backupFile, true, true);
Timber.d("copyWallet is %s", success);
return success;
}
private static final int CREATE_BACKUP_INTENT = 4711;
private static final int RESTORE_BACKUP_INTENT = 4712;
private ZipBackup zipBackup;
@Override
public void onWalletBackup(String walletName) {
Timber.d("backup for wallet ." + walletName + ".");
new AsyncBackup().execute(walletName);
zipBackup = new ZipBackup(this, walletName);
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/zip");
intent.putExtra(Intent.EXTRA_TITLE, zipBackup.getBackupName());
startActivityForResult(intent, CREATE_BACKUP_INTENT);
}
private class AsyncArchive extends AsyncTask<String, Void, Boolean> {
@Override
protected void onPreExecute() {
super.onPreExecute();
showProgressDialog(R.string.archive_progress);
}
@Override
public void onWalletRestore() {
Timber.d("restore wallet");
@Override
protected Boolean doInBackground(String... params) {
if (params.length != 1) return false;
String walletName = params[0];
if (backupWallet(walletName) && deleteWallet(Helper.getWalletFile(LoginActivity.this, walletName))) {
KeyStoreHelper.removeWalletUserPass(LoginActivity.this, walletName);
return true;
} else {
return false;
}
}
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/zip");
startActivityForResult(intent, RESTORE_BACKUP_INTENT);
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (isDestroyed()) {
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CREATE_BACKUP_INTENT) {
if (data == null) {
// nothing selected
Toast.makeText(this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show();
return;
}
dismissProgressDialog();
if (result) {
reloadWalletList();
} else {
Toast.makeText(LoginActivity.this, getString(R.string.archive_failed), Toast.LENGTH_LONG).show();
try {
zipBackup.writeTo(data.getData());
Toast.makeText(this, getString(R.string.backup_success), Toast.LENGTH_SHORT).show();
} catch (IOException ex) {
Timber.e(ex);
Toast.makeText(this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show();
}
} else if (requestCode == RESTORE_BACKUP_INTENT) {
if (data == null) {
// nothing selected
Toast.makeText(this, getString(R.string.restore_failed), Toast.LENGTH_LONG).show();
return;
}
try {
ZipRestore zipRestore = new ZipRestore(this, data.getData());
Toast.makeText(this, getString(R.string.menu_restore), Toast.LENGTH_SHORT).show();
if (zipRestore.restore()) {
reloadWalletList();
} else {
Toast.makeText(this, getString(R.string.restore_failed), Toast.LENGTH_LONG).show();
}
} catch (IOException ex) {
Timber.e(ex);
Toast.makeText(this, getString(R.string.restore_failed), Toast.LENGTH_LONG).show();
}
}
}
@Override
public void onWalletArchive(final String walletName) {
Timber.d("archive for wallet ." + walletName + ".");
public void onWalletDelete(final String walletName) {
Timber.d("delete for wallet ." + walletName + ".");
if (checkServiceRunning()) return;
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
new AsyncArchive().execute(walletName);
break;
case DialogInterface.BUTTON_NEGATIVE:
// do nothing
break;
}
DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
if (deleteWallet(Helper.getWalletFile(LoginActivity.this, walletName))) {
reloadWalletList();
} else {
Toast.makeText(LoginActivity.this, getString(R.string.delete_failed), Toast.LENGTH_LONG).show();
}
break;
case DialogInterface.BUTTON_NEGATIVE:
// do nothing
break;
}
};
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setMessage(getString(R.string.archive_alert_message))
builder.setMessage(getString(R.string.delete_alert_message))
.setTitle(walletName)
.setPositiveButton(getString(R.string.archive_alert_yes), dialogClickListener)
.setNegativeButton(getString(R.string.archive_alert_no), dialogClickListener)
.setPositiveButton(getString(R.string.delete_alert_yes), dialogClickListener)
.setNegativeButton(getString(R.string.delete_alert_no), dialogClickListener)
.show();
}
@ -628,6 +566,7 @@ public class LoginActivity extends BaseActivity
loginFragment.loadList();
}
} catch (ClassCastException ex) {
Timber.w(ex);
}
}
@ -774,37 +713,6 @@ public class LoginActivity extends BaseActivity
startReviewFragment(b);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
@NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult()");
switch (requestCode) {
case Helper.PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE:
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startLoginFragment = true;
} else {
String msg = getString(R.string.message_strorage_not_permitted);
Timber.e(msg);
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
break;
default:
}
}
private boolean startLoginFragment = false;
@Override
protected void onResumeFragments() {
super.onResumeFragments();
if (startLoginFragment) {
startLoginFragment();
startLoginFragment = false;
}
}
void startLoginFragment() {
// we set these here because we cannot be ceratin we have permissions for storage before
Helper.setMoneroHome(this);
@ -1035,22 +943,12 @@ public class LoginActivity extends BaseActivity
});
}
void toast(final String msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_LONG).show();
}
});
private void toast(final String msg) {
runOnUiThread(() -> Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_LONG).show());
}
void toast(final int msgId) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this, getString(msgId), Toast.LENGTH_LONG).show();
}
});
private void toast(final int msgId) {
runOnUiThread(() -> Toast.makeText(LoginActivity.this, getString(msgId), Toast.LENGTH_LONG).show());
}
@Override
@ -1058,7 +956,7 @@ public class LoginActivity extends BaseActivity
File walletFolder = getStorageRoot();
File walletFile = new File(walletFolder, name);
Timber.d("New Wallet %s", walletFile.getAbsolutePath());
walletFile.delete(); // when recovering wallets, the cache seems corrupt
walletFile.delete(); // when recovering wallets, the cache seems corrupt - so remove it
popFragmentStack(GENERATE_STACK);
Toast.makeText(LoginActivity.this,
@ -1079,8 +977,7 @@ public class LoginActivity extends BaseActivity
}
}
boolean copyWallet(File srcWallet, File dstWallet, boolean overwrite,
boolean ignoreCacheError) {
boolean copyWallet(File srcWallet, File dstWallet, boolean overwrite, boolean ignoreCacheError) {
if (walletExists(dstWallet, true) && !overwrite) return false;
boolean success = false;
File srcDir = srcWallet.getParentFile();
@ -1123,19 +1020,14 @@ public class LoginActivity extends BaseActivity
success = addressFile.delete() && success;
}
Timber.d("deleteWallet is %s", success);
KeyStoreHelper.removeWalletUserPass(this, walletFile.getName());
return success;
}
void copyFile(File src, File dst) throws IOException {
FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel();
try {
try (FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
} finally {
if (inChannel != null)
inChannel.close();
if (outChannel != null)
outChannel.close();
}
}
@ -1220,63 +1112,64 @@ public class LoginActivity extends BaseActivity
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_create_help_new:
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_new);
return true;
case R.id.action_create_help_keys:
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_keys);
return true;
case R.id.action_create_help_view:
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_view);
return true;
case R.id.action_create_help_seed:
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_seed);
return true;
case R.id.action_create_help_ledger:
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_ledger);
return true;
case R.id.action_details_help:
HelpFragment.display(getSupportFragmentManager(), R.string.help_details);
return true;
case R.id.action_details_changepw:
onWalletChangePassword();
return true;
case R.id.action_license_info:
AboutFragment.display(getSupportFragmentManager());
return true;
case R.id.action_help_list:
HelpFragment.display(getSupportFragmentManager(), R.string.help_list);
return true;
case R.id.action_help_node:
HelpFragment.display(getSupportFragmentManager(), R.string.help_node);
return true;
case R.id.action_default_nodes: {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if ((WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) &&
(f instanceof NodeFragment)) {
((NodeFragment) f).restoreDefaultNodes();
}
return true;
final int id = item.getItemId();
if (id == R.id.action_create_help_new) {
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_new);
return true;
} else if (id == R.id.action_create_help_keys) {
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_keys);
return true;
} else if (id == R.id.action_create_help_view) {
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_view);
return true;
} else if (id == R.id.action_create_help_seed) {
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_seed);
return true;
} else if (id == R.id.action_create_help_ledger) {
HelpFragment.display(getSupportFragmentManager(), R.string.help_create_ledger);
return true;
} else if (id == R.id.action_details_help) {
HelpFragment.display(getSupportFragmentManager(), R.string.help_details);
return true;
} else if (id == R.id.action_details_changepw) {
onWalletChangePassword();
return true;
} else if (id == R.id.action_license_info) {
AboutFragment.display(getSupportFragmentManager());
return true;
} else if (id == R.id.action_help_list) {
HelpFragment.display(getSupportFragmentManager(), R.string.help_list);
return true;
} else if (id == R.id.action_help_node) {
HelpFragment.display(getSupportFragmentManager(), R.string.help_node);
return true;
} else if (id == R.id.action_default_nodes) {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if ((WalletManager.getInstance().getNetworkType() == NetworkType.NetworkType_Mainnet) &&
(f instanceof NodeFragment)) {
((NodeFragment) f).restoreDefaultNodes();
}
case R.id.action_privacy_policy:
PrivacyFragment.display(getSupportFragmentManager());
return true;
case R.id.action_language:
onChangeLocale();
return true;
case R.id.action_theme:
onChangeTheme();
return true;
case R.id.action_ledger_seed: {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (f instanceof GenerateFragment) {
((GenerateFragment) f).convertLedgerSeed();
}
return true;
return true;
} else if (id == R.id.action_privacy_policy) {
PrivacyFragment.display(getSupportFragmentManager());
return true;
} else if (id == R.id.action_language) {
onChangeLocale();
return true;
} else if (id == R.id.action_theme) {
onChangeTheme();
return true;
} else if (id == R.id.action_restore) {
onWalletRestore();
return true;
} else if (id == R.id.action_ledger_seed) {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (f instanceof GenerateFragment) {
((GenerateFragment) f).convertLedgerSeed();
}
default:
return super.onOptionsItemSelected(item);
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@ -1317,7 +1210,7 @@ public class LoginActivity extends BaseActivity
if (result) {
Timber.d("selected wallet is .%s.", node.getName());
// now it's getting real, onValidateFields if wallet exists
promptAndStart(walletName, node, streetmode);
promptAndStart(walletName, streetmode);
} else {
if (node.getResponseCode() == 0) { // IOException
Toast.makeText(LoginActivity.this, getString(R.string.status_wallet_node_invalid), Toast.LENGTH_LONG).show();
@ -1333,25 +1226,21 @@ public class LoginActivity extends BaseActivity
String keyPath = new File(Helper.getWalletRoot(LoginActivity.this),
walletName + ".keys").getAbsolutePath();
// check if we need connected hardware
Wallet.Device device =
WalletManager.getInstance().queryWalletDevice(keyPath, password);
switch (device) {
case Device_Ledger:
if (!hasLedger()) {
toast(R.string.open_wallet_ledger_missing);
} else {
return true;
}
break;
default:
// device could be undefined meaning the password is wrong
// this gets dealt with later
Wallet.Device device = WalletManager.getInstance().queryWalletDevice(keyPath, password);
if (device == Wallet.Device.Device_Ledger) {
if (!hasLedger()) {
toast(R.string.open_wallet_ledger_missing);
} else {
return true;
}
} else {// device could be undefined meaning the password is wrong
// this gets dealt with later
return true;
}
return false;
}
void promptAndStart(String walletName, Node node, final boolean streetmode) {
void promptAndStart(String walletName, final boolean streetmode) {
File walletFile = Helper.getWalletFile(this, walletName);
if (WalletManager.getInstance().walletExists(walletFile)) {
Helper.promptPassword(LoginActivity.this, walletName, false,
@ -1418,38 +1307,23 @@ public class LoginActivity extends BaseActivity
Ledger.connect(usbManager, usbDevice);
if (!Ledger.check()) {
Ledger.disconnect();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_start_app, usbDevice.getProductName()),
Toast.LENGTH_SHORT)
.show();
}
});
runOnUiThread(() -> Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_start_app, usbDevice.getProductName()),
Toast.LENGTH_SHORT)
.show());
} else {
registerDetachReceiver();
onLedgerAction();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
Toast.LENGTH_SHORT)
.show();
}
});
runOnUiThread(() -> Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_attached, usbDevice.getProductName()),
Toast.LENGTH_SHORT)
.show());
}
} catch (IOException ex) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.open_wallet_ledger_missing),
Toast.LENGTH_SHORT)
.show();
}
});
runOnUiThread(() -> Toast.makeText(LoginActivity.this,
getString(R.string.open_wallet_ledger_missing),
Toast.LENGTH_SHORT)
.show());
}
}
@ -1507,15 +1381,10 @@ public class LoginActivity extends BaseActivity
final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
Timber.i("Ledger detached!");
if (device != null)
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_detached, device.getProductName()),
Toast.LENGTH_SHORT)
.show();
}
});
runOnUiThread(() -> Toast.makeText(LoginActivity.this,
getString(R.string.toast_ledger_detached, device.getProductName()),
Toast.LENGTH_SHORT)
.show());
Ledger.disconnect();
onLedgerAction();
}

@ -31,7 +31,6 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -62,8 +61,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
private WalletInfoAdapter adapter;
private List<WalletManager.WalletInfo> walletList = new ArrayList<>();
private List<WalletManager.WalletInfo> displayedList = new ArrayList<>();
private final List<WalletManager.WalletInfo> walletList = new ArrayList<>();
private View tvGuntherSays;
private ImageView ivGunther;
@ -86,7 +84,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void onWalletBackup(String name);
void onWalletArchive(String walletName);
void onWalletRestore();
void onWalletDelete(String walletName);
void onAddWallet(String type);
@ -110,7 +110,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
@Override
public void onAttach(Context context) {
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof Listener) {
this.activityCallback = (Listener) context;
@ -200,12 +200,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
// Wallet touched
@Override
public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) {
String addressPrefix = WalletManager.getInstance().addressPrefix();
if (addressPrefix.indexOf(infoItem.address.charAt(0)) < 0) {
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
return;
}
openWallet(infoItem.name, false);
openWallet(infoItem.getName(), false);
}
private void openWallet(String name, boolean streetmode) {
@ -214,48 +209,32 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo listItem) {
switch (item.getItemId()) {
case R.id.action_streetmode:
openWallet(listItem.name, true);
break;
case R.id.action_info:
showInfo(listItem.name);
break;
case R.id.action_rename:
activityCallback.onWalletRename(listItem.name);
break;
case R.id.action_backup:
activityCallback.onWalletBackup(listItem.name);
break;
case R.id.action_archive:
activityCallback.onWalletArchive(listItem.name);
break;
default:
return super.onContextItemSelected(item);
final int id = item.getItemId();
if (id == R.id.action_streetmode) {
openWallet(listItem.getName(), true);
} else if (id == R.id.action_info) {
showInfo(listItem.getName());
} else if (id == R.id.action_rename) {
activityCallback.onWalletRename(listItem.getName());
} else if (id == R.id.action_backup) {
activityCallback.onWalletBackup(listItem.getName());
} else if (id == R.id.action_archive) {
activityCallback.onWalletDelete(listItem.getName());
} else {
return super.onContextItemSelected(item);
}
return true;
}
private void filterList() {
displayedList.clear();
String addressPrefix = WalletManager.getInstance().addressPrefix();
for (WalletManager.WalletInfo s : walletList) {
if (addressPrefix.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s);
}
}
public void loadList() {
Timber.d("loadList()");
WalletManager mgr = WalletManager.getInstance();
List<WalletManager.WalletInfo> walletInfos =
mgr.findWallets(activityCallback.getStorageRoot());
walletList.clear();
walletList.addAll(walletInfos);
filterList();
adapter.setInfos(displayedList);
walletList.addAll(mgr.findWallets(activityCallback.getStorageRoot()));
adapter.setInfos(walletList);
// deal with Gunther & FAB animation
if (displayedList.isEmpty()) {
if (walletList.isEmpty()) {
fab.startAnimation(fab_pulse);
if (ivGunther.getDrawable() == null) {
ivGunther.setImageResource(R.drawable.ic_emptygunther);
@ -274,7 +253,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
.getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
.getAll().keySet();
for (WalletManager.WalletInfo s : walletList) {
removedWallets.remove(s.name);
removedWallets.remove(s.getName());
}
for (String name : removedWallets) {
KeyStoreHelper.removeWalletUserPass(getActivity(), name);
@ -292,7 +271,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.list_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@ -362,37 +341,29 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public void onClick(View v) {
int id = v.getId();
final int id = v.getId();
Timber.d("onClick %d/%d", id, R.id.fabLedger);
switch (id) {
case R.id.fab:
animateFAB();
break;
case R.id.fabNew:
fabScreen.setVisibility(View.INVISIBLE);
isFabOpen = false;
activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
break;
case R.id.fabView:
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY);
break;
case R.id.fabKey:
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_KEY);
break;
case R.id.fabSeed:
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
break;
case R.id.fabLedger:
Timber.d("FAB_LEDGER");
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
break;
case R.id.fabScreen:
animateFAB();
break;
if (id == R.id.fab) {
animateFAB();
} else if (id == R.id.fabNew) {
fabScreen.setVisibility(View.INVISIBLE);
isFabOpen = false;
activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
} else if (id == R.id.fabView) {
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY);
} else if (id == R.id.fabKey) {
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_KEY);
} else if (id == R.id.fabSeed) {
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
} else if (id == R.id.fabLedger) {
Timber.d("FAB_LEDGER");
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
} else if (id == R.id.fabScreen) {
animateFAB();
}
}

@ -74,7 +74,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).name.equals(mNewList.get(newItemPosition).name);
return mOldList.get(oldItemPosition).getName().equals(mNewList.get(newItemPosition).getName());
}
@Override
@ -86,9 +86,9 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
@NonNull
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_wallet, parent, false);
return new ViewHolder(view);
return new ViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_wallet, parent, false)
);
}
@Override
@ -122,7 +122,6 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final TextView tvName;
final TextView tvAddress;
final ImageButton ibOptions;
WalletManager.WalletInfo infoItem;
boolean popupOpen = false;
@ -130,7 +129,6 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
ViewHolder(View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
tvAddress = itemView.findViewById(R.id.tvAddress);
ibOptions = itemView.findViewById(R.id.ibOptions);
ibOptions.setOnClickListener(view -> {
if (popupOpen) return;
@ -160,8 +158,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
void bind(int position) {
infoItem = infoItems.get(position);
tvName.setText(infoItem.name);
tvAddress.setText(infoItem.address.substring(0, 16) + "...");
tvName.setText(infoItem.getName());
}
@Override

@ -21,15 +21,13 @@ import com.m2049r.xmrwallet.data.Node;
import com.m2049r.xmrwallet.ledger.Ledger;
import com.m2049r.xmrwallet.util.RestoreHeight;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import lombok.Getter;
import timber.log.Timber;
public class WalletManager {
@ -103,7 +101,8 @@ public class WalletManager {
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;
}
@ -210,44 +209,20 @@ public class WalletManager {
//public native List<String> findWallets(String path); // this does not work - some error in boost
public class WalletInfo implements Comparable<WalletInfo> {
public File path;
public String name;
public String address;
@Getter
final private File path;
@Getter
final private String name;
public WalletInfo(File wallet) {
path = wallet.getParentFile();
name = wallet.getName();
}
@Override
public int compareTo(WalletInfo another) {
int n = name.toLowerCase().compareTo(another.name.toLowerCase());
if (n != 0) {
return n;
} else { // wallet names are the same
return address.compareTo(another.address);
}
}
}
public WalletInfo getWalletInfo(File wallet) {
WalletInfo info = new WalletInfo();
info.path = wallet.getParentFile();
info.name = wallet.getName();
File addressFile = new File(info.path, info.name + ".address.txt");
//Timber.d(addressFile.getAbsolutePath());
info.address = "??????";
BufferedReader addressReader = null;
try {
addressReader = new BufferedReader(new FileReader(addressFile));
info.address = addressReader.readLine();
} catch (IOException ex) {
Timber.d(ex.getLocalizedMessage());
} finally {
if (addressReader != null) {
try {
addressReader.close();
} catch (IOException ex) {
// that's just too bad
}
}
return name.toLowerCase().compareTo(another.name.toLowerCase());
}
return info;
}
public List<WalletInfo> findWallets(File path) {
@ -261,7 +236,7 @@ public class WalletManager {
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(getWalletInfo(f));
wallets.add(new WalletInfo(f));
}
return wallets;
}

@ -34,7 +34,6 @@ import android.hardware.fingerprint.FingerprintManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.Environment;
import android.os.StrictMode;
import android.system.ErrnoException;
import android.system.Os;
@ -77,16 +76,12 @@ import javax.net.ssl.HttpsURLConnection;
import timber.log.Timber;
public class Helper {
static private final String FLAVOR_SUFFIX =
(BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
+ (BuildConfig.DEBUG ? "-debug" : "");
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
static public final String BASE_CRYPTO = "XMR";
static private final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
static private final String HOME_DIR = "monero" + FLAVOR_SUFFIX;
static private final String WALLET_DIR = "wallets";
static private final String MONERO_DIR = "monero";
static public int DISPLAY_DIGITS_INFO = 5;
@ -95,12 +90,7 @@ public class Helper {
}
static public File getStorage(Context context, String folderName) {
if (!isExternalStorageWritable()) {
String msg = context.getString(R.string.message_strorage_not_writable);
Timber.e(msg);
throw new IllegalStateException(msg);
}
File dir = new File(Environment.getExternalStorageDirectory(), folderName);
File dir = new File(context.getFilesDir(), folderName);
if (!dir.exists()) {
Timber.i("Creating %s", dir.getAbsolutePath());
dir.mkdirs(); // try to make it
@ -113,24 +103,6 @@ public class Helper {
return dir;
}
static public final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
static public boolean getWritePermission(Activity context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_DENIED) {
Timber.w("Permission denied to WRITE_EXTERNAL_STORAGE - requesting it");
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
context.requestPermissions(permissions, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
return false;
} else {
return true;
}
} else {
return true;
}
}
static public final int PERMISSIONS_REQUEST_CAMERA = 7;
static public boolean getCameraPermission(Activity context) {
@ -156,12 +128,6 @@ public class Helper {
return f;
}
/* Checks if external storage is available for read and write */
private static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
static public void showKeyboard(Activity act) {
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
final View focus = act.getCurrentFocus();
@ -339,7 +305,7 @@ public class Helper {
static public void setMoneroHome(Context context) {
try {
String home = getStorage(context, HOME_DIR).getAbsolutePath();
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
Os.setenv("HOME", home, true);
} catch (ErrnoException ex) {
throw new IllegalStateException(ex);
@ -355,7 +321,7 @@ public class Helper {
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
static public void initLogger(Context context, int level) {
String home = getStorage(context, HOME_DIR).getAbsolutePath();
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
WalletManager.initLogger(home + "/monerujo", "monerujo.log");
if (level >= WalletManager.LOGLEVEL_SILENT)
WalletManager.setLogLevel(level);

@ -24,9 +24,10 @@ import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import androidx.annotation.NonNull;
import android.util.Base64;
import androidx.annotation.NonNull;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
@ -167,6 +168,11 @@ public class KeyStoreHelper {
.remove(wallet).apply();
}
public static void copyWalletUserPass(Context context, String srcWallet, String dstWallet) throws BrokenPasswordStoreException {
final String pass = loadWalletUserPass(context, srcWallet);
saveWalletUserPass(context, dstWallet, pass);
}
/**
* 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.

@ -0,0 +1,170 @@
package com.m2049r.xmrwallet.util;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.preference.PreferenceManager;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.model.WalletManager;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
import timber.log.Timber;
@RequiredArgsConstructor
public class LegacyStorageHelper {
final private File srcDir;
final private File dstDir;
static public void migrateWallets(Context context) {
try {
if (isStorageMigrated(context)) return;
if (!hasWritePermission(context)) {
// nothing to migrate, so don't try again
setStorageMigrated(context);
return;
}
final File oldRoot = getWalletRoot();
if (!oldRoot.exists()) {
// nothing to migrate, so don't try again
setStorageMigrated(context);
return;
}
final File newRoot = Helper.getWalletRoot(context);
(new LegacyStorageHelper(oldRoot, newRoot)).migrate();
setStorageMigrated(context); // done it once - don't try again
} catch (IllegalStateException ex) {
Timber.d(ex);
// nothing we can do here
}
}
public void migrate() {
String addressPrefix = WalletManager.getInstance().addressPrefix();
File[] wallets = srcDir.listFiles((dir, filename) -> filename.endsWith(".keys"));
for (File wallet : wallets) {
final String walletName = wallet.getName().substring(0, wallet.getName().length() - ".keys".length());
if (addressPrefix.indexOf(getAddress(walletName).charAt(0)) < 0) {
Timber.d("skipping %s", walletName);
continue;
}
try {
copy(walletName);
} catch (IOException ex) { // something failed - try to clean up
deleteDst(walletName);
}
}
}
// return "@" by default so we don't need to deal with null stuff
private String getAddress(String walletName) {
File addressFile = new File(srcDir, walletName + ".address.txt");
if (!addressFile.exists()) return "@";
try (BufferedReader addressReader = new BufferedReader(new FileReader(addressFile))) {
return addressReader.readLine();
} catch (IOException ex) {
Timber.d(ex.getLocalizedMessage());
}
return "@";
}
private void copy(String walletName) throws IOException {
final String dstName = getUniqueName(dstDir, walletName);
copyFile(new File(srcDir, walletName), new File(dstDir, dstName));
copyFile(new File(srcDir, walletName + ".keys"), new File(dstDir, dstName + ".keys"));
}
private void deleteDst(String walletName) {
// do our best, but if it fails, it fails
(new File(dstDir, walletName)).delete();
(new File(dstDir, walletName + ".keys")).delete();
}
private void copyFile(File src, File dst) throws IOException {
if (!src.exists()) return;
Timber.d("%s => %s", src.getAbsolutePath(), dst.getAbsolutePath());
try (FileChannel inChannel = new FileInputStream(src).getChannel();
FileChannel outChannel = new FileOutputStream(dst).getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
}
}
private static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
private static File getWalletRoot() {
if (!isExternalStorageWritable())
throw new IllegalStateException();
// wallet folder for legacy (pre-Q) installations
final String FLAVOR_SUFFIX =
(BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
+ (BuildConfig.DEBUG ? "-debug" : "");
final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR);
if (!dir.exists() || !dir.isDirectory())
throw new IllegalStateException();
return dir;
}
private static boolean hasWritePermission(Context context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
return context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED;
} else {
return true;
}
}
private static final Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
private static String getUniqueName(File root, String name) {
if (!(new File(root, name + ".keys")).exists()) // <name> does not exist => it's ok to use
return name;
File[] wallets = root.listFiles(
(dir, filename) -> {
Matcher m = WALLET_PATTERN.matcher(filename);
if (m.find())
return m.group(1).equals(name);
else return false;
});
if (wallets.length == 0) return name + " (1)";
int maxIndex = 0;
for (File wallet : wallets) {
try {
final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
if (!m.find())
throw new IllegalStateException("this must match as it did before");
final int index = Integer.parseInt(m.group(2));
if (index > maxIndex) maxIndex = index;
} catch (NumberFormatException ex) {
// this cannot happen & we can ignore it if it does
}
}
return name + " (" + (maxIndex + 1) + ")";
}
private static final String MIGRATED_KEY = "migrated_legacy_storage";
public static boolean isStorageMigrated(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MIGRATED_KEY, false);
}
public static void setStorageMigrated(Context context) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(MIGRATED_KEY, true).apply();
}
}

@ -0,0 +1,64 @@
package com.m2049r.xmrwallet.util;
import android.content.Context;
import android.net.Uri;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class ZipBackup {
final private Context context;
final private String walletName;
private ZipOutputStream zip;
public void writeTo(Uri zipUri) throws IOException {
if (zip != null)
throw new IllegalStateException("zip already initialized");
try {
zip = new ZipOutputStream(context.getContentResolver().openOutputStream(zipUri));
final File walletRoot = Helper.getWalletRoot(context);
addFile(new File(walletRoot, walletName + ".keys"));
addFile(new File(walletRoot, walletName));
zip.close();
} finally {
if (zip != null) zip.close();
}
}
private void addFile(File file) throws IOException {
if (!file.exists()) return; // ignore missing files (e.g. the cache file might not exist)
ZipEntry entry = new ZipEntry(file.getName());
zip.putNextEntry(entry);
writeFile(file);
zip.closeEntry();
}
private void writeFile(File source) throws IOException {
try (InputStream is = new FileInputStream(source)) {
byte[] buffer = new byte[8192];
int length;
while ((length = is.read(buffer)) > 0) {
zip.write(buffer, 0, length);
}
}
}
private static final SimpleDateFormat DATETIME_FORMATTER =
new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss");
public String getBackupName() {
return walletName + " " + DATETIME_FORMATTER.format(new Date());
}
}

@ -0,0 +1,139 @@
package com.m2049r.xmrwallet.util;
import android.content.Context;
import android.net.Uri;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import lombok.RequiredArgsConstructor;
import timber.log.Timber;
@RequiredArgsConstructor
public class ZipRestore {
final private Context context;
final private Uri zipUri;
private File walletRoot;
private ZipInputStream zip;
public boolean restore() throws IOException {
walletRoot = Helper.getWalletRoot(context);
String walletName = testArchive();
if (walletName == null) return false;
walletName = getUniqueName(walletName);
if (zip != null)
throw new IllegalStateException("zip already initialized");
try {
zip = new ZipInputStream(context.getContentResolver().openInputStream(zipUri));
for (ZipEntry entry = zip.getNextEntry(); entry != null; zip.closeEntry(), entry = zip.getNextEntry()) {
File destination;
final String name = entry.getName();
if (name.endsWith(".keys")) {
destination = new File(walletRoot, walletName + ".keys");
} else if (name.endsWith(".address.txt")) {
destination = new File(walletRoot, walletName + ".address.txt");
} else {
destination = new File(walletRoot, walletName);
}
writeFile(destination);
}
} finally {
if (zip != null) zip.close();
}
return true;
}
private void writeFile(File destination) throws IOException {
try (OutputStream os = new FileOutputStream(destination)) {
byte[] buffer = new byte[8192];
int length;
while ((length = zip.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
}
}
// test the archive to contain files we expect & return the name of the contained wallet or null
private String testArchive() {
String walletName = null;
boolean keys = false;
ZipInputStream zipStream = null;
try {
zipStream = new ZipInputStream(context.getContentResolver().openInputStream(zipUri));
for (ZipEntry entry = zipStream.getNextEntry(); entry != null;
zipStream.closeEntry(), entry = zipStream.getNextEntry()) {
if (entry.isDirectory())
return null;
final String name = entry.getName();
if ((new File(name)).getParentFile() != null)
return null;
if (walletName == null) {
if (name.endsWith(".keys")) {
walletName = name.substring(0, name.length() - ".keys".length());
keys = true; // we have they keys
} else if (name.endsWith(".address.txt")) {
walletName = name.substring(0, name.length() - ".address.txt".length());
} else {
walletName = name;
}
} else { // we have a wallet name
if (name.endsWith(".keys")) {
if (!name.equals(walletName + ".keys")) return null;
keys = true; // we have they keys
} else if (name.endsWith(".address.txt")) {
if (!name.equals(walletName + ".address.txt")) return null;
} else if (!name.equals(walletName)) return null;
}
}
} catch (IOException ex) {
return null;
} finally {
try {
if (zipStream != null) zipStream.close();
} catch (IOException ex) {
Timber.w(ex);
}
}
// we need the keys at least
if (keys) return walletName;
else return null;
}
final static Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
private String getUniqueName(String name) {
if (!(new File(walletRoot, name + ".keys")).exists()) // <name> does not exist => it's ok to use
return name;
File[] wallets = walletRoot.listFiles(
(dir, filename) -> {
Matcher m = WALLET_PATTERN.matcher(filename);
if (m.find())
return m.group(1).equals(name);
else return false;
});
if (wallets.length == 0) return name + " (1)";
int maxIndex = 0;
for (File wallet : wallets) {
try {
final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
m.find();
final int index = Integer.parseInt(m.group(2));
if (index > maxIndex) maxIndex = index;
} catch (NumberFormatException ex) {
// this cannot happen & we can ignore it if it does
}
}
return name + " (" + (maxIndex + 1) + ")";
}
}

@ -18,19 +18,9 @@
android:layout_height="wrap_content"
android:layout_marginStart="48dp"
android:layout_marginTop="16dp"
android:layout_toStartOf="@+id/ibOptions"
tools:text="My Wallet" />
<TextView
android:id="@+id/tvAddress"
style="@style/MoneroText.Small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tvName"
android:layout_marginStart="48dp"
android:layout_marginBottom="16dp"
android:layout_toStartOf="@+id/ibOptions"
tools:text="4AdkPJox...g3viqCuk" />
tools:text="My Wallet" />
<ImageButton
android:id="@+id/ibOptions"

@ -19,7 +19,7 @@
<item
android:id="@+id/action_archive"
android:orderInCategory="400"
android:title="@string/menu_archive" />
android:title="@string/menu_delete" />
<item
android:id="@+id/action_info"

@ -33,4 +33,9 @@
android:title="@string/menu_daynight"
app:showAsAction="never" />
<item
android:id="@+id/action_restore"
android:orderInCategory="600"
android:title="@string/menu_restore"
app:showAsAction="never" />
</menu>

@ -9,7 +9,6 @@
<string name="menu_help">Ajuda</string>
<string name="menu_receive">Rebre</string>
<string name="menu_rename">Canvi de nom</string>
<string name="menu_archive">Arxivar</string>
<string name="menu_backup">Còpia de seguretat</string>
<string name="menu_changepw">Canvi de contrasenya</string>
@ -94,7 +93,6 @@
<string name="backup_success">Còpia de seguretat amb èxit</string>
<string name="backup_failed">Còpia de seguretat fallida!</string>
<string name="archive_failed">Arxiu fallit!</string>
<string name="rename_failed">Canvi de nom fallit!</string>
<string name="changepw_failed">Canvi de contrasenya fallit!</string>
<string name="changepw_success">La contrasenya ha canviat</string>
@ -273,9 +271,8 @@
<string name="details_alert_no">Torna\'m enrere!</string>
<string name="details_title">Detalls</string>
<string name="archive_alert_message">Es farà una còpia de seguretat del portamonedes i s\'esborrarà.</string>
<string name="archive_alert_yes">Si, fes-ho!</string>
<string name="archive_alert_no">No gràcies!</string>
<string name="delete_alert_yes">Si, fes-ho!</string>
<string name="delete_alert_no">No gràcies!</string>
<string name="fab_create_new">Crear un nou portamonedes</string>
<string name="fab_restore_viewonly">Restablir portamonedes de només lectura</string>
@ -420,4 +417,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,7 @@
<string name="menu_help">Hilfe</string>
<string name="menu_receive">Empfangen</string>
<string name="menu_rename">Umbenennen</string>
<string name="menu_archive">Archivieren</string>
<string name="menu_delete">Löschen</string>
<string name="menu_backup">Backup</string>
<string name="menu_changepw">Passwort ändern</string>
@ -94,7 +94,7 @@
<string name="backup_success">Backup erfolgreich</string>
<string name="backup_failed">Backup fehlgeschlagen!</string>
<string name="archive_failed">Archivierung fehlgeschlagen!</string>
<string name="delete_failed">Löschen fehlgeschlagen!</string>
<string name="rename_failed">Umbenennen fehlgeschlagen!</string>
<string name="changepw_failed">Passwort ändern fehlgeschlagen!</string>
<string name="changepw_success">Passwort geändert</string>
@ -266,9 +266,9 @@
<string name="details_alert_no">Nein, doch nicht!</string>
<string name="details_title">Details</string>
<string name="archive_alert_message">Das Wallet wird archiviert und dann gelöscht!</string>
<string name="archive_alert_yes">Ja, mach das!</string>
<string name="archive_alert_no">Nein, danke!</string>
<string name="delete_alert_message">Das Wallet wird gelöscht!</string>
<string name="delete_alert_yes">Ja, mach das!</string>
<string name="delete_alert_no">Nein, danke!</string>
<string name="fab_create_new">Neues Wallet erstellen</string>
<string name="fab_restore_viewonly">View-Only-Wallet wiederherstellen</string>
@ -422,4 +422,6 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Βοήθεια</string>
<string name="menu_receive">Λήψη</string>
<string name="menu_rename">Μετονομασία</string>
<string name="menu_archive">Αρχειοθέτησε</string>
<string name="menu_backup">Δημιουργία αντίγραφου ασφαλείας</string>
<string name="password_weak">Συνέχισε να πληκτρολογείς &#8230;</string>
@ -92,7 +91,6 @@
<string name="backup_success">Η δημιουργία αντίγραφου ασφαλείας ήταν επιτυχής</string>
<string name="backup_failed">Η δημιουργία αντίγραφου ασφαλείας απέτυχε!</string>
<string name="archive_failed">Η αρχειοθέτηση απέτυχε!</string>
<string name="rename_failed">Η μετονομασία απέτυχε!</string>
<string name="label_daemon">Κόμβος(Δαίμονας)</string>
@ -241,9 +239,8 @@
<string name="details_alert_no">Πήγαινε με πίσω!</string>
<string name="details_title">Λεπτομέρειες</string>
<string name="archive_alert_message">Το πορτοφόλι θα δημιουργήσει αντίγραφο ασφαλείας και στη συνέχεια θα διαγραφεί!</string>
<string name="archive_alert_yes">Ναι, κάνε αυτό!</string>
<string name="archive_alert_no">Όχι ευχαριστώ!</string>
<string name="delete_alert_yes">Ναι, κάνε αυτό!</string>
<string name="delete_alert_no">Όχι ευχαριστώ!</string>
<string name="fab_create_new">Δημιουργία νέου πορτοφολιού</string>
<string name="fab_restore_viewonly">Επαναφορά πορτοφολιού προβολής-μόνο</string>
@ -422,4 +419,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Helpo</string>
<string name="menu_receive">Ricevi</string>
<string name="menu_rename">Renomi</string>
<string name="menu_archive">Arkivo</string>
<string name="menu_backup">Sekurkopio</string>
<string name="menu_changepw">Ŝanĝi pasfrazon</string>
@ -94,7 +93,6 @@
<string name="backup_success">Sekurkopio sukcesis!</string>
<string name="backup_failed">Sekurkopio malsukcesis!</string>
<string name="archive_failed">Arkivado malsukcesis!</string>
<string name="rename_failed">Renomado malsukcesis!</string>
<string name="changepw_failed">Pasvortŝanĝo malsukcesis!</string>
<string name="changepw_success">Pasvortŝanĝo sukcesis!</string>
@ -273,9 +271,8 @@
<string name="details_alert_no">Malantaŭen!</string>
<string name="details_title">Detaloj</string>
<string name="archive_alert_message">La monujo sekurkopiĝos kaj poste forviŝiĝos!</string>
<string name="archive_alert_yes">Jes, faru tion!</string>
<string name="archive_alert_no">Ne dankon!</string>
<string name="delete_alert_yes">Jes, faru tion!</string>
<string name="delete_alert_no">Ne dankon!</string>
<string name="fab_create_new">Krei novan monujon</string>
<string name="fab_restore_viewonly">Restaŭri nurvidan monujon</string>
@ -422,4 +419,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Ayuda</string>
<string name="menu_receive">Recibir</string>
<string name="menu_rename">Renombrar</string>
<string name="menu_archive">Archivar</string>
<string name="menu_backup">Copia de seguridad</string>
<string name="menu_changepw">Cambiar contraseña</string>
@ -49,7 +48,6 @@
<string name="backup_success">Copia de seguridad exitosa</string>
<string name="backup_failed">¡Copia de seguridad fallida!</string>
<string name="archive_failed">¡Archivado fallido!</string>
<string name="rename_failed">¡Cambio de nombre fallido!</string>
<string name="changepw_failed">¡Cambio de contraseña fallido!</string>
<string name="changepw_success">Contraseña cambiada</string>
@ -209,9 +207,9 @@
<string name="details_alert_no">¡Llévame de vuelta!</string>
<string name="details_title">Detalles</string>
<string name="archive_alert_message">¡El monedero será copiado y después borrado!</string>
<string name="archive_alert_yes">¡Sí, hazlo!</string>
<string name="archive_alert_no">¡No, gracias!</string>
<string name="delete_alert_message">¡El monedero será borrado!</string>
<string name="delete_alert_yes">¡Sí, hazlo!</string>
<string name="delete_alert_no">¡No, gracias!</string>
<string name="fab_create_new">Crear nuevo monedero</string>
<string name="fab_restore_viewonly">Restaurar monedero de sólo vista</string>
@ -413,4 +411,9 @@
<string name="subaddress_select_label">Elige una subdirección</string>
<string name="subaddress_details_hint">Presiona largo para ver detalles</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Abi</string>
<string name="menu_receive">Küsi raha</string>
<string name="menu_rename">Nimeta ümber</string>
<string name="menu_archive">Arhiveeri</string>
<string name="menu_backup">Tagavarakoopia</string>
<string name="menu_changepw">Vaheta parooli</string>
@ -93,7 +92,6 @@
<string name="service_progress">Teen ettevalmistusi &#8230;\nSee võib aega võtta!</string>
<string name="backup_failed">Tagavarakoopia ebaõnnestus!</string>
<string name="archive_failed">Arhiveerimine ebaõnnestus!</string>
<string name="rename_failed">Ümber nimetamine ebaõnnestus!</string>
<string name="changepw_failed">Parooli vahetamine ebaõnnestus!</string>
<string name="changepw_success">Parool vahetatud</string>
@ -268,9 +266,8 @@
<string name="details_alert_no">Vii mind tagasi!</string>
<string name="details_title">Lisainfo</string>
<string name="archive_alert_message">Rahakotist tehakse tagavarakoopia ja siis see kustutatakse!</string>
<string name="archive_alert_yes">Täpselt nii!</string>
<string name="archive_alert_no">Ei, tänan!</string>
<string name="delete_alert_yes">Täpselt nii!</string>
<string name="delete_alert_no">Ei, tänan!</string>
<string name="fab_create_new">Loo uus rahakott</string>
<string name="fab_restore_viewonly">Taasta rahakott vaatamiseks</string>
@ -420,4 +417,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Aide</string>
<string name="menu_receive">Recevoir</string>
<string name="menu_rename">Renommer</string>
<string name="menu_archive">Archiver</string>
<string name="menu_backup">Sauvegarder</string>
<string name="menu_changepw">Modifier Phrase secrète</string>
@ -95,7 +94,6 @@
<string name="backup_success">Sauvegarde réussie</string>
<string name="backup_failed">Sauvegarde Échouée !</string>
<string name="archive_failed">Archivage Échouée !</string>
<string name="rename_failed">Renommage Échoué !</string>
<string name="changepw_failed">Modification du mot de passe Échouée !</string>
<string name="changepw_success">Modification du mot de passe réussie</string>
@ -269,9 +267,9 @@
<string name="details_alert_no">Non merci !</string>
<string name="details_title">Détails</string>
<string name="archive_alert_message">Le portefeuille sera sauvegardé puis supprimé !</string>
<string name="archive_alert_yes">Oui, procéder !</string>
<string name="archive_alert_no">Non merci !</string>
<string name="delete_alert_message">Le portefeuille sera supprimé !</string>
<string name="delete_alert_yes">Oui, procéder !</string>
<string name="delete_alert_no">Non merci !</string>
<string name="fab_create_new">Créer un nouveau portefeuille</string>
<string name="fab_restore_viewonly">Restaurer un portefeuille d\'audit</string>
@ -426,4 +424,9 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Segítség</string>
<string name="menu_receive">Fogadás</string>
<string name="menu_rename">Átnevezés</string>
<string name="menu_archive">Archiválás</string>
<string name="menu_backup">Biztonsági mentés</string>
<string name="menu_changepw">Jelszómódosítás</string>
@ -93,7 +92,6 @@
<string name="service_progress">Műveletek befejezése&#8230;\nEz eltarthat egy ideig!</string>
<string name="backup_failed">Sikertelen biztonsági mentés!</string>
<string name="archive_failed">Sikertelen archiválás!</string>
<string name="rename_failed">Sikertelen átnevezés!</string>
<string name="changepw_failed">Sikertelen jelszómódosítás!</string>
<string name="changepw_success">Jelszó megváltoztatva</string>
@ -266,9 +264,8 @@
<string name="details_alert_no">Inkább ne!</string>
<string name="details_title">Részletek</string>
<string name="archive_alert_message">A tárcáról biztonsági másolat készül, majd törlésre fog kerülni!</string>
<string name="archive_alert_yes">Igen, mehet!</string>
<string name="archive_alert_no">Inkább ne!</string>
<string name="delete_alert_yes">Igen, mehet!</string>
<string name="delete_alert_no">Inkább ne!</string>
<string name="fab_create_new">Új tárca létrehozása</string>
<string name="fab_restore_viewonly">Figyelőtárca visszaállítása</string>
@ -424,4 +421,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Aiuto</string>
<string name="menu_receive">Ricevi</string>
<string name="menu_rename">Rinomina</string>
<string name="menu_archive">Archivia</string>
<string name="menu_backup">Backup</string>
<string name="menu_changepw">Cambia passphrase</string>
@ -95,7 +94,6 @@
<string name="backup_success">Backup effettuato con successo</string>
<string name="backup_failed">Backup fallito!</string>
<string name="archive_failed">Archiviazione fallita!</string>
<string name="rename_failed">Rinomina fallita!</string>
<string name="changepw_failed">Modifica password fallita!</string>
<string name="changepw_success">Password cambiata</string>
@ -268,9 +266,9 @@
<string name="details_alert_no">Torna indietro!</string>
<string name="details_title">Dettagli</string>
<string name="archive_alert_message">Il portafoglio verrà archiviato e poi eliminato!</string>
<string name="archive_alert_yes">Sì, procedi!</string>
<string name="archive_alert_no">No grazie!</string>
<string name="delete_alert_message">Il portafoglio verrà eliminato!</string>
<string name="delete_alert_yes">Sì, procedi!</string>
<string name="delete_alert_no">No grazie!</string>
<string name="fab_create_new">Crea un nuovo portafoglio</string>
<string name="fab_restore_viewonly">Recupera un portafoglio solo-visualizzazione</string>
@ -425,4 +423,9 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">ヘルプ</string>
<string name="menu_receive">受取り</string>
<string name="menu_rename">名前の変更</string>
<string name="menu_archive">アーカイブ</string>
<string name="menu_backup">バックアップ</string>
<string name="menu_changepw">パスフレーズの変更</string>
@ -93,7 +92,6 @@
<string name="service_progress">準備中 &#8230;\nしばらく時間がかかるかもしれません!</string>
<string name="backup_failed">バックアップに失敗しました!</string>
<string name="archive_failed">アーカイブに失敗しました!</string>
<string name="rename_failed">名前の変更に失敗しました!</string>
<string name="changepw_failed">パスワード変更に失敗しました!</string>
<string name="changepw_success">パスワードが変更されました</string>
@ -273,9 +271,8 @@
<string name="details_alert_no">戻ります!</string>
<string name="details_title">詳細</string>
<string name="archive_alert_message">ウォレットはバックアップされてから削除されます!</string>
<string name="archive_alert_yes">はい、お願いします!</string>
<string name="archive_alert_no">いいえ、結構です!</string>
<string name="delete_alert_yes">はい、お願いします!</string>
<string name="delete_alert_no">いいえ、結構です!</string>
<string name="fab_create_new">新しいウォレットの作成</string>
<string name="fab_restore_viewonly">閲覧専用ウォレットを復元</string>
@ -425,4 +422,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Hjelp</string>
<string name="menu_receive">Motta</string>
<string name="menu_rename">Gi nytt navn</string>
<string name="menu_archive">Arkiver</string>
<string name="menu_backup">Backup</string>
<string name="menu_changepw">[Forandre passord]</string>
@ -93,7 +92,6 @@
<string name="backup_success">Backup suksessfull</string>
<string name="backup_failed">Backup feila!</string>
<string name="archive_failed">Arkivering feila!</string>
<string name="rename_failed">Kunne ikke gi nytt navn!</string>
<string name="changepw_failed">[Passordforandring feila!]</string>
<string name="changepw_success">[Passord forandra]</string>
@ -266,9 +264,8 @@
<string name="details_alert_no">Ta meg tilbake!</string>
<string name="details_title">Detaljer</string>
<string name="archive_alert_message">Lommeboka vil bli backa opp og så sletta!</string>
<string name="archive_alert_yes">Ja, gjør det!</string>
<string name="archive_alert_no">Nei takk!</string>
<string name="delete_alert_yes">Ja, gjør det!</string>
<string name="delete_alert_no">Nei takk!</string>
<string name="fab_create_new">Lag ny lommebok</string>
<string name="fab_restore_viewonly">Gjenoprett bare-se lommebok</string>
@ -422,4 +419,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Help</string>
<string name="menu_receive">Ontvangen</string>
<string name="menu_rename">Hernoemen</string>
<string name="menu_archive">Archiveren</string>
<string name="menu_backup">Back-up</string>
<string name="menu_changepw">Wachtzin wijzigen</string>
@ -93,7 +92,6 @@
<string name="service_progress">Bezig met voltooien…\nDit kan even duren.</string>
<string name="backup_failed">Back-up mislukt!</string>
<string name="archive_failed">Archiveren mislukt!</string>
<string name="rename_failed">Hernoemen mislukt!</string>
<string name="changepw_failed">Wachtwoord wijzigen mislukt!</string>
<string name="changepw_success">Wachtwoord is gewijzigd</string>
@ -263,9 +261,9 @@
<string name="details_alert_no">Ga terug!</string>
<string name="details_title">Details</string>
<string name="archive_alert_message">Er wordt een back-up gemaakt en daarna wordt de portemonnee verwijderd!</string>
<string name="archive_alert_yes">Ja, doe dat!</string>
<string name="archive_alert_no">Nee, niet doen!</string>
<string name="delete_alert_message">Er wordt de portemonnee verwijderd!</string>
<string name="delete_alert_yes">Ja, doe dat!</string>
<string name="delete_alert_no">Nee, niet doen!</string>
<string name="fab_create_new">Nieuwe portemonnee</string>
<string name="fab_restore_viewonly">Alleen-lezen portemonnee herstellen</string>
@ -422,4 +420,9 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Ajuda</string>
<string name="menu_receive">Receber</string>
<string name="menu_rename">Renomear</string>
<string name="menu_archive">Arquivar</string>
<string name="menu_backup">Backup</string>
<string name="menu_changepw">Alterar Senha</string>
@ -93,7 +92,6 @@
<string name="service_progress">Finalizando o processo &#8230;\nPode demorar um pouco</string>
<string name="backup_failed">Erro ao criar backup!</string>
<string name="archive_failed">Erro ao arquivar!</string>
<string name="rename_failed">Erro ao renomear!</string>
<string name="changepw_failed">Erro ao alterar senha!</string>
<string name="changepw_success">Senha alterada</string>
@ -265,9 +263,8 @@
<string name="details_alert_no">Tire-me daqui!</string>
<string name="details_title">Detalhes</string>
<string name="archive_alert_message">Farei um backup da carteira e depois vou deletá-la!</string>
<string name="archive_alert_yes">Sim, pode fazer!</string>
<string name="archive_alert_no">Não, obrigado!</string>
<string name="delete_alert_yes">Sim, pode fazer!</string>
<string name="delete_alert_no">Não, obrigado!</string>
<string name="fab_create_new">Criar nova carteira</string>
<string name="fab_restore_viewonly">Restaurar carteira \"Somente leitura\"</string>
@ -414,4 +411,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Ajuda</string>
<string name="menu_receive">Receber</string>
<string name="menu_rename">Renomear</string>
<string name="menu_archive">Arquivar</string>
<string name="menu_backup">Cópia de segurança</string>
<string name="menu_changepw">Alterar palavra passe</string>
@ -93,7 +92,6 @@
<string name="service_progress">A concluir o processamento &#8230;\nIsto pode demorar!</string>
<string name="backup_failed">Cópia de segurança falhou!</string>
<string name="archive_failed">Erro ao arquivar!</string>
<string name="rename_failed">Erro ao renomear!</string>
<string name="changepw_failed">Erro ao alterar a palavra passe!</string>
<string name="changepw_success">Palavra passe alterada</string>
@ -265,9 +263,8 @@
<string name="details_alert_no">Volta atrás!</string>
<string name="details_title">Detalhes</string>
<string name="archive_alert_message">Vai ser feita uma cópia de segurança da carteira e depois será apagada!</string>
<string name="archive_alert_yes">Sim, faz isso!</string>
<string name="archive_alert_no">Não obrigado!</string>
<string name="delete_alert_yes">Sim, faz isso!</string>
<string name="delete_alert_no">Não obrigado!</string>
<string name="fab_create_new">Criar nova carteira</string>
<string name="fab_restore_viewonly">Restaurar carteira apenas de visualização</string>
@ -426,4 +423,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Ajutor</string>
<string name="menu_receive">Primește</string>
<string name="menu_rename">Redenumește</string>
<string name="menu_archive">Arhivă</string>
<string name="menu_backup">Backup</string>
<string name="password_weak">Continuă &#8230;</string>
@ -92,7 +91,6 @@
<string name="backup_success">Copia de rezervă creată cu succes</string>
<string name="backup_failed">Copia de rezervă a eșuat!</string>
<string name="archive_failed">Arhivarea a eșuat!</string>
<string name="rename_failed">Redenumirea a eșuat!</string>
<string name="label_daemon">Nodul</string>
@ -241,9 +239,8 @@
<string name="details_alert_no">Du-mă înapoi!</string>
<string name="details_title">Detalii</string>
<string name="archive_alert_message">Voi face o copie de siguranță a portofelului apoi va fi șters!</string>
<string name="archive_alert_yes">Da, fă asta!</string>
<string name="archive_alert_no">Nu mersi!</string>
<string name="delete_alert_yes">Da, fă asta!</string>
<string name="delete_alert_no">Nu mersi!</string>
<string name="fab_create_new">Creeaza portofel nou</string>
<string name="fab_restore_viewonly">Restaurează portofel-vizualizare</string>
@ -422,4 +419,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Помощь</string>
<string name="menu_receive">Получить</string>
<string name="menu_rename">Переименовать</string>
<string name="menu_archive">Архив</string>
<string name="menu_backup">Резервная копия</string>
<string name="menu_changepw">Изменить пароль</string>
@ -94,7 +93,6 @@
<string name="service_progress">Для данного действия &#8230;\nМожет потребоваться некоторое время!</string>
<string name="backup_failed">Резервное копирование произошло с ошибкой!</string>
<string name="archive_failed">Архивирование произошло с ошибкой!</string>
<string name="rename_failed">Переименование произошло с ошибкой!</string>
<string name="changepw_failed">Изменение пароля произошло с ошибкой!</string>
<string name="changepw_success">Изменение пароля выполено успешно</string>
@ -267,9 +265,9 @@
<string name="details_alert_no">Верните меня обратно!</string>
<string name="details_title">Подробная информация</string>
<string name="archive_alert_message">Этот кошелек будет скопирован, а затем удален!</string>
<string name="archive_alert_yes">Да, сделай это!</string>
<string name="archive_alert_no">Нет, спасибо!</string>
<string name="delete_alert_message">Этот кошелек будет удален!</string>
<string name="delete_alert_yes">Да, сделай это!</string>
<string name="delete_alert_no">Нет, спасибо!</string>
<string name="fab_create_new">Создать новый кошелек</string>
<string name="fab_restore_viewonly">Восстановить кошелек только для просмотра</string>
@ -426,4 +424,9 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Pomoc</string>
<string name="menu_receive">Prijať</string>
<string name="menu_rename">Premenovať</string>
<string name="menu_archive">Archivovať</string>
<string name="menu_backup">Záloha</string>
<string name="menu_changepw">Zmena Hesla</string>
@ -94,7 +93,6 @@
<string name="service_progress">Balím si veci &#8230;\nMôže to chvíľu trvať!</string>
<string name="backup_failed">Zálohovanie zlyhalo!</string>
<string name="archive_failed">Archivácia zlyhala!</string>
<string name="rename_failed">Premenovanie zlyhalo!</string>
<string name="changepw_failed">Zmena hesla zlyhala!</string>
<string name="changepw_success">Heslo zmenené</string>
@ -264,9 +262,9 @@
<string name="details_alert_no">Naspäť!</string>
<string name="details_title">Detaily</string>
<string name="archive_alert_message">Peňaženka bude zálohovaná a následne zmazaná!</string>
<string name="archive_alert_yes">Áno, poďme na to!</string>
<string name="archive_alert_no">Nie, díky!</string>
<string name="delete_alert_message">Peňaženka bude zmazaná!</string>
<string name="delete_alert_yes">Áno, poďme na to!</string>
<string name="delete_alert_no">Nie, díky!</string>
<string name="fab_create_new">Vytvor novú peňaženku</string>
<string name="fab_restore_viewonly">Obnoviť prezeraciu peňaženku</string>
@ -423,4 +421,9 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Pomoć</string>
<string name="menu_receive">Primi</string>
<string name="menu_rename">Preimenuj</string>
<string name="menu_archive">Arhiva</string>
<string name="menu_backup">Bekap</string>
<string name="menu_changepw">Promeni lozinku</string>
@ -94,7 +93,6 @@
<string name="backup_success">Bekap uspešan</string>
<string name="backup_failed">Bekap neuspeo!</string>
<string name="archive_failed">Arhiviranje neuspelo!</string>
<string name="rename_failed">Preimenovanje neuspelo!</string>
<string name="changepw_failed">Promena lozinke neuspela!</string>
<string name="changepw_success">Lozinka promenjena</string>
@ -274,9 +272,9 @@
<string name="details_alert_no">Vrati me nazad!</string>
<string name="details_title">Detalji</string>
<string name="archive_alert_message">Novčanik će biti bekapovan a potom obrisan!</string>
<string name="archive_alert_yes">Da, uradi to!</string>
<string name="archive_alert_no">Ne, hvala!</string>
<string name="delete_alert_message">Novčanik će biti obrisan!</string>
<string name="delete_alert_yes">Da, uradi to!</string>
<string name="delete_alert_no">Ne, hvala!</string>
<string name="fab_create_new">Kreiraj novi novčanik</string>
<string name="fab_restore_viewonly">Obnovi novčanik za pregled</string>
@ -421,4 +419,9 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Hjälp</string>
<string name="menu_receive">Ta emot</string>
<string name="menu_rename">Byt namn</string>
<string name="menu_archive">Arkivera</string>
<string name="menu_backup">Säkerhetskopiera</string>
<string name="menu_changepw">Ändra lösenfras</string>
@ -94,7 +93,6 @@
<string name="backup_success">Säkerhetskopia sparad</string>
<string name="backup_failed">Det gick inte att säkerhetskopiera!</string>
<string name="archive_failed">Det gick inte att arkivera!</string>
<string name="rename_failed">Det gick inte att byta namn!</string>
<string name="changepw_failed">Det gick inte att ändra lösenord!</string>
<string name="changepw_success">Lösenordet har ändrats</string>
@ -254,9 +252,8 @@
<string name="details_alert_no">Ta mig tillbaka!</string>
<string name="details_title">Detaljer</string>
<string name="archive_alert_message">Plånboken kommer att säkerhetskopieras och sedan tas bort!</string>
<string name="archive_alert_yes">Ja, gör det!</string>
<string name="archive_alert_no">Nej tack!</string>
<string name="delete_alert_yes">Ja, gör det!</string>
<string name="delete_alert_no">Nej tack!</string>
<string name="fab_create_new">Skapa ny plånbok</string>
<string name="fab_restore_viewonly">Återställ granskningsplånbok</string>
@ -414,4 +411,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">Допомога</string>
<string name="menu_receive">Отримати</string>
<string name="menu_rename">Перейменувати</string>
<string name="menu_archive">Архів</string>
<string name="menu_backup">Резервна копія</string>
<string name="menu_changepw">Змінити пароль</string>
@ -94,7 +93,6 @@
<string name="service_progress">Для даної дії &#8230;\nМоже знадобитися деякий час!</string>
<string name="backup_failed">Резервне копіювання відбулося з помилкою!</string>
<string name="archive_failed">Архівування відбулося з помилкою!</string>
<string name="rename_failed">Перейменування відбулося з помилкою!</string>
<string name="changepw_failed">Зміна паролю відбулася з помилкою!</string>
<string name="changepw_success">Зміну паролю виконано успішно</string>
@ -267,9 +265,9 @@
<string name="details_alert_no">Поверніть мене назад!</string>
<string name="details_title">Детальна інформація</string>
<string name="archive_alert_message">Цей гаманець буде скопійовано, а потім знищено!</string>
<string name="archive_alert_yes">Так, зроби це!</string>
<string name="archive_alert_no">Ні, дякую!</string>
<string name="delete_alert_message">Цей гаманець буде знищено!</string>
<string name="delete_alert_yes">Так, зроби це!</string>
<string name="delete_alert_no">Ні, дякую!</string>
<string name="fab_create_new">Створити новий гаманець</string>
<string name="fab_restore_viewonly">Відновити гаманець тільки для перегляду</string>
@ -426,4 +424,9 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -7,7 +7,6 @@
<string name="menu_help">帮助</string>
<string name="menu_receive">收款</string>
<string name="menu_rename">重命名</string>
<string name="menu_archive">存档</string>
<string name="menu_backup">备份</string>
<string name="menu_changepw">修改密码</string>
<string name="password_weak">强度不够哦 &#8230;</string>
@ -70,7 +69,6 @@
<string name="service_progress">整合中&#8230;\n这将花上不少时间</string>
<string name="backup_success">备份成功</string>
<string name="backup_failed">备份失败!</string>
<string name="archive_failed">存档失败!</string>
<string name="rename_failed">重命名失败!</string>
<string name="changepw_failed">密码修改失败!</string>
<string name="changepw_success">密码已修改</string>
@ -213,9 +211,8 @@
<string name="details_alert_yes">安全</string>
<string name="details_alert_no">返回上一步</string>
<string name="details_title">详情</string>
<string name="archive_alert_message">即将开始备份钱包,完成后将删除钱包!</string>
<string name="archive_alert_yes">好的,开始吧!</string>
<string name="archive_alert_no">不用了,谢谢!</string>
<string name="delete_alert_yes">好的,开始吧!</string>
<string name="delete_alert_no">不用了,谢谢!</string>
<string name="fab_create_new">创建新钱包</string>
<string name="fab_restore_viewonly">恢复只读钱包</string>
<string name="fab_restore_key">通过私钥恢复钱包</string>
@ -346,4 +343,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -9,7 +9,6 @@
<string name="menu_help">幫助</string>
<string name="menu_receive">收款</string>
<string name="menu_rename">重新命名</string>
<string name="menu_archive">封存</string>
<string name="menu_backup">備份</string>
<string name="menu_changepw">更改密碼</string>
@ -95,7 +94,6 @@
<string name="backup_success">備份成功</string>
<string name="backup_failed">備份失敗!</string>
<string name="archive_failed">封存失敗!</string>
<string name="rename_failed">重新命名失敗!</string>
<string name="changepw_failed">密碼更改失敗!</string>
<string name="changepw_success">密碼更改成功</string>
@ -265,9 +263,8 @@
<string name="details_alert_no">不看了!</string>
<string name="details_title">詳細資訊</string>
<string name="archive_alert_message">錢包檔案將會被備份並且於列表中刪除!</string>
<string name="archive_alert_yes">好的!</string>
<string name="archive_alert_no">不了!</string>
<string name="delete_alert_yes">好的!</string>
<string name="delete_alert_no">不了!</string>
<string name="fab_create_new">建立新錢包</string>
<string name="fab_restore_viewonly">回復唯讀錢包</string>
@ -421,4 +418,10 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="menu_delete">Delete</string><!-- like: "Delete wallet!" -->
<string name="delete_failed">Delete failed!</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

@ -10,7 +10,7 @@
<string name="menu_help">Help</string>
<string name="menu_receive">Receive</string>
<string name="menu_rename">Rename</string>
<string name="menu_archive">Archive</string>
<string name="menu_delete">Delete</string>
<string name="menu_backup">Backup</string>
<string name="menu_changepw">Change Passphrase</string>
@ -86,16 +86,16 @@
<string name="label_send_address">Destination Address</string>
<string name="label_send_notes">Notes</string>
<string name="backup_progress">Backup in progress</string>
<string name="archive_progress">Archive in progress</string>
<string name="rename_progress">Rename in progress</string>
<string name="backup_progress">Backup in progress</string><!--TODO deleteme-->
<string name="archive_progress">Archive in progress</string><!--TODO deleteme-->
<string name="rename_progress">Rename in progress</string><!--TODO deleteme-->
<string name="changepw_progress">Change Password in progress</string>
<string name="service_progress">Wrapping things up &#8230;\nThis can take a while!</string>
<string name="backup_success">Backup successful</string>
<string name="backup_failed">Backup failed!</string>
<string name="archive_failed">Archive failed!</string>
<string name="delete_failed">Delete failed!</string>
<string name="rename_failed">Rename failed!</string>
<string name="changepw_failed">Change Password failed!</string>
<string name="changepw_success">Password changed</string>
@ -132,7 +132,7 @@
<string name="bad_saved_password">Saved password is incorrect.\nPlease enter password manually.</string>
<string name="bad_wallet">Wallet does not exist!</string>
<string name="prompt_daemon_missing">Node must be set!</string>
<string name="prompt_wrong_net">Wallet does not match selected net</string>
<string name="prompt_wrong_net">Wallet does not match selected net</string><!--TODO deleteme-->
<string name="label_watchonly">(Watch Only)</string>
@ -278,9 +278,9 @@
<string name="details_alert_no">Take me back!</string>
<string name="details_title">Details</string>
<string name="archive_alert_message">The wallet will be backed up and then deleted!</string>
<string name="archive_alert_yes">Yes, do that!</string>
<string name="archive_alert_no">No thanks!</string>
<string name="delete_alert_message">The wallet will be deleted!</string>
<string name="delete_alert_yes">Yes, do that!</string>
<string name="delete_alert_no">No thanks!</string>
<string-array name="currency" translatable="false">
<item>EUR</item>
@ -495,4 +495,6 @@
<string name="subaddress_select_label">Select a subaddress</string>
<string name="subaddress_details_hint">Long-press for details</string>
<string name="menu_restore">Import wallet</string>
<string name="restore_failed">Import failed!</string>
</resources>

Loading…
Cancel
Save