/* * Copyright 2011-2013 the original author or authors. * * 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 de.schildbach.wallet.digitalcoin.ui; import java.math.BigInteger; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Process; import android.preference.PreferenceManager; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.MeasureSpec; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; import android.view.ViewGroup; import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.CursorAdapter; import android.widget.ListView; import android.widget.PopupWindow; import android.widget.TextView; import com.actionbarsherlock.app.SherlockFragment; import com.actionbarsherlock.view.ActionMode; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.google.digitalcoin.core.Address; import com.google.digitalcoin.core.AddressFormatException; import com.google.digitalcoin.core.NetworkParameters; import com.google.digitalcoin.core.Sha256Hash; import com.google.digitalcoin.core.Transaction; import com.google.digitalcoin.core.TransactionConfidence; import com.google.digitalcoin.core.TransactionConfidence.ConfidenceType; import com.google.digitalcoin.core.Wallet; import com.google.digitalcoin.core.Wallet.BalanceType; import com.google.digitalcoin.core.Wallet.SendRequest; import com.google.digitalcoin.uri.DigitalcoinURI; import com.google.digitalcoin.uri.DigitalcoinURIParseException; import de.schildbach.wallet.digitalcoin.AddressBookProvider; import de.schildbach.wallet.digitalcoin.Constants; import de.schildbach.wallet.digitalcoin.WalletApplication; import de.schildbach.wallet.digitalcoin.integration.android.DigitalcoinIntegration; import de.schildbach.wallet.digitalcoin.service.BlockchainService; import de.schildbach.wallet.digitalcoin.service.BlockchainServiceImpl; import de.schildbach.wallet.digitalcoin.util.WalletUtils; import de.schildbach.wallet.digitalcoin.R; /** * @author Andreas Schildbach */ public final class SendCoinsFragment extends SherlockFragment implements AmountCalculatorFragment.Listener { private AbstractWalletActivity activity; private WalletApplication application; private ContentResolver contentResolver; private SharedPreferences prefs; private Wallet wallet; private BlockchainService service; private final Handler handler = new Handler(); private HandlerThread backgroundThread; private Handler backgroundHandler; private AutoCompleteTextView receivingAddressView; private View receivingStaticView; private TextView receivingStaticAddressView; private TextView receivingStaticLabelView; private CurrencyAmountView amountView; private CurrencyAmountView feeView; private ListView sentTransactionView; private TransactionsListAdapter sentTransactionListAdapter; private Button viewGo; private Button viewCancel; private TextView popupMessageView; private View popupAvailableView; private PopupWindow popupWindow; private MenuItem scanAction; private Address validatedAddress = null; private String receivingLabel = null; private boolean isValidAmounts = false; private State state = State.INPUT; private Transaction sentTransaction = null; private static final int REQUEST_CODE_SCAN = 0; private enum State { INPUT, PREPARATION, SENDING, SENT, FAILED } private final ServiceConnection serviceConnection = new ServiceConnection() { public void onServiceConnected(final ComponentName name, final IBinder binder) { service = ((BlockchainServiceImpl.LocalBinder) binder).getService(); } public void onServiceDisconnected(final ComponentName name) { service = null; } }; private final class ReceivingAddressListener implements OnFocusChangeListener, TextWatcher { public void onFocusChange(final View v, final boolean hasFocus) { if (!hasFocus) validateReceivingAddress(true); } public void afterTextChanged(final Editable s) { dismissPopup(); receivingLabel = null; validateReceivingAddress(false); } public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { } public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { } } private final ReceivingAddressListener receivingAddressListener = new ReceivingAddressListener(); private final CurrencyAmountView.Listener amountsListener = new CurrencyAmountView.Listener() { public void changed() { dismissPopup(); Log.d("digitalcoin", "Amount: " + amountView.getAmount() + ", Fee: " + feeView.getAmount()); validateAmounts(false); } public void done() { validateAmounts(true); viewGo.requestFocusFromTouch(); } public void focusChanged(final boolean hasFocus) { if (!hasFocus) { validateAmounts(true); } } }; private final ContentObserver contentObserver = new ContentObserver(handler) { @Override public void onChange(final boolean selfChange) { updateView(); } }; private final TransactionConfidence.Listener sentTransactionConfidenceListener = new TransactionConfidence.Listener() { public void onConfidenceChanged(final Transaction tx) { activity.runOnUiThread(new Runnable() { public void run() { sentTransactionListAdapter.notifyDataSetChanged(); if (state == State.SENDING) { final TransactionConfidence confidence = sentTransaction.getConfidence(); if (confidence.getConfidenceType() == ConfidenceType.DEAD) state = State.FAILED; else if (confidence.numBroadcastPeers() > 1 || confidence.getConfidenceType() == ConfidenceType.BUILDING) state = State.SENT; updateView(); } } }); } }; @Override public void onAttach(final Activity activity) { super.onAttach(activity); this.activity = (AbstractWalletActivity) activity; application = (WalletApplication) activity.getApplication(); contentResolver = activity.getContentResolver(); prefs = PreferenceManager.getDefaultSharedPreferences(activity); wallet = application.getWallet(); } @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); if (savedInstanceState != null) { state = (State) savedInstanceState.getSerializable("state"); if (savedInstanceState.containsKey("validated_address_bytes")) validatedAddress = new Address((NetworkParameters) savedInstanceState.getSerializable("validated_address_params"), savedInstanceState.getByteArray("validated_address_bytes")); else validatedAddress = null; receivingLabel = savedInstanceState.getString("receiving_label"); isValidAmounts = savedInstanceState.getBoolean("is_valid_amounts"); if (savedInstanceState.containsKey("sent_transaction_hash")) { sentTransaction = wallet.getTransaction((Sha256Hash) savedInstanceState.getSerializable("sent_transaction_hash")); sentTransaction.getConfidence().addEventListener(sentTransactionConfidenceListener); } } activity.bindService(new Intent(activity, BlockchainServiceImpl.class), serviceConnection, Context.BIND_AUTO_CREATE); backgroundThread = new HandlerThread("backgroundThread", Process.THREAD_PRIORITY_BACKGROUND); backgroundThread.start(); backgroundHandler = new Handler(backgroundThread.getLooper()); } @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.send_coins_fragment, container); receivingAddressView = (AutoCompleteTextView) view.findViewById(R.id.send_coins_receiving_address); receivingAddressView.setAdapter(new AutoCompleteAddressAdapter(activity, null)); receivingAddressView.setOnFocusChangeListener(receivingAddressListener); receivingAddressView.addTextChangedListener(receivingAddressListener); receivingStaticView = view.findViewById(R.id.send_coins_receiving_static); receivingStaticAddressView = (TextView) view.findViewById(R.id.send_coins_receiving_static_address); receivingStaticLabelView = (TextView) view.findViewById(R.id.send_coins_receiving_static_label); receivingStaticView.setOnFocusChangeListener(new OnFocusChangeListener() { private ActionMode actionMode; public void onFocusChange(final View v, final boolean hasFocus) { if (hasFocus) { actionMode = activity.startActionMode(new ActionMode.Callback() { public boolean onCreateActionMode(final ActionMode mode, final Menu menu) { final MenuInflater inflater = mode.getMenuInflater(); inflater.inflate(R.menu.send_coins_address_context, menu); return true; } public boolean onPrepareActionMode(final ActionMode mode, final Menu menu) { return false; } public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { switch (item.getItemId()) { case R.id.send_coins_address_context_edit_address: handleEditAddress(); mode.finish(); return true; case R.id.send_coins_address_context_clear: handleClear(); mode.finish(); return true; } return false; } public void onDestroyActionMode(final ActionMode mode) { if (receivingStaticView.hasFocus()) amountView.requestFocus(); } private void handleEditAddress() { EditAddressBookEntryFragment.edit(getFragmentManager(), validatedAddress.toString()); } private void handleClear() { // switch from static to input validatedAddress = null; receivingLabel = null; receivingAddressView.setText(null); receivingStaticAddressView.setText(null); updateView(); receivingAddressView.requestFocus(); } }); } else { actionMode.finish(); } } }); amountView = (CurrencyAmountView) view.findViewById(R.id.send_coins_amount); amountView.setContextButton(R.drawable.ic_input_calculator, new OnClickListener() { public void onClick(final View v) { AmountCalculatorFragment.calculate(getFragmentManager(), SendCoinsFragment.this); } }); feeView = (CurrencyAmountView) view.findViewById(R.id.send_coins_fee); feeView.setAmount(Constants.DEFAULT_TX_FEE); sentTransactionView = (ListView) view.findViewById(R.id.send_coins_sent_transaction); sentTransactionListAdapter = new TransactionsListAdapter(activity, wallet, application.maxConnectedPeers()); sentTransactionView.setAdapter(sentTransactionListAdapter); viewGo = (Button) view.findViewById(R.id.send_coins_go); viewGo.setOnClickListener(new OnClickListener() { public void onClick(final View v) { validateReceivingAddress(true); validateAmounts(true); if (everythingValid()) { final BigInteger fee = feeView.getAmount(); if (fee.compareTo(Constants.DEFAULT_TX_FEE) >= 0) handleGo(); else feeDialog(); } } private void feeDialog() { final boolean allowLowFee = prefs.getBoolean(Constants.PREFS_KEY_LABS_SEND_COINS_LOW_FEE, false); final AlertDialog.Builder dialog = new AlertDialog.Builder(activity); dialog.setMessage(getString(R.string.send_coins_dialog_fee_message, Constants.CURRENCY_CODE_LITECOIN + " " + WalletUtils.formatValue(Constants.DEFAULT_TX_FEE, Constants.WDC_PRECISION))); if (allowLowFee) { dialog.setPositiveButton(R.string.send_coins_dialog_fee_button_send, new DialogInterface.OnClickListener() { public void onClick(final DialogInterface dialog, final int which) { handleGo(); } }); } else { dialog.setPositiveButton(R.string.send_coins_dialog_fee_button_adjust, new DialogInterface.OnClickListener() { public void onClick(final DialogInterface dialog, final int which) { feeView.setAmount(Constants.DEFAULT_TX_FEE); } }); } dialog.setNegativeButton(R.string.send_coins_dialog_fee_button_dismiss, null); dialog.show(); } }); viewCancel = (Button) view.findViewById(R.id.send_coins_cancel); viewCancel.setOnClickListener(new OnClickListener() { public void onClick(final View v) { if (state == State.INPUT) activity.setResult(Activity.RESULT_CANCELED); activity.finish(); } }); popupMessageView = (TextView) inflater.inflate(R.layout.send_coins_popup_message, container); popupAvailableView = inflater.inflate(R.layout.send_coins_popup_available, container); return view; } @Override public void onResume() { super.onResume(); contentResolver.registerContentObserver(AddressBookProvider.contentUri(activity.getPackageName()), true, contentObserver); amountView.setListener(amountsListener); feeView.setListener(amountsListener); updateView(); } @Override public void onPause() { feeView.setListener(null); amountView.setListener(null); contentResolver.unregisterContentObserver(contentObserver); super.onPause(); } @Override public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); outState.putSerializable("state", state); if (validatedAddress != null) { outState.putSerializable("validated_address_params", validatedAddress.getParameters()); outState.putByteArray("validated_address_bytes", validatedAddress.getHash160()); outState.putBoolean("is_valid_amounts", isValidAmounts); } outState.putString("receiving_label", receivingLabel); if (sentTransaction != null) outState.putSerializable("sent_transaction_hash", sentTransaction.getHash()); } @Override public void onDestroy() { backgroundThread.getLooper().quit(); activity.unbindService(serviceConnection); if (sentTransaction != null) sentTransaction.getConfidence().removeEventListener(sentTransactionConfidenceListener); super.onDestroy(); } @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { if (requestCode == REQUEST_CODE_SCAN && resultCode == Activity.RESULT_OK) { final String contents = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT); if (contents.matches("[a-zA-Z0-9]*")) { update(contents, null, null); } else { try { final DigitalcoinURI digitalcoinUri = new DigitalcoinURI(null, contents); final Address address = digitalcoinUri.getAddress(); final String addressLabel = digitalcoinUri.getLabel(); update(address != null ? address.toString() : null, addressLabel, digitalcoinUri.getAmount()); } catch (final DigitalcoinURIParseException x) { activity.parseErrorDialog(contents); } } } } @SuppressLint("InlinedApi") @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.send_coins_fragment_options, menu); scanAction = menu.findItem(R.id.send_coins_options_scan); final PackageManager pm = activity.getPackageManager(); scanAction.setVisible(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) || pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)); super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.send_coins_options_scan: handleScan(); return true; } return super.onOptionsItemSelected(item); } private void validateReceivingAddress(final boolean popups) { try { final String addressStr = receivingAddressView.getText().toString().trim(); if (addressStr.length() > 0) { final NetworkParameters addressParams = Address.getParametersFromAddress(addressStr); if (addressParams != null && !addressParams.equals(Constants.NETWORK_PARAMETERS)) { // address is valid, but from different known network if (popups) popupMessage(receivingAddressView, getString(R.string.send_coins_fragment_receiving_address_error_cross_network, addressParams.getId())); } else if (addressParams == null) { // address is valid, but from different unknown network if (popups) popupMessage(receivingAddressView, getString(R.string.send_coins_fragment_receiving_address_error_cross_network_unknown)); } else { // valid address validatedAddress = new Address(Constants.NETWORK_PARAMETERS, addressStr); } } else { // empty field should not raise error message } } catch (final AddressFormatException x) { // could not decode address at all if (popups) popupMessage(receivingAddressView, getString(R.string.send_coins_fragment_receiving_address_error)); } updateView(); } private void validateAmounts(final boolean popups) { isValidAmounts = false; final BigInteger amount = amountView.getAmount(); if (amount == null) { // empty amount if (popups) popupMessage(amountView, getString(R.string.send_coins_fragment_amount_empty)); } else if (amount.signum() > 0) { final BigInteger estimated = wallet.getBalance(BalanceType.ESTIMATED); final BigInteger available = wallet.getBalance(BalanceType.AVAILABLE); final BigInteger pending = estimated.subtract(available); // TODO subscribe to wallet changes final BigInteger availableAfterAmount = available.subtract(amount); final boolean enoughFundsForAmount = availableAfterAmount.signum() >= 0; if (enoughFundsForAmount) { final BigInteger fee = feeView.getAmount(); final boolean validFee = fee != null && fee.signum() >= 0; if (validFee) { final boolean enoughFunds = availableAfterAmount.subtract(fee).signum() >= 0; if (enoughFunds) { // everything fine isValidAmounts = true; } else { // not enough funds for fee if (popups) popupAvailable(feeView, availableAfterAmount, pending); } } else { // invalid fee if (popups) popupMessage(feeView, getString(R.string.send_coins_fragment_amount_error)); } } else { // not enough funds for amount if (popups) popupAvailable(amountView, available, pending); } } else { // invalid amount if (popups) popupMessage(amountView, getString(R.string.send_coins_fragment_amount_error)); } updateView(); } private void popupMessage(final View anchor, final String message) { dismissPopup(); popupMessageView.setText(message); popupMessageView.setMaxWidth(getView().getWidth()); popup(anchor, popupMessageView); } private void popupAvailable(final View anchor, final BigInteger available, final BigInteger pending) { dismissPopup(); final CurrencyTextView viewAvailable = (CurrencyTextView) popupAvailableView.findViewById(R.id.send_coins_popup_available_amount); viewAvailable.setPrefix(Constants.CURRENCY_CODE_LITECOIN); viewAvailable.setAmount(available); final TextView viewPending = (TextView) popupAvailableView.findViewById(R.id.send_coins_popup_available_pending); viewPending.setVisibility(pending.signum() > 0 ? View.VISIBLE : View.GONE); viewPending.setText(getString(R.string.send_coins_fragment_pending, WalletUtils.formatValue(pending, Constants.WDC_PRECISION))); popup(anchor, popupAvailableView); } private void popup(final View anchor, final View contentView) { contentView.measure(MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0), MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0)); popupWindow = new PopupWindow(contentView, contentView.getMeasuredWidth(), contentView.getMeasuredHeight(), false); popupWindow.showAsDropDown(anchor); // hack contentView.setBackgroundResource(popupWindow.isAboveAnchor() ? R.drawable.popup_frame_above : R.drawable.popup_frame_below); } private void dismissPopup() { if (popupWindow != null) { popupWindow.dismiss(); popupWindow = null; } } private void handleGo() { state = State.PREPARATION; updateView(); // create spend final SendRequest sendRequest = SendRequest.to(validatedAddress, amountView.getAmount()); sendRequest.fee = feeView.getAmount(); backgroundHandler.post(new Runnable() { public void run() { final Transaction transaction = wallet.sendCoinsOffline(sendRequest); // Get the size of the transaction Log.d("digitalcoin", "Transaction size is " + transaction.getOutputs().size()); handler.post(new Runnable() { public void run() { if (transaction != null) { sentTransaction = transaction; state = State.SENDING; updateView(); sentTransaction.getConfidence().addEventListener(sentTransactionConfidenceListener); service.broadcastTransaction(sentTransaction); final Intent result = new Intent(); DigitalcoinIntegration.transactionHashToResult(result, sentTransaction.getHashAsString()); activity.setResult(Activity.RESULT_OK, result); // final String label = AddressBookProvider.resolveLabel(contentResolver, // validatedAddress.toString()); // if (label == null) // showAddAddressDialog(validatedAddress.toString(), receivingLabel); } else { state = State.FAILED; updateView(); activity.longToast(R.string.send_coins_error_msg); } } }); } }); } private void handleScan() { startActivityForResult(new Intent(activity, ScanActivity.class), REQUEST_CODE_SCAN); } public class AutoCompleteAddressAdapter extends CursorAdapter { public AutoCompleteAddressAdapter(final Context context, final Cursor c) { super(context, c); } @Override public View newView(final Context context, final Cursor cursor, final ViewGroup parent) { final LayoutInflater inflater = LayoutInflater.from(context); return inflater.inflate(R.layout.address_book_row, parent, false); } @Override public void bindView(final View view, final Context context, final Cursor cursor) { final String label = cursor.getString(cursor.getColumnIndexOrThrow(AddressBookProvider.KEY_LABEL)); final String address = cursor.getString(cursor.getColumnIndexOrThrow(AddressBookProvider.KEY_ADDRESS)); final ViewGroup viewGroup = (ViewGroup) view; final TextView labelView = (TextView) viewGroup.findViewById(R.id.address_book_row_label); labelView.setText(label); final TextView addressView = (TextView) viewGroup.findViewById(R.id.address_book_row_address); addressView.setText(WalletUtils.formatHash(address, Constants.ADDRESS_FORMAT_GROUP_SIZE, Constants.ADDRESS_FORMAT_LINE_SIZE)); } @Override public CharSequence convertToString(final Cursor cursor) { return cursor.getString(cursor.getColumnIndexOrThrow(AddressBookProvider.KEY_ADDRESS)); } @Override public Cursor runQueryOnBackgroundThread(final CharSequence constraint) { final Cursor cursor = activity.managedQuery(AddressBookProvider.contentUri(activity.getPackageName()), null, AddressBookProvider.SELECTION_QUERY, new String[] { constraint.toString() }, null); return cursor; } } private void updateView() { if (validatedAddress != null) { receivingAddressView.setVisibility(View.GONE); receivingStaticView.setVisibility(View.VISIBLE); receivingStaticAddressView.setText(WalletUtils.formatAddress(validatedAddress, Constants.ADDRESS_FORMAT_GROUP_SIZE, Constants.ADDRESS_FORMAT_LINE_SIZE)); final String label = AddressBookProvider.resolveLabel(activity, validatedAddress.toString()); receivingStaticLabelView.setText(label != null ? label : (receivingLabel != null ? receivingLabel : getString(R.string.address_unlabeled))); receivingStaticLabelView.setTextColor(label != null ? R.color.fg_significant : R.color.fg_insignificant); } else { receivingStaticView.setVisibility(View.GONE); receivingAddressView.setVisibility(View.VISIBLE); } receivingAddressView.setEnabled(state == State.INPUT); receivingStaticView.setEnabled(state == State.INPUT); amountView.setEnabled(state == State.INPUT); feeView.setEnabled(state == State.INPUT); if (sentTransaction != null) { sentTransactionView.setVisibility(View.VISIBLE); sentTransactionListAdapter.setPrecision(Integer.parseInt(prefs.getString(Constants.PREFS_KEY_WDC_PRECISION, Integer.toString(Constants.WDC_PRECISION)))); sentTransactionListAdapter.replace(sentTransaction); } else { sentTransactionView.setVisibility(View.GONE); sentTransactionListAdapter.clear(); } viewCancel.setEnabled(state != State.PREPARATION); viewGo.setEnabled(everythingValid()); if (state == State.INPUT) { viewCancel.setText(R.string.button_cancel); viewGo.setText(R.string.send_coins_fragment_button_send); } else if (state == State.PREPARATION) { viewCancel.setText(R.string.button_cancel); viewGo.setText(R.string.send_coins_preparation_msg); } else if (state == State.SENDING) { viewCancel.setText(R.string.send_coins_fragment_button_back); viewGo.setText(R.string.send_coins_sending_msg); } else if (state == State.SENT) { viewCancel.setText(R.string.send_coins_fragment_button_back); viewGo.setText(R.string.send_coins_sent_msg); } else if (state == State.FAILED) { viewCancel.setText(R.string.send_coins_fragment_button_back); viewGo.setText(R.string.send_coins_failed_msg); } if (scanAction != null) scanAction.setEnabled(state == State.INPUT); } private boolean everythingValid() { return state == State.INPUT && validatedAddress != null && isValidAmounts; } public void update(final String receivingAddress, final String receivingLabel, final BigInteger amount) { try { validatedAddress = new Address(Constants.NETWORK_PARAMETERS, receivingAddress); this.receivingLabel = receivingLabel; } catch (final Exception x) { receivingAddressView.setText(receivingAddress); x.printStackTrace(); } if (amount != null) amountView.setAmount(amount); // focus if (receivingAddress != null && amount == null) amountView.requestFocus(); else if (receivingAddress != null && amount != null) feeView.requestFocus(); updateView(); handler.postDelayed(new Runnable() { public void run() { validateReceivingAddress(true); validateAmounts(true); } }, 500); } private void showAddAddressDialog(final String address, final String suggestedAddressLabel) { final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setMessage(R.string.send_coins_add_address_dialog_title); builder.setPositiveButton(R.string.send_coins_add_address_dialog_button_add, new DialogInterface.OnClickListener() { public void onClick(final DialogInterface dialog, final int id) { EditAddressBookEntryFragment.edit(getFragmentManager(), address, suggestedAddressLabel); } }); builder.setNegativeButton(R.string.button_dismiss, null); builder.show(); } public void useCalculatedAmount(final BigInteger amount) { amountView.setAmount(amount); } }