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

Compare commits

...

22 Commits

Author SHA1 Message Date
m2049r
d97b36aa44 v1.11 (#545) 2019-03-02 12:26:36 +01:00
m2049r
8fa06e5b37 2019-03 restore height (#544) 2019-03-02 09:44:40 +01:00
m2049r
8d95de828b prevent screenshots on dialogs (#543) 2019-03-02 09:31:06 +01:00
m2049r
93a7be0452 update slow_hash signature (#542) 2019-03-02 09:07:27 +01:00
m2049r
25c8ec1229 fix reading rest (#541) 2019-02-28 22:07:22 +01:00
m2049r
0bf0444dce seed is a password (#540) 2019-02-28 18:43:32 +01:00
m2049r
b74f9c6bd7 upgrade monero core (#539) 2019-02-28 18:33:16 +01:00
m2049r
f843bb1685 share: multiple send & save only when fired (#537) 2019-02-25 21:55:57 +01:00
m2049r
7d9d49c29e landscape mode on tablets (#535) 2019-02-18 19:05:45 +01:00
m2049r
4ca9328949 bump version (#534) 2019-02-17 20:23:00 +01:00
m2049r
08b5a87f19 get rid of keystore exception on first call (#533) 2019-02-14 23:45:01 +01:00
m2049r
445d8acc38 open on monero: & bitcoin: uri (#532) 2019-02-14 23:34:38 +01:00
m2049r
9385ac8c31 update gradle version 2019-02-11 18:50:43 +01:00
Keksoj
4c7ebd8402 translated 'help', 'strings' and 'about' to esperanto (#531) 2019-02-11 07:41:50 +01:00
m2049r
67f3c5f948 bump version 2019-02-04 19:41:28 +01:00
m2049r
cd67a7e2bf 2019-02 height 2019-02-04 19:41:28 +01:00
0140454
fa5fe313ea Update zh-rTW translation (#529) 2019-02-03 07:54:41 +01:00
jaro Lee
ed4957a3cc sk strings update (#523) 2019-01-29 22:33:09 +01:00
jaro Lee
3e0eeebd51 sk help.xml update (#527)
sk-translation - help_send update
2019-01-29 22:32:40 +01:00
m2049r
0d213a1eb4 hide sweep amount in street mode (#526) 2019-01-28 01:16:00 +01:00
m2049r
39d048fd5e Feature bitpay (#525)
* support bitcoin payment protocol (BIP70/72)

* prep translations
2019-01-27 21:32:06 +01:00
m2049r
1a5d2d0399 Update Android Studio v3.3 (#522)
* Update Android Studio v3.3

* also update circleci script to accept licenses
2019-01-21 17:38:59 +01:00
96 changed files with 2222 additions and 358 deletions

View File

@@ -3,11 +3,13 @@ jobs:
build:
working_directory: ~/code
docker:
- image: bitriseio/android-ndk
- image: circleci/android:api-28-ndk
environment:
JVM_OPTS: -Xmx3200m
steps:
- checkout
- run: yes | sdkmanager --licenses || exit 0
- run: yes | sdkmanager --update || exit 0
- restore_cache:
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run:

3
.idea/.gitignore generated vendored
View File

@@ -1,3 +0,0 @@
workspace.xml
markdown-*
misc.xml

1
.idea/.name generated
View File

@@ -1 +0,0 @@
xmrwallet

22
.idea/compiler.xml generated
View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@@ -1,3 +0,0 @@
<component name="CopyrightManager">
<settings default="" />
</component>

19
.idea/gradle.xml generated
View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="disableWrapperSourceDistributionNotification" value="true" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

9
.idea/modules.xml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/xmrwallet.iml" filepath="$PROJECT_DIR$/xmrwallet.iml" />
</modules>
</component>
</project>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 28
versionCode 164
versionName "1.10.14 'Node-O-matiC'"
versionCode 170
versionName "1.11.0 'Chernushka'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {

View File

@@ -26,14 +26,14 @@
android:configChanges="orientation|keyboardHidden"
android:label="@string/wallet_activity_name"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
android:screenOrientation="behind" />
<activity
android:name=".LoginActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="portrait">
android:screenOrientation="locked">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -42,6 +42,22 @@
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<data android:scheme="monero" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<data android:scheme="bitcoin" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter" />
@@ -52,5 +68,15 @@
android:description="@string/service_description"
android:exported="false"
android:label="Monero Wallet Service" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>

View File

@@ -60,14 +60,14 @@ enum {
HASH_DATA_AREA = 136
};
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed);
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height);
inline void slow_hash(const void *data, const size_t length, char *hash) {
cn_slow_hash(data, length, hash, 0 /* variant */, 0/*prehashed*/);
cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/);
}
inline void slow_hash_broken(const void *data, char *hash, int variant) {
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/);
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, 0 /*height*/);
}
#ifdef __cplusplus

View File

@@ -173,10 +173,10 @@ public class LevinReader {
// this should be in LittleEndianDataInputStream because it has little
// endian logic
private long readRest(int firstByte, int bytes) throws IOException {
private long readRest(final int firstByte, final int bytes) throws IOException {
long result = firstByte;
for (int i = 0; i < bytes; i++) {
result = result + (in.readUnsignedByte() << 8);
for (int i = 1; i < bytes + 1; i++) {
result = result + (((long) in.readUnsignedByte()) << (8 * i));
}
return result;
}

View File

@@ -95,7 +95,6 @@ public class GenerateFragment extends Fragment {
etWalletRestoreHeight = view.findViewById(R.id.etWalletRestoreHeight);
bGenerate = view.findViewById(R.id.bGenerate);
etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etWalletViewKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etWalletSpendKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);

View File

@@ -269,7 +269,11 @@ public class LoginActivity extends BaseActivity
} else {
Timber.i("Waiting for permissions");
}
processUsbIntent(getIntent());
// try intents
Intent intent = getIntent();
if (!processUsbIntent(intent))
processUriIntent(intent);
}
boolean checkServiceRunning() {
@@ -716,6 +720,10 @@ public class LoginActivity extends BaseActivity
intent.putExtra(WalletActivity.REQUEST_PW, walletPassword);
intent.putExtra(WalletActivity.REQUEST_FINGERPRINT_USED, fingerprintUsed);
intent.putExtra(WalletActivity.REQUEST_STREETMODE, streetmode);
if (uri != null) {
intent.putExtra(WalletActivity.REQUEST_URI, uri);
uri = null; // use only once
}
startActivity(intent);
}
@@ -1385,7 +1393,7 @@ public class LoginActivity extends BaseActivity
processUsbIntent(intent);
}
private void processUsbIntent(Intent intent) {
private boolean processUsbIntent(Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
synchronized (this) {
@@ -1398,6 +1406,21 @@ public class LoginActivity extends BaseActivity
}
}
}
return true;
}
return false;
}
private String uri = null;
private void processUriIntent(Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
synchronized (this) {
uri = intent.getDataString();
Timber.d("URI Intent %s", uri);
HelpFragment.display(getSupportFragmentManager(), R.string.help_uri);
}
}
}

View File

@@ -17,19 +17,28 @@
package com.m2049r.xmrwallet;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.nfc.NfcManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment;
import android.support.v4.content.FileProvider;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.ShareActionProvider;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
@@ -56,6 +65,9 @@ import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.ExchangeView;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@@ -69,8 +81,8 @@ public class ReceiveFragment extends Fragment {
private TextInputLayout etNotes;
private ExchangeView evAmount;
private TextView tvQrCode;
private ImageView qrCode;
private ImageView qrCodeFull;
private ImageView ivQrCode;
private ImageView ivQrCodeFull;
private EditText etDummy;
private ImageButton bCopyAddress;
private Button bSubaddress;
@@ -97,9 +109,9 @@ public class ReceiveFragment extends Fragment {
tvAddress = view.findViewById(R.id.tvAddress);
etNotes = view.findViewById(R.id.etNotes);
evAmount = view.findViewById(R.id.evAmount);
qrCode = view.findViewById(R.id.qrCode);
ivQrCode = view.findViewById(R.id.qrCode);
tvQrCode = view.findViewById(R.id.tvQrCode);
qrCodeFull = view.findViewById(R.id.qrCodeFull);
ivQrCodeFull = view.findViewById(R.id.qrCodeFull);
etDummy = view.findViewById(R.id.etDummy);
bCopyAddress = view.findViewById(R.id.bCopyAddress);
bSubaddress = view.findViewById(R.id.bSubaddress);
@@ -178,25 +190,25 @@ public class ReceiveFragment extends Fragment {
}
});
qrCode.setOnClickListener(new View.OnClickListener() {
ivQrCode.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.hideKeyboard(getActivity());
etDummy.requestFocus();
if (qrValid) {
qrCodeFull.setImageBitmap(((BitmapDrawable) qrCode.getDrawable()).getBitmap());
qrCodeFull.setVisibility(View.VISIBLE);
ivQrCodeFull.setImageBitmap(((BitmapDrawable) ivQrCode.getDrawable()).getBitmap());
ivQrCodeFull.setVisibility(View.VISIBLE);
} else {
evAmount.doExchange();
}
}
});
qrCodeFull.setOnClickListener(new View.OnClickListener() {
ivQrCodeFull.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
qrCodeFull.setImageBitmap(null);
qrCodeFull.setVisibility(View.GONE);
ivQrCodeFull.setImageBitmap(null);
ivQrCodeFull.setVisibility(View.GONE);
}
});
@@ -228,6 +240,81 @@ public class ReceiveFragment extends Fragment {
return view;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
private ShareActionProvider shareActionProvider;
@Override
public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.receive_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
// Locate MenuItem with ShareActionProvider
MenuItem item = menu.findItem(R.id.menu_item_share);
// Fetch and store ShareActionProvider
shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
shareActionProvider.setOnShareTargetSelectedListener(new ShareActionProvider.OnShareTargetSelectedListener() {
@Override
public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
saveQrCode(); // save it only if we need it
return false;
}
});
}
private void setShareIntent() {
if (shareActionProvider != null) {
if (qrValid) {
shareActionProvider.setShareIntent(getShareIntent());
} else {
shareActionProvider.setShareIntent(null);
}
}
}
private void saveQrCode() {
if (!qrValid) throw new IllegalStateException("trying to save null qr code!");
File cachePath = new File(getActivity().getCacheDir(), "images");
if (!cachePath.exists())
if (!cachePath.mkdirs()) throw new IllegalStateException("cannot create images folder");
File png = new File(cachePath, "QR.png");
try {
FileOutputStream stream = new FileOutputStream(png);
Bitmap qrBitmap = ((BitmapDrawable) ivQrCode.getDrawable()).getBitmap();
qrBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.close();
} catch (IOException ex) {
Timber.e(ex);
// make sure we don't share an old qr code
if (!png.delete()) throw new IllegalStateException("cannot delete old qr code");
// if we manage to delete it, the URI points to nothing and the user gets a toast with the error
}
}
private Intent getShareIntent() {
File imagePath = new File(getActivity().getCacheDir(), "images");
File png = new File(imagePath, "QR.png");
Uri contentUri = FileProvider.getUriForFile(getActivity(),
"com.m2049r.xmrwallet.fileprovider", png);
if (contentUri != null) {
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
shareIntent.setDataAndType(contentUri, getActivity().getContentResolver().getType(contentUri));
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.putExtra(Intent.EXTRA_TEXT, bcData.getUriString());
return shareIntent;
}
return null;
}
void enableSubaddressButton(boolean enable) {
bSubaddress.setEnabled(enable);
if (enable) {
@@ -242,23 +329,23 @@ public class ReceiveFragment extends Fragment {
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
}
boolean qrValid = true;
private boolean qrValid = false;
void clearQR() {
if (qrValid) {
qrCode.setImageBitmap(null);
ivQrCode.setImageBitmap(null);
qrValid = false;
setShareIntent();
if (isLoaded)
tvQrCode.setVisibility(View.VISIBLE);
}
}
void setQR(Bitmap qr) {
qrCode.setImageBitmap(qr);
ivQrCode.setImageBitmap(qr);
qrValid = true;
setShareIntent();
tvQrCode.setVisibility(View.GONE);
Helper.hideKeyboard(getActivity());
etDummy.requestFocus();
}
@Override
@@ -392,7 +479,7 @@ public class ReceiveFragment extends Fragment {
return;
}
bcData = new BarcodeData(BarcodeData.Asset.XMR, address, null, notes, xmrAmount);
int size = Math.min(qrCode.getHeight(), qrCode.getWidth());
int size = Math.max(ivQrCode.getWidth(), ivQrCode.getHeight());
Bitmap qr = generate(bcData.getUriString(), size, size);
if (qr != null) {
setQR(qr);
@@ -422,8 +509,8 @@ public class ReceiveFragment extends Fragment {
Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.RGB_565);
bitmap = addLogo(bitmap);
return bitmap;
} catch (WriterException e) {
e.printStackTrace();
} catch (WriterException ex) {
Timber.e(ex);
}
return null;
}

View File

@@ -31,7 +31,7 @@ public abstract class SecureActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!BuildConfig.DEBUG && !BuildConfig.FLAVOR_type.equals("alpha")) {
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
}
}

View File

@@ -36,7 +36,7 @@ import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.SimpleDateFormat;

View File

@@ -47,6 +47,7 @@ import android.widget.Toast;
import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
@@ -59,7 +60,6 @@ import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.util.ArrayList;
@@ -81,6 +81,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
public static final String REQUEST_PW = "pw";
public static final String REQUEST_FINGERPRINT_USED = "fingerprint";
public static final String REQUEST_STREETMODE = "streetmode";
public static final String REQUEST_URI = "uri";
private NavigationView accountsView;
private DrawerLayout drawer;
@@ -92,6 +93,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
private String password;
private String uri = null;
private long streetMode = 0;
@Override
@@ -191,6 +194,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
// we can set the streetmode height AFTER opening the wallet
requestStreetMode = extras.getBoolean(REQUEST_STREETMODE);
password = extras.getString(REQUEST_PW);
uri = extras.getString(REQUEST_URI);
connectWalletService(walletId, password);
} else {
finish();
@@ -512,7 +516,8 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override
public void onSendRequest() {
replaceFragment(new SendFragment(), null, null);
replaceFragment(SendFragment.newInstance(uri), null, null);
uri = null; // only use uri once
}
@Override
@@ -958,8 +963,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
}
if (!processed || (onUriScannedListener == null)) {
Toast.makeText(this, getString(R.string.nfc_tag_read_what), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, getString(R.string.nfc_tag_read_success), Toast.LENGTH_SHORT).show();
}
}

View File

@@ -21,7 +21,10 @@ import android.net.Uri;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.OpenAliasHelper;
import com.m2049r.xmrwallet.util.PaymentProtocolHelper;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
@@ -37,9 +40,10 @@ public class BarcodeData {
public static final String OA_XMR_ASSET = "xmr";
public static final String OA_BTC_ASSET = "btc";
static final String BTC_SCHEME = "bitcoin:";
static final String BTC_SCHEME = "bitcoin";
static final String BTC_DESCRIPTION = "message";
static final String BTC_AMOUNT = "amount";
static final String BTC_BIP70_PARM = "r";
public enum Asset {
XMR, BTC
@@ -48,7 +52,8 @@ public class BarcodeData {
public enum Security {
NORMAL,
OA_NO_DNSSEC,
OA_DNSSEC
OA_DNSSEC,
BIP70
}
final public Asset asset;
@@ -58,57 +63,44 @@ public class BarcodeData {
final public String amount;
final public String description;
final public Security security;
final public String bip70;
public BarcodeData(Asset asset, String address) {
this.asset = asset;
this.address = address;
amount = null;
paymentId = null;
addressName = null;
description = null;
this.security = Security.NORMAL;
this(asset, address, null, null, null, null, Security.NORMAL);
}
public BarcodeData(Asset asset, String address, String amount) {
this.asset = asset;
this.address = address;
this.amount = amount;
paymentId = null;
addressName = null;
description = null;
this.security = Security.NORMAL;
this(asset, address, null, null, null, amount, Security.NORMAL);
}
public BarcodeData(Asset asset, String address, String amount, String description, Security security) {
this(asset, address, null, null, description, amount, security);
}
public BarcodeData(Asset asset, String address, String paymentId, String amount) {
this.asset = asset;
this.address = address;
this.paymentId = paymentId;
this.amount = amount;
addressName = null;
description = null;
this.security = Security.NORMAL;
this(asset, address, null, paymentId, null, amount, Security.NORMAL);
}
public BarcodeData(Asset asset, String address, String paymentId, String description, String amount) {
this.asset = asset;
this.address = address;
this.paymentId = paymentId;
this.description = description;
this.amount = amount;
addressName = null;
this.security = Security.NORMAL;
this(asset, address, null, paymentId, description, amount, Security.NORMAL);
}
public BarcodeData(Asset asset, String address, String addressName, String paymentId, String description, String amount, Security sec) {
public BarcodeData(Asset asset, String address, String addressName, String paymentId, String description, String amount, Security security) {
this(asset, address, addressName, null, paymentId, description, amount, security);
}
public BarcodeData(Asset asset, String address, String addressName, String bip70, String paymentId, String description, String amount, Security security) {
this.asset = asset;
this.address = address;
this.bip70 = bip70;
this.addressName = addressName;
this.paymentId = paymentId;
this.description = description;
this.amount = amount;
this.security = sec;
this.security = security;
}
public Uri getUri() {
return Uri.parse(getUriString());
}
@@ -146,6 +138,10 @@ public class BarcodeData {
if (bcData == null) {
bcData = parseBitcoinUri(qrCode);
}
// check for btc payment uri (like bitpay)
if (bcData == null) {
bcData = parseBitcoinPaymentUrl(qrCode);
}
// check for naked btc address
if (bcData == null) {
bcData = parseBitcoinNaked(qrCode);
@@ -228,19 +224,28 @@ public class BarcodeData {
}
// bitcoin:mpQ84J43EURZHkCnXbyQ4PpNDLLBqdsMW2?amount=0.01
static public BarcodeData parseBitcoinUri(String uri) {
Timber.d("parseBitcoinUri=%s", uri);
// bitcoin:?r=https://bitpay.com/i/xxx
static public BarcodeData parseBitcoinUri(String uriString) {
Timber.d("parseBitcoinUri=%s", uriString);
if (uri == null) return null;
if (uriString == null) return null;
URI uri;
try {
uri = new URI(uriString);
} catch (URISyntaxException ex) {
return null;
}
if (!uri.isOpaque() ||
!uri.getScheme().equals(BTC_SCHEME)) return null;
if (!uri.startsWith(BTC_SCHEME)) return null;
String noScheme = uri.substring(BTC_SCHEME.length());
Uri bitcoin = Uri.parse(noScheme);
String[] parts = uri.getRawSchemeSpecificPart().split("[?]");
if ((parts.length <= 0) || (parts.length > 2)) {
Timber.d("invalid number of parts %d", parts.length);
return null;
}
Map<String, String> parms = new HashMap<>();
String query = bitcoin.getQuery();
if (query != null) {
String[] args = query.split("&");
if (parts.length == 2) {
String[] args = parts[1].split("&");
for (String arg : args) {
String[] namevalue = arg.split("=");
if (namevalue.length == 0) {
@@ -250,10 +255,26 @@ public class BarcodeData {
namevalue.length > 1 ? Uri.decode(namevalue[1]) : "");
}
}
String address = bitcoin.getPath();
String description = parms.get(BTC_DESCRIPTION);
String address = parts[0]; // no need to decode as there can bo no special characters
if (address.isEmpty()) { // possibly a BIP72 uri
String bip70 = parms.get(BTC_BIP70_PARM);
if (bip70 == null) {
Timber.d("no address and can't find pp url");
return null;
}
if (!PaymentProtocolHelper.isHttp(bip70)) {
Timber.d("[%s] is not http url", bip70);
return null;
}
return new BarcodeData(BarcodeData.Asset.BTC, null, null, bip70, null, description, null, Security.NORMAL);
}
if (!BitcoinAddressValidator.validate(address)) {
Timber.d("BTC address (%s) invalid", address);
return null;
}
String amount = parms.get(BTC_AMOUNT);
if (amount != null) {
if ((amount != null) && (!amount.isEmpty())) {
try {
Double.parseDouble(amount);
} catch (NumberFormatException ex) {
@@ -261,11 +282,22 @@ public class BarcodeData {
return null; // we have an amount but its not a number!
}
}
if (!BitcoinAddressValidator.validate(address)) {
Timber.d("address invalid");
return new BarcodeData(BarcodeData.Asset.BTC, address, null, description, amount);
}
// https://bitpay.com/invoice?id=xxx
// https://bitpay.com/i/KbMdd4EhnLXSbpWGKsaeo6
static public BarcodeData parseBitcoinPaymentUrl(String url) {
Timber.d("parseBitcoinUri=%s", url);
if (url == null) return null;
if (!PaymentProtocolHelper.isHttp(url)) {
Timber.d("[%s] is not http url", url);
return null;
}
return new BarcodeData(BarcodeData.Asset.BTC, address, null, description, amount);
return new BarcodeData(Asset.BTC, url);
}
static public BarcodeData parseBitcoinNaked(String address) {

View File

@@ -20,9 +20,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.util.UserNotes;
import timber.log.Timber;
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
public class TxData implements Parcelable {

View File

@@ -18,12 +18,11 @@ package com.m2049r.xmrwallet.data;
import android.os.Parcel;
import com.m2049r.xmrwallet.model.PendingTransaction;
public class TxDataBtc extends TxData {
private String xmrtoUuid;
private String btcAddress;
private String bip70;
private double btcAmount;
public TxDataBtc() {
@@ -50,6 +49,14 @@ public class TxDataBtc extends TxData {
this.btcAddress = btcAddress;
}
public String getBip70() {
return bip70;
}
public void setBip70(String bip70) {
this.bip70 = bip70;
}
public double getBtcAmount() {
return btcAmount;
}
@@ -63,6 +70,7 @@ public class TxDataBtc extends TxData {
super.writeToParcel(out, flags);
out.writeString(xmrtoUuid);
out.writeString(btcAddress);
out.writeString(bip70);
out.writeDouble(btcAmount);
}
@@ -81,16 +89,19 @@ public class TxDataBtc extends TxData {
super(in);
xmrtoUuid = in.readString();
btcAddress = in.readString();
bip70 = in.readString();
btcAmount = in.readDouble();
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
sb.append(",xmrtoUuid:");
sb.append(xmrtoUuid);
sb.append(",btcAddress:");
sb.append(btcAddress);
sb.append(",bip70:");
sb.append(bip70);
sb.append(",btcAmount:");
sb.append(btcAmount);
return sb.toString();

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.m2049r.xmrwallet.util;
package com.m2049r.xmrwallet.data;
import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;

View File

@@ -67,7 +67,7 @@ public class HelpFragment extends DialogFragment {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(view);
builder.setNegativeButton(R.string.about_close,
builder.setNegativeButton(R.string.help_ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {

View File

@@ -22,11 +22,11 @@ import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.view.WindowManager;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R;
import java.util.Locale;
@@ -78,6 +78,11 @@ public class ProgressDialog extends AlertDialog {
}
super.onCreate(savedInstanceState);
// set FLAG_SECURE to prevent screenshots in Release Mode
if (!(BuildConfig.DEBUG && BuildConfig.FLAVOR_type.equals("alpha"))) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
}
public void setProgress(int value, int max) {

View File

@@ -120,6 +120,15 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
return true;
}
private void setBip70Mode() {
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
if (txDataBtc.getBip70() != null) {
numberPad.setVisibility(View.INVISIBLE);
} else {
numberPad.setVisibility(View.VISIBLE);
}
}
double maxBtc = 0;
double minBtc = 0;
@@ -141,12 +150,13 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
tvFunds.setText(getString(R.string.send_available,
getString(R.string.unknown_amount)));
}
if ((evAmount.getAmount() == null) || evAmount.getAmount().isEmpty()) {
final BarcodeData data = sendListener.popBarcodeData();
if ((data != null) && (data.amount != null)) {
final BarcodeData data = sendListener.popBarcodeData();
if (data != null) {
if (data.amount != null) {
evAmount.setAmount(data.amount);
}
}
setBip70Mode();
callXmrTo();
}

View File

@@ -524,8 +524,8 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
xmrtoStatus = null;
showProgress(1, getString(R.string.label_send_progress_xmrto_create));
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
double btcAmount = txDataBtc.getBtcAmount();
getXmrToApi().createOrder(btcAmount, txDataBtc.getBtcAddress(), new XmrToCallback<CreateOrder>() {
XmrToCallback<CreateOrder> callback = new XmrToCallback<CreateOrder>() {
@Override
public void onSuccess(CreateOrder createOrder) {
if (!isResumed) return;
@@ -545,7 +545,13 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
}
processCreateOrderError(ex);
}
});
};
if (txDataBtc.getBip70() != null) {
getXmrToApi().createOrder(txDataBtc.getBip70(), callback);
} else {
getXmrToApi().createOrder(txDataBtc.getBtcAmount(), txDataBtc.getBtcAddress(), callback);
}
}
private QueryOrderStatus xmrtoStatus = null;
@@ -666,7 +672,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
private XmrToApi xmrToApi = null;
private final XmrToApi getXmrToApi() {
private XmrToApi getXmrToApi() {
if (xmrToApi == null) {
synchronized (this) {
if (xmrToApi == null) {

View File

@@ -36,7 +36,7 @@ import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.data.UserNotes;
import timber.log.Timber;
@@ -214,11 +214,16 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
if (pendingTransaction != null) {
llConfirmSend.setVisibility(View.VISIBLE);
bSend.setEnabled(true);
tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount()));
tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee()));
//tvTxDust.setText(Wallet.getDisplayAmount(pendingTransaction.getDust()));
tvTxTotal.setText(Wallet.getDisplayAmount(
pendingTransaction.getFee() + pendingTransaction.getAmount()));
if (getActivityCallback().isStreetMode()
&& (sendListener.getTxData().getAmount() == Wallet.SWEEP_ALL)) {
tvTxAmount.setText(getString(R.string.street_sweep_amount));
tvTxTotal.setText(getString(R.string.street_sweep_amount));
} else {
tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount()));
tvTxTotal.setText(Wallet.getDisplayAmount(
pendingTransaction.getFee() + pendingTransaction.getAmount()));
}
} else {
llConfirmSend.setVisibility(View.GONE);
bSend.setEnabled(false);

View File

@@ -34,20 +34,20 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.m2049r.xmrwallet.OnBackPressedListener;
import com.m2049r.xmrwallet.OnUriScannedListener;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.WalletActivity;
import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.PendingTx;
import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.layout.SpendViewPager;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.Notice;
import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.widget.DotBar;
import com.m2049r.xmrwallet.widget.Toolbar;
@@ -105,6 +105,14 @@ public class SendFragment extends Fragment
static private int MAX_FALLBACK = Integer.MAX_VALUE;
public static SendFragment newInstance(String uri) {
SendFragment f = new SendFragment();
Bundle args = new Bundle();
args.putString(WalletActivity.REQUEST_URI, uri);
f.setArguments(args);
return f;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -188,6 +196,16 @@ public class SendFragment extends Fragment
etDummy.requestFocus();
Helper.hideKeyboard(getActivity());
Bundle args = getArguments();
if (args != null) {
String uri = args.getString(WalletActivity.REQUEST_URI);
Timber.d("URI: %s", uri);
if (uri != null) {
barcodeData = BarcodeData.fromQrCode(uri);
Timber.d("barcodeData: %s", barcodeData != null ? barcodeData.toString() : "null");
}
}
return view;
}

View File

@@ -27,6 +27,7 @@ import android.widget.Toast;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.PendingTx;
import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
import timber.log.Timber;
@@ -54,6 +55,8 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
void enableDone();
SendFragment.Mode getMode();
SendFragment.Listener getActivityCallback();
}
ImageButton bCopyTxId;
@@ -120,7 +123,13 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
tvTxId.setText(committedTx.txId);
bCopyTxId.setEnabled(true);
bCopyTxId.setImageResource(R.drawable.ic_content_copy_black_24dp);
tvTxAmount.setText(getString(R.string.send_amount, Helper.getDisplayAmount(committedTx.amount)));
if (sendListener.getActivityCallback().isStreetMode()
&& (sendListener.getTxData().getAmount() == Wallet.SWEEP_ALL)) {
tvTxAmount.setText(getString(R.string.street_sweep_amount));
} else {
tvTxAmount.setText(getString(R.string.send_amount, Helper.getDisplayAmount(committedTx.amount)));
}
tvTxFee.setText(getString(R.string.send_fee, Helper.getDisplayAmount(committedTx.fee)));
}
sendListener.enableDone();

View File

@@ -28,7 +28,7 @@ import android.widget.TextView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.UserNotes;
import com.m2049r.xmrwallet.data.UserNotes;
import java.text.SimpleDateFormat;
import java.util.ArrayList;

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