/* * Aegis Bitcoin Wallet - The secure Bitcoin wallet for Android * Copyright 2014 Bojan Simic and specularX.co, designed by Reuven Yamrom * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.aegiswallet.actions; import android.app.ActionBar; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.os.Vibrator; import android.preference.PreferenceManager; import android.telephony.SmsManager; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.View; import android.view.Window; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.EditText; import android.widget.ImageButton; import android.widget.SimpleAdapter; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.aegiswallet.PayBitsApplication; import com.aegiswallet.R; import com.aegiswallet.helpers.PaymentProtocolHelper; import com.aegiswallet.listeners.PasswordProvidedListener; import com.aegiswallet.objects.SMSTransactionPojo; import com.aegiswallet.tasks.SendBTCTask; import com.aegiswallet.utils.BasicUtils; import com.aegiswallet.utils.Constants; import com.aegiswallet.utils.NfcUtils; import com.aegiswallet.utils.SMSTools; import com.aegiswallet.utils.WalletUtils; import com.google.bitcoin.core.Address; import com.google.bitcoin.core.AddressFormatException; import com.google.bitcoin.core.Wallet; import com.google.bitcoin.core.WrongNetworkException; import com.google.zxing.client.android.CaptureActivity; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by bsimic on 3/11/14. */ public class AddressScanActivity extends Activity implements PasswordProvidedListener { private String TAG = this.getClass().getName(); private PayBitsApplication application; private ImageButton backButton; private Button scanButton; private Button sendButton; private TextView titleTextView; private TextView balanceOnSendView; private TextView balanceOnSendViewCurrency; private TextView balanceOnSendCurrencyType; private EditText amountText; private AutoCompleteTextView toAddressText; private TextView amountInCurrency; private Context context = this; private Wallet wallet; private SharedPreferences prefs; private Spinner currencySpinner; private int currentSpinnerSelection = 0; private BigInteger latestSendAmountInSatoshis; private String[] tagOptions; private AutoCompleteTextView tagTextView; private SharedPreferences tagPrefs; private SharedPreferences smsTxnsPrefs; private ArrayList<Map<String, String>> peopleList; private String selectedNumber; private String selectedName; private SimpleAdapter simpleAdapter; private boolean nfcEnabled; private boolean sendCoinsFlag; private Pattern numberPattern = Pattern.compile("^[+]?[0-9]{10,13}$"); private String smsTransactionPhoneNumber; private boolean isSMSTransaction; private String ACTION_START_SCAN = "com.google.zxing.client.android.SCAN"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_send); getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); getActionBar().setCustomView(R.layout.aegis_send_actionbar); titleTextView = (TextView) findViewById(R.id.action_bar_title_text); titleTextView.setText(getString(R.string.send_btc_header)); application = (PayBitsApplication) getApplication(); wallet = application.getWallet(); prefs = PreferenceManager.getDefaultSharedPreferences(this); currencySpinner = (Spinner) findViewById(R.id.currency_spinner); String[] items = new String[]{getString(R.string.btc_string), prefs.getString(Constants.CURRENCY_PREF_KEY, null)}; ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.spinner_item, items); currencySpinner.setAdapter(adapter); //Make the default USD or user defined currency currencySpinner.setSelection(1); balanceOnSendView = (TextView) findViewById(R.id.balance_on_send_view); balanceOnSendView.setText(" " + BasicUtils.satoshiToBTC(application.getWallet().getBalance(Wallet.BalanceType.AVAILABLE)) + " "); balanceOnSendViewCurrency = (TextView) findViewById(R.id.balance_in_currency_send_view); balanceOnSendViewCurrency.setText(WalletUtils.getWalletCurrencyValue(getApplicationContext(), prefs, wallet.getBalance(Wallet.BalanceType.AVAILABLE)) + " "); balanceOnSendCurrencyType = (TextView) findViewById(R.id.send_balance_currency_type); balanceOnSendCurrencyType.setText(prefs.getString(Constants.CURRENCY_PREF_KEY, null)); tagOptions = getResources().getStringArray(R.array.default_tags); tagPrefs = application.getSharedPreferences( getString(R.string.tag_pref_filename), Context.MODE_PRIVATE); smsTxnsPrefs = application.getSharedPreferences(getString(R.string.sms_transaction_filename), Context.MODE_PRIVATE); tagOptions = createTagOptionsFromPrefs(tagPrefs, tagOptions); ArrayAdapter<String> autocompleteAdapter = new ArrayAdapter<String>(this, R.layout.autocomplete1line, tagOptions); tagTextView = (AutoCompleteTextView) findViewById(R.id.transaction_tag); tagTextView.setAdapter(autocompleteAdapter); peopleList = new ArrayList<Map<String, String>>(); peopleList = application.getPeopleList(); handleButtons(); determineNFCEnabled(); } private String[] createTagOptionsFromPrefs(SharedPreferences tagPrefs, String[] tagOptions) { String[] result = tagOptions; Map<String, ?> keys = tagPrefs.getAll(); ArrayList<String> defaultTagList = new ArrayList<String>(Arrays.asList(tagOptions)); for (Map.Entry<String, ?> entry : keys.entrySet()) { if (!defaultTagList.contains(entry.getValue().toString())) { String value = entry.getValue().toString(); defaultTagList.add(value); } } result = defaultTagList.toArray(new String[defaultTagList.size()]); return result; } @Override protected void onResume() { super.onResume(); determineNFCEnabled(); peopleList = application.getPeopleList(); NfcUtils.listen(this, getClass()); } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); byte[] result = NfcUtils.getData(intent); Vibrator v = (Vibrator) this.context.getSystemService(Context.VIBRATOR_SERVICE); v.vibrate(500); String resultString = null; if (result != null) resultString = new String(result); String shamirX2Hashed = application.getPrefs().getString(Constants.SHAMIR_X2_HASHED, null); if (resultString != null && shamirX2Hashed.equals(WalletUtils.convertToSha256(resultString))) { if (sendCoinsFlag) { String amountStr = amountText.getText().toString(); sendCoinsFlag = false; application.cancelNFCPrompt(context); initiateSendCoins(amountStr, resultString); } } else { Toast.makeText(context, getString(R.string.nfc_tag_invalid_string), Toast.LENGTH_SHORT).show(); } } private void handleButtons() { currencySpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) { currentSpinnerSelection = position; handleCurrencyChange(position); } @Override public void onNothingSelected(AdapterView<?> parentView) { } }); backButton = (ImageButton) findViewById(R.id.action_bar_icon_back); backButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent openMainActivity = new Intent(context, MainActivity.class); openMainActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); context.startActivity(openMainActivity); finish(); } }); amountText = (EditText) findViewById(R.id.sent_amount); amountInCurrency = (TextView) findViewById(R.id.send_action_currency_value); toAddressText = (AutoCompleteTextView) findViewById(R.id.send_address); simpleAdapter = new SimpleAdapter(this, peopleList, R.layout.contact_autocomplete, new String[]{"Name", "Phone", "Type"}, new int[]{ R.id.contactName, R.id.contactNumber, R.id.contactNumberType}); toAddressText.setAdapter(simpleAdapter); toAddressText.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> av, View arg1, int index, long arg3) { Map<String, String> map = (Map<String, String>) av.getItemAtPosition(index); String name = map.get("Name"); String number = map.get("Phone"); String value = "" + name + " <" + number + ">"; toAddressText.setText(value); selectedNumber = number; selectedName = name; toAddressText.setSelection(value.length()); } }); toAddressText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { } @Override public void afterTextChanged(Editable editable) { } }); String addressFromIntent = this.getIntent().getStringExtra("address"); String nameFromIntent = this.getIntent().getStringExtra("name"); String numberFromIntent = this.getIntent().getStringExtra("number"); String amountFromIntent = this.getIntent().getStringExtra("amount"); String timeStampFromIntent = this.getIntent().getStringExtra("timestamp"); String tagFromIntent = this.getIntent().getStringExtra("tag"); scanButton = (Button) findViewById(R.id.scan_qr_code_button); if (addressFromIntent != null) { toAddressText.setText(addressFromIntent); } if(numberFromIntent != null && amountFromIntent != null){ if(nameFromIntent == null) nameFromIntent = ""; String message = getString(R.string.sms_received_message, nameFromIntent, numberFromIntent); String amount = BasicUtils.satoshiToBTC(new BigInteger(amountFromIntent)); currencySpinner.setSelection(0); amountText.setText(amount); TextView sendMessageTextView = (TextView) findViewById(R.id.send_message); sendMessageTextView.setText(message); sendMessageTextView.setVisibility(View.VISIBLE); scanButton.setVisibility(View.GONE); amountInCurrency.setText(WalletUtils.getBTCCurrencryValue(getApplicationContext(), prefs, new BigDecimal(amount)) + " " + getString(R.string.btc_string)); if(tagFromIntent != null){ tagTextView.setText(tagFromIntent); } isSMSTransaction = true; smsTransactionPhoneNumber = numberFromIntent.replaceAll("[^0-9]",""); } else { isSMSTransaction = false; } sendButton = (Button) findViewById(R.id.confirm_send_coins); sendButton.setEnabled(true); scanButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { decodeQRCode(); } }); amountText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { } @Override public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { handleCurrencyChange(currentSpinnerSelection); } @Override public void afterTextChanged(Editable editable) { } }); if (application.getWallet().isEncrypted()) { sendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (checkAddressAndAmountValid()) { if (isSMSTransaction) { sendTextMessage(); } else if (nfcEnabled) { application.showNFCPrompt(context); sendCoinsFlag = true; } else { application.showPasswordPrompt(context, Constants.ACTION_DECRYPT); } } } }); } else { sendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (checkAddressAndAmountValid()) { if (isSMSTransaction) { sendTextMessage(); } else { String amountStr = amountText.getText().toString(); initiateSendCoins(amountStr, null); } } } }); } } private void sendTextMessage() { final Dialog textMessageDialog = new Dialog(context); textMessageDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); textMessageDialog.setContentView(R.layout.sms_initate_transaction_prompt); Button cancelButton = (Button) textMessageDialog.findViewById(R.id.sms_init_cancel_button); cancelButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { textMessageDialog.dismiss(); } }); final Button continueButton = (Button) textMessageDialog.findViewById(R.id.sms_init_continue_button); continueButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String textToSend = getString(R.string.sms_send_message) + " " + amountText.getText().toString() + " " + currencySpinner.getItemAtPosition(currentSpinnerSelection) + " (" + amountInCurrency.getText().toString() + ")."; String plainNumber = selectedNumber.replaceAll("[^0-9]",""); try { textToSend = URLEncoder.encode(textToSend, "UTF-8"); textToSend = URLDecoder.decode(textToSend, "UTF-8"); String gsm7String = new String(SMSTools.convertUnicode2GSM(textToSend)); SmsManager sms = SmsManager.getDefault(); ArrayList<String> msgStringArray = sms.divideMessage(gsm7String); if (smsTxnsPrefs.contains(plainNumber)) { Toast.makeText(context, getString(R.string.sms_pendngtx) + " " + selectedName, Toast.LENGTH_LONG).show(); } { sms.sendMultipartTextMessage(selectedNumber, null, msgStringArray, null, null); SMSTransactionPojo smsTransactionPojo = new SMSTransactionPojo(selectedNumber, selectedName, latestSendAmountInSatoshis, "", Constants.SMS_STATUS_INIT, tagTextView.getText().toString()); smsTxnsPrefs.edit().putString(plainNumber, smsTransactionPojo.getJSONBase64()).commit(); Intent openMainActivity = new Intent(context, MainActivity.class); openMainActivity.putExtra("message", getString(R.string.sms_sent_message, toAddressText.getText().toString())); openMainActivity.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); context.startActivity(openMainActivity); finish(); } } catch (UnsupportedEncodingException e) { Log.d(TAG, e.getMessage()); } } }); textMessageDialog.show(); } private void handleCurrencyChange(int position) { String amountString = amountText.getText().toString(); //Position 0 is Bitcoin if (position == 0) { try { BigDecimal btcAmountBigDecimal = new BigDecimal(amountString); amountInCurrency.setText( WalletUtils.getBTCCurrencryValue(getApplicationContext(), prefs, btcAmountBigDecimal) + " " + prefs.getString(Constants.CURRENCY_PREF_KEY, null)); latestSendAmountInSatoshis = BasicUtils.toNanoCoins(btcAmountBigDecimal.toString(), 0); doCheckForLatestSatoshiAmountValid(latestSendAmountInSatoshis); } catch (NumberFormatException e) { Log.d(TAG, e.getMessage()); } } //This is the user's selected currency else { try { BigDecimal testDecimalAmount = new BigDecimal(amountString); BigDecimal oneBTCDecimal = BigDecimal.valueOf(100000000); BigDecimal exchangeRate = WalletUtils.getExchangeRate(context, prefs); BigDecimal btcAmountDecimal = testDecimalAmount.multiply(oneBTCDecimal).divide(exchangeRate, 8, RoundingMode.HALF_EVEN); BigInteger btcAmountInteger = btcAmountDecimal.toBigInteger(); if (btcAmountInteger != null) { amountInCurrency.setText(BasicUtils.satoshiToBTC(btcAmountInteger) + " " + getString(R.string.plain_btc_string)); } latestSendAmountInSatoshis = btcAmountInteger; BigInteger walletBalance = wallet.getBalance(Wallet.BalanceType.AVAILABLE); if(latestSendAmountInSatoshis.compareTo(walletBalance) == 1){ latestSendAmountInSatoshis = walletBalance.subtract(BigInteger.valueOf(1000)); } doCheckForLatestSatoshiAmountValid(latestSendAmountInSatoshis); } catch (NumberFormatException e) { Log.d(TAG, e.getMessage()); } } } private void doCheckForLatestSatoshiAmountValid(BigInteger satoshiAmount){ } private void decodeQRCode() { Intent intent = new Intent(this, CaptureActivity.class); intent.setAction(ACTION_START_SCAN); intent.putExtra("SCAN_MODE", "QR_CODE_MODE"); startActivityForResult(intent, 1); } private void initiateSendCoins(String amountStr, String passOrNFC) { BigInteger newAmount = null; BigDecimal currAmountBigInt = null; if (currentSpinnerSelection == 0) { newAmount = BasicUtils.toNanoCoins(amountStr, 0); } else if (currentSpinnerSelection == 1) { BigDecimal testDecimalAmount = new BigDecimal(amountStr); BigDecimal oneBTCDecimal = BigDecimal.valueOf(100000000); BigDecimal exchangeRate = WalletUtils.getExchangeRate(context, prefs); BigDecimal btcAmountDecimal = testDecimalAmount.multiply(oneBTCDecimal).divide(exchangeRate, 5, RoundingMode.HALF_EVEN); BigInteger btcAmountInteger = btcAmountDecimal.toBigInteger(); newAmount = btcAmountInteger; } if (BasicUtils.isNetworkAvailable(context)) { String tagText = ""; if (tagTextView != null && tagTextView.getText().toString() != null) { tagText = tagTextView.getText().toString(); } SendBTCTask sendBTCTask = new SendBTCTask(this, wallet, passOrNFC, toAddressText.getText().toString(), newAmount, application, tagText); //sendBTCTask.execute(); sendBTCTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.sms_transaction_filename), Context.MODE_PRIVATE); // If it's an SMS transaction, we remove it because it is now completed. if(prefs.contains(smsTransactionPhoneNumber)){ SharedPreferences.Editor editor = prefs.edit(); editor.remove(smsTransactionPhoneNumber); editor.commit(); } sendButton.setEnabled(false); //finish(); } else { Toast.makeText(context, getString(R.string.no_internet_connection_available_string), Toast.LENGTH_LONG).show(); } } private boolean checkAddressAndAmountValid() { boolean result = false; boolean isValidAddress = false; boolean isValidPhoneNumber = false; String addressTextBoxValue = toAddressText.getText().toString(); if(selectedName == null){ selectedNumber = addressTextBoxValue; } try { Log.d(TAG, "Trying address: " + addressTextBoxValue); Address a = new Address(Constants.NETWORK_PARAMETERS, addressTextBoxValue); result = true; isSMSTransaction = false; isValidAddress = true; Log.d(TAG, "isValidAddress: " + isValidAddress); } catch (WrongNetworkException e) { Log.d(TAG, "WrongNetworkException" + e.getMessage()); result = false; } catch (AddressFormatException e) { Log.d(TAG, "AddressFormatException" + e.getMessage()); result = false; } catch (Exception e){ Log.d(TAG, "Some other Exception" + e.getMessage()); } Log.d(TAG, "selectedNumber: " + selectedNumber); if (selectedNumber != null && !isValidAddress) { String parsedAddressValue = addressTextBoxValue.replaceAll("[^\\d+]", ""); Log.d(TAG, "parsedAddressValue: " + parsedAddressValue); String selectedNumberValue = selectedNumber.replaceAll("[^\\d+]", ""); Log.d(TAG, "parsedNumberValue: " + parsedAddressValue); Matcher addressMatcher = numberPattern.matcher(parsedAddressValue); Matcher numberMatcher = numberPattern.matcher(selectedNumberValue); boolean addressValueMatches = addressMatcher.matches(); boolean selectedNumberMatches = numberMatcher.matches(); boolean isValidNumber = addressValueMatches || selectedNumberMatches; if (!isValidNumber) { Toast.makeText(this, getString(R.string.invalid_address_string), Toast.LENGTH_LONG).show(); Log.d(TAG, "INVALID NUMBER " + selectedNumber); return false; } else{ Log.d(TAG, "VALID NUMBER " + selectedNumber); isSMSTransaction = true; isValidPhoneNumber = true; result = true; } } if (amountText.length() <= 0) { Toast.makeText(context, getString(R.string.amount_cannot_be_empty), Toast.LENGTH_LONG).show(); return false; } if (latestSendAmountInSatoshis == null || (latestSendAmountInSatoshis != null && latestSendAmountInSatoshis.longValue() <= Constants.MIN_AMOUNT_IN_SATOSHI)) { Toast.makeText(context, getString(R.string.send_amount_less_than_minimum_amount), Toast.LENGTH_LONG).show(); return false; } if (latestSendAmountInSatoshis != null) { BigInteger availMinusInput = wallet.getBalance(Wallet.BalanceType.AVAILABLE).subtract(latestSendAmountInSatoshis); if (availMinusInput.longValue() < 0) { Toast.makeText(this, getString(R.string.invalid_amount_string), Toast.LENGTH_LONG).show(); return false; } } if(!isValidAddress && !isValidPhoneNumber){ Toast.makeText(this, getString(R.string.invalid_address_string), Toast.LENGTH_LONG).show(); } return result; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { String contents = data.getStringExtra("SCAN_RESULT"); PaymentProtocolHelper helper = new PaymentProtocolHelper(contents); if (helper.getAddress() != null) { toAddressText.setText(helper.getAddress().toString()); } if (helper.getAmount() != null) { currencySpinner.setSelection(0); BigInteger amount = helper.getAmount(); String btcAmount = BasicUtils.satoshiToBTC(amount); amountText.setText(btcAmount); amountInCurrency.setText(WalletUtils.getBTCCurrencryValue(getApplicationContext(), prefs, new BigDecimal(btcAmount)) + " " + getString(R.string.btc_string)); } } } @Override public void onPasswordProvided(String password, int action) { String amountStr = amountText.getText().toString(); initiateSendCoins(amountStr, password); } private void determineNFCEnabled() { nfcEnabled = prefs.contains(Constants.SHAMIR_ENCRYPTED_KEY) ? false : true; } }