/*
* Copyright 2011-2012 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 piuk.blockchain.android.ui;
import java.math.BigInteger;
import java.util.Locale;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentActivity;
import android.text.ClipboardManager;
import android.util.Pair;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.Toast;
import piuk.blockchain.android.EventListeners;
import piuk.blockchain.android.MyRemoteWallet;
import piuk.blockchain.android.MyRemoteWallet.SendProgress;
import piuk.blockchain.android.R;
import piuk.blockchain.android.Constants;
import piuk.blockchain.android.SuccessCallback;
import piuk.blockchain.android.WalletApplication;
import piuk.blockchain.android.WalletApplication.AddAddressCallback;
import piuk.blockchain.android.ui.dialogs.RequestPasswordDialog;
//import piuk.blockchain.android.util.ActionBarFragment;
import piuk.blockchain.android.util.WalletUtils;
import com.dm.zbar.android.scanner.ZBarConstants;
import com.dm.zbar.android.scanner.ZBarScannerActivity;
import com.google.android.gcm.GCMRegistrar;
import com.google.bitcoin.core.Address;
import com.google.bitcoin.core.ECKey;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.params.MainNetParams;
import com.google.bitcoin.uri.BitcoinURI;
/**
* @author Andreas Schildbach
*/
public abstract class AbstractWalletActivity extends FragmentActivity {
protected WalletApplication application = (WalletApplication) this.getApplication();
// protected ActionBarFragment actionBar;
protected final AbstractWalletActivity self = this;
protected Handler handler = new Handler();
protected ActivityDelegate activityDelegate;
public static long lastDisplayedNetworkError = 0;
private static final int ZBAR_SCANNER_REQUEST = 0;
private static final int ZBAR_QR_SCANNER_REQUEST = 1;
protected boolean dontCheckStatus = false;
public abstract class QrCodeDelagate implements ActivityDelegate {
public abstract void didReadQRCode(String data) throws Exception;
@Override
public void onActivityResult(final int requestCode, final int resultCode,
final Intent intent) {
//Zxing
if (resultCode == RESULT_OK) {
final String raw_code = intent.getStringExtra("SCAN_RESULT");
try {
if (raw_code == null || raw_code.length() == 0)
throw new Exception("Null result returned");
didReadQRCode(raw_code);
} catch (Exception e) {
e.printStackTrace();
longToast(R.string.unknown_error);
}
}
}
}
public void registerNotifications() {
try {
final String regId = GCMRegistrar.getRegistrationId(this);
if (regId == null || regId.equals("")) {
GCMRegistrar.register(this, Constants.SENDER_ID);
} else {
application.registerForNotificationsIfNeeded(regId);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static interface ActivityDelegate {
public void onActivityResult(final int requestCode, final int resultCode,
final Intent intent);
}
@Override
public void onActivityResult(final int requestCode, final int resultCode,
final Intent intent) {
if (activityDelegate != null)
activityDelegate.onActivityResult(requestCode, resultCode, intent);
}
public void showQRReader(ActivityDelegate activityDelegate) {
this.activityDelegate = activityDelegate;
Intent intent = new Intent(this, ZBarScannerActivity.class);
startActivityForResult(intent, ZBAR_QR_SCANNER_REQUEST);
}
static void handleCopyToClipboard(final Context context, final String address)
{
final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboardManager.setText(address);
Toast.makeText(context, R.string.wallet_address_fragment_clipboard_msg, Toast.LENGTH_SHORT).show();
}
public final boolean hasInternetConnection() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isAvailable() && cm.getActiveNetworkInfo().isConnected()) {
return true;
} else {
return false;
}
}
public final boolean isWifiEnabled() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (info.isConnected()) {
return true;
} else {
return false;
}
}
public void startP2PMode() {
final MyRemoteWallet remoteWallet = application.getRemoteWallet();
if (remoteWallet == null)
return;
if (remoteWallet.isDoubleEncrypted() == false) {
application.startBlockchainService();
} else {
if (remoteWallet.temporySecondPassword == null) {
RequestPasswordDialog.show(getSupportFragmentManager(), new SuccessCallback() {
public void onSuccess() {
application.startBlockchainService();
}
public void onFail() {
Toast.makeText(application, R.string.password_incorrect, Toast.LENGTH_LONG).show();
}
}, RequestPasswordDialog.PasswordTypeSecond);
} else {
application.startBlockchainService();
}
}
}
private EventListeners.EventListener eventListener = new EventListeners.EventListener() {
@Override
public String getDescription() {
return getClass() + " Wallet Check Status";
}
@Override
public void onWalletDidChange() {
application.checkWalletStatus(self);
}
@Override
public void onMultiAddrError() {
if (self instanceof WalletActivity) {
if (lastDisplayedNetworkError > System.currentTimeMillis()-Constants.NetworkErrorDisplayThreshold)
return;
//Don't do anything is we are already running the blockchain service
if (application.isInP2PFallbackMode())
return;
lastDisplayedNetworkError = System.currentTimeMillis();
//Only ask for P2P mode when connected to wifi
if (!hasInternetConnection() || !isWifiEnabled()) {
handler.post(new Runnable() {
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(self);
builder.setTitle(R.string.network_error);
builder.setMessage(R.string.network_error_description);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
builder.show();
}
});
} else {
handler.post(new Runnable() {
public void run() {
AlertDialog.Builder builder = new AlertDialog.Builder(self);
builder.setTitle(R.string.blockchain_network_error);
builder.setMessage(R.string.blockchain_network_error_description);
builder.setNegativeButton(R.string.ignore, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
builder.setPositiveButton(R.string.start_p2p_mode, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
startP2PMode();
}
});
builder.show();
}
});
}
}
};
};
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
application = (WalletApplication) this.getApplication();
}
@Override
protected void onResume() {
super.onResume();
EventListeners.addEventListener(eventListener);
if (!dontCheckStatus) {
application.checkWalletStatus(self);
}
application.connect();
}
@Override
protected void onPause() {
super.onPause();
EventListeners.removeEventListener(eventListener);
application.disconnectSoon();
}
@Override
protected void onStart() {
super.onStart();
/*
if (getActionBarFragment() != null) {
actionBar.setIcon(Constants.APP_ICON_RESID);
actionBar.setSecondaryTitle(Constants.TEST ? "[testnet]" : null);
}
*/
}
/*
public ActionBarFragment getActionBarFragment() {
if (actionBar == null)
actionBar = (ActionBarFragment) getSupportFragmentManager()
.findFragmentById(R.id.action_bar_fragment);
return actionBar;
}
*/
public final void toast(final String text, final Object... formatArgs) {
toast(text, 0, Toast.LENGTH_SHORT, formatArgs);
}
public final void longToast(final String text, final Object... formatArgs) {
toast(text, 0, Toast.LENGTH_LONG, formatArgs);
}
public final void toast(final String text, final int imageResId,
final int duration, final Object... formatArgs) {
if (text == null)
return;
final View view = getLayoutInflater().inflate(
R.layout.transient_notification, null);
TextView tv = (TextView) view
.findViewById(R.id.transient_notification_text);
tv.setText(String.format(text, formatArgs));
tv.setCompoundDrawablesWithIntrinsicBounds(imageResId, 0, 0, 0);
final Toast toast = new Toast(this);
toast.setView(view);
toast.setDuration(duration);
toast.show();
}
public final void toast(final int textResId, final Object... formatArgs) {
toast(textResId, 0, Toast.LENGTH_SHORT, formatArgs);
}
public final void longToast(final int textResId, final Object... formatArgs) {
toast(textResId, 0, Toast.LENGTH_LONG, formatArgs);
}
public final void toast(final int textResId, final int imageResId,
final int duration, final Object... formatArgs) {
final View view = getLayoutInflater().inflate(
R.layout.transient_notification, null);
TextView tv = (TextView) view
.findViewById(R.id.transient_notification_text);
tv.setText(getString(textResId, formatArgs));
tv.setCompoundDrawablesWithIntrinsicBounds(imageResId, 0, 0, 0);
final Toast toast = new Toast(this);
toast.setView(view);
toast.setDuration(duration);
toast.show();
}
public void errorDialog(final int title, final String message) {
final Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(title);
dialog.setMessage(message);
dialog.setNeutralButton(R.string.button_dismiss, null);
dialog.show();
}
public void errorDialog(final int title, final String message, final DialogInterface.OnClickListener dismiss) {
final Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(title);
dialog.setMessage(message);
dialog.setNeutralButton(R.string.button_dismiss, dismiss);
dialog.show();
}
protected final static String languagePrefix() {
final String language = Locale.getDefault().getLanguage();
if ("de".equals(language))
return "_de";
else if ("cs".equals(language))
return "_cs";
else if ("el".equals(language))
return "_el";
else if ("es".equals(language))
return "_es";
else if ("fr".equals(language))
return "_fr";
else if ("it".equals(language))
return "_it";
else if ("nl".equals(language))
return "_nl";
else if ("pl".equals(language))
return "_pl";
else if ("ru".equals(language))
return "_ru";
else if ("sv".equals(language))
return "_sv";
else if ("tr".equals(language))
return "_tr";
else if ("zh".equals(language))
return "_zh";
else
return "";
}
public void showMarketPage(final String packageName) {
final Intent marketIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(String.format(Constants.MARKET_APP_URL, packageName)));
if (getPackageManager().resolveActivity(marketIntent, 0) != null)
startActivity(marketIntent);
else
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(String
.format(Constants.WEBMARKET_APP_URL, packageName))));
}
private void handleScanPrivateKeyPair(final ECKey key) throws Exception {
final AlertDialog.Builder b = new AlertDialog.Builder(this);
b.setPositiveButton(R.string.sweep_text, null);
b.setNeutralButton(R.string.import_text, null);
b.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
b.setTitle("Scan Private Key");
b.setMessage("Fetching Balance. Please Wait");
final AlertDialog dialog = b.show();
dialog.getButton(Dialog.BUTTON1).setEnabled(false);
new Thread() {
public void run() {
try {
final String address;
if (key.isCompressed()) {
address = key.toAddressCompressed(MainNetParams.get()).toString();
} else {
address = key.toAddressUnCompressed(MainNetParams.get()).toString();
}
System.out.println("Scanned PK Address " + address);
BigInteger balance = MyRemoteWallet.getAddressBalance(address);
final BigInteger finalBalance = balance;
handler.post(new Runnable() {
@Override
public void run() {
final MyRemoteWallet remoteWallet = application.getRemoteWallet();
if (remoteWallet == null)
return;
dialog.getButton(Dialog.BUTTON3).setEnabled(true);
if (finalBalance.longValue() == 0) {
dialog.setMessage("The Balance of address " + address + " is zero.");
} else {
dialog.getButton(Dialog.BUTTON1).setEnabled(true);
dialog.setMessage("The Balance of "+address+" is "+WalletUtils.formatValue(finalBalance)+" BTC. Would you like to sweep it?");
}
dialog.getButton(Dialog.BUTTON3).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (remoteWallet.isDoubleEncrypted() == false) {
reallyAddKey(dialog, key);
} else {
if (remoteWallet.temporySecondPassword == null) {
RequestPasswordDialog.show(
getSupportFragmentManager(),
new SuccessCallback() {
public void onSuccess() {
reallyAddKey(dialog, key); }
public void onFail() {
}
}, RequestPasswordDialog.PasswordTypeSecond);
} else {
reallyAddKey(dialog, key);
}
}
}
});
dialog.getButton(Dialog.BUTTON1).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
MyRemoteWallet wallet = new MyRemoteWallet();
wallet.addKey(key, address, null);
Address to = application.determineSelectedAddress();
if (to == null) {
handler.post(new Runnable() {
public void run() {
dialog.dismiss();
}
});
return;
}
BigInteger baseFee = wallet.getBaseFee();
wallet.simpleSendCoinsAsync(to.toString(), finalBalance.subtract(baseFee), MyRemoteWallet.FeePolicy.FeeForce, baseFee, new SendProgress() {
@Override
public boolean onReady(
Transaction tx,
BigInteger fee,
MyRemoteWallet.FeePolicy feePolicy,
long priority) {
return true;
}
@Override
public void onSend(
Transaction tx,
String message) {
handler.post(new Runnable() {
public void run() {
dialog.dismiss();
longToast("Private Key Successfully Swept");
}
});
}
@Override
public ECKey onPrivateKeyMissing(String address) {
return null;
}
@Override
public void onError(final String message) {
handler.post(new Runnable() {
public void run() {
dialog.dismiss();
longToast(message);
}
});
}
@Override
public void onProgress(String message) {}
@Override
public void onStart() {
}
});
} catch (final Exception e) {
e.getLocalizedMessage();
handler.post(new Runnable() {
public void run() {
dialog.dismiss();
longToast(e.getLocalizedMessage());
}
});
}
}
});
}
});
} catch (final Exception e) {
e.printStackTrace();
handler.post(new Runnable() {
public void run() {
dialog.dismiss();
longToast(e.getLocalizedMessage());
}
});
}
}
}.start();
}
private void reallyAddKey(final Dialog dialog, final ECKey key) {
application.addKeyToWallet(key, key.toAddress(MainNetParams.get()).toString(), null, 0,
new AddAddressCallback() {
public void onSavedAddress(String address) {
longToast("Private Key Successfully Imported");
dialog.dismiss();
}
public void onError(String reason) {
Toast.makeText(self, reason, Toast.LENGTH_LONG).show();
dialog.dismiss();
}
});
}
public void handleScanPrivateKey(final String data) throws Exception {
handler.postDelayed(new Runnable() {
@Override
public void run() {
try {
final String format = WalletUtils.detectPrivateKeyFormat(data);
System.out.println("Scanned Private Key Format " + format);
if (format.equals("bip38")) {
RequestPasswordDialog.show(getSupportFragmentManager(), new SuccessCallback() {
public void onSuccess() {
String password = RequestPasswordDialog.getPasswordResult();
try {
handleScanPrivateKeyPair(WalletUtils.parsePrivateKey(format, data, password));
} catch (Exception e) {
longToast(e.getLocalizedMessage());
}
}
public void onFail() {
}
}, RequestPasswordDialog.PasswordTypePrivateKey);
} else {
handleScanPrivateKeyPair(WalletUtils.parsePrivateKey(format, data, null));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, 100);
}
public void handleAddWatchOnly(String data) throws Exception {
String address;
try {
address = new Address(Constants.NETWORK_PARAMETERS, data).toString();
} catch (Exception e) {
try {
BitcoinURI uri = new BitcoinURI(data);
address = uri.getAddress().toString();
} catch (Exception e1) {
longToast(R.string.send_coins_fragment_receiving_address_error);
return;
}
}
final AlertDialog.Builder b = new AlertDialog.Builder(this);
final String finalAddress = address;
b.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (application.getRemoteWallet() == null)
return;
try {
application.getRemoteWallet().addWatchOnly(finalAddress, "android_watch_only");
application.saveWallet(new SuccessCallback() {
@Override
public void onSuccess() {
// EditAddressBookEntryFragment.edit(getSupportFragmentManager(), finalAddress);
}
@Override
public void onFail() {
}
});
} catch (Exception e) {
longToast(e.getLocalizedMessage());
}
}
});
b.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
b.setTitle("Watch Only Address");
b.setMessage("Do you wish to add the Watch Only bitcoin address " + address + " to your wallet? \n\nYou will not be able to spend any funds in this address unless you have the private key stored elsewhere. You should never add a Watch Only address that you do not have the private key for.");
b.show();
}
}