package com.greenaddress.greenbits.ui;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.btchip.BTChipDongle;
import com.btchip.BTChipDongle.BTChipPublicKey;
import com.btchip.BTChipException;
import com.btchip.comm.BTChipTransport;
import com.btchip.comm.android.BTChipTransportAndroid;
import com.btchip.comm.android.BTChipTransportAndroidNFC;
import com.btchip.utils.KeyUtils;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.greenaddress.greenapi.LoginData;
import com.greenaddress.greenapi.LoginFailed;
import com.greenaddress.greenbits.wallets.BTChipHWWallet;
import com.greenaddress.greenbits.wallets.TrezorHWWallet;
import com.satoshilabs.trezor.Trezor;
import com.satoshilabs.trezor.TrezorGUICallback;
import java.util.List;
import java.util.concurrent.ExecutionException;
import nordpol.android.AndroidCard;
import nordpol.android.OnDiscoveredTagListener;
import nordpol.android.TagDispatcher;
public class RequestLoginActivity extends LoginActivity implements OnDiscoveredTagListener {
private static final String TAG = RequestLoginActivity.class.getSimpleName();
private static final byte DUMMY_COMMAND[] = { (byte)0xE0, (byte)0xC4, (byte)0x00, (byte)0x00, (byte)0x00 };
private Dialog mBTChipDialog;
private BTChipHWWallet mHwWallet;
private TagDispatcher mTagDispatcher;
private Tag mTag;
private SettableFuture<BTChipTransport> mTransportFuture;
private MaterialDialog mNfcWaitDialog;
@Override
protected int getMainViewId() { return R.layout.activity_first_login_requested; }
@Override
protected void onCreateWithService(final Bundle savedInstanceState) {}
private boolean onTrezor() {
final Trezor t;
t = Trezor.getDevice(this, new TrezorGUICallback() {
@Override
public String pinMatrixRequest() {
final SettableFuture<String> ret = SettableFuture.create();
RequestLoginActivity.this.runOnUiThread(new Runnable() {
public void run() {
final View v = getLayoutInflater().inflate(R.layout.dialog_trezor_pin, null, false);
final Button[] buttons = new Button[]{
// upside down
UI.find(v, R.id.trezorPinButton7),
UI.find(v, R.id.trezorPinButton8),
UI.find(v, R.id.trezorPinButton9),
UI.find(v, R.id.trezorPinButton4),
UI.find(v, R.id.trezorPinButton5),
UI.find(v, R.id.trezorPinButton6),
UI.find(v, R.id.trezorPinButton1),
UI.find(v, R.id.trezorPinButton2),
UI.find(v, R.id.trezorPinButton3)
};
final EditText pinValue = UI.find(v, R.id.trezorPinValue);
for (int i = 0; i < 9; ++i) {
final int ii = i;
buttons[i].setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
pinValue.setText(UI.getText(pinValue) + (ii + 1));
pinValue.setSelection(UI.getText(pinValue).length());
}
});
}
UI.popup(RequestLoginActivity.this, "Hardware Wallet PIN")
.customView(v, true)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(final MaterialDialog dialog, final DialogAction which) {
ret.set(UI.getText(pinValue));
}
}).build().show();
}
});
try {
return ret.get();
} catch (final InterruptedException | ExecutionException e) {
e.printStackTrace();
return "";
}
}
@Override
public String passphraseRequest() {
final SettableFuture<String> ret = SettableFuture.create();
RequestLoginActivity.this.runOnUiThread(new Runnable() {
public void run() {
final View v = getLayoutInflater().inflate(R.layout.dialog_trezor_passphrase, null, false);
final EditText passphraseValue = UI.find(v, R.id.trezorPassphraseValue);
UI.popup(RequestLoginActivity.this, "Hardware Wallet passphrase")
.customView(v, true)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(final MaterialDialog dialog, final DialogAction which) {
ret.set(UI.getText(passphraseValue));
}
}).build().show();
}
});
try {
return ret.get();
} catch (final InterruptedException | ExecutionException e) {
e.printStackTrace();
return "";
}
}
});
if (t == null)
return false;
final List<Integer> version = t.getFirmwareVersion();
if (t.getVendorId() == 21324 &&
(version.get(0) < 1 || (version.get(0) == 1 && version.get(1) < 3))) {
final TextView instructions = UI.find(this, R.id.firstLoginRequestedInstructionsText);
instructions.setText(R.string.firstLoginRequestedInstructionsOldTrezor);
return true;
}
if (t.getVendorId() == 11044 && (version.get(0) < 1)) {
final TextView instructions = UI.find(this, R.id.firstLoginRequestedInstructionsText);
instructions.setText(R.string.firstLoginRequestedInstructionsOldTrezor);
return true;
}
Futures.addCallback(Futures.transform(mService.onConnected, new AsyncFunction<Void, LoginData>() {
@Override
public ListenableFuture<LoginData> apply(final Void input) throws Exception {
return mService.login(new TrezorHWWallet(t));
}
}), new FutureCallback<LoginData>() {
@Override
public void onSuccess(final LoginData result) {
RequestLoginActivity.this.onLoginSuccess();
}
@Override
public void onFailure(final Throwable t) {
if (Throwables.getRootCause(t) instanceof LoginFailed)
// login failed - most likely TREZOR/KeepKey/BWALLET/AvalonWallet not paired
runOnUiThread(new Runnable() {
public void run() {
new MaterialDialog.Builder(RequestLoginActivity.this)
.title(R.string.trezor_login_failed)
.content(R.string.trezor_login_failed_details)
.build().show();
}
});
else
finishOnUiThread();
}
});
return true;
}
private void onLedger(final Intent intent) {
final TextView edit = UI.find(this, R.id.firstLoginRequestedInstructionsText);
UI.clear(edit);
UI.hide(edit);
// not TREZOR/KeepKey/BWALLET/AvalonWallet, so must be BTChip
if (mTag != null)
showPinDialog();
else {
final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if (device != null)
if (BTChipTransportAndroid.isLedgerWithScreen(device))
login(device);
else
showPinDialog(device);
}
}
private void onUsbDeviceDetected(final Intent intent) {
if (onTrezor())
return;
onLedger(intent);
}
private void showPinDialog() {
showPinDialog(null);
}
private void login(final UsbDevice device) {
Futures.addCallback(Futures.transform(mService.onConnected, new AsyncFunction<Void, LoginData>() {
@Override
public ListenableFuture<LoginData> apply(final Void nada) throws Exception {
final BTChipTransport transport;
if (device != null) {
final UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
transport = BTChipTransportAndroid.open(manager, device);
} else {
// If the tag was already tapped, work with it
transport = getTransport(mTag);
if (transport == null) {
// Prompt the user to tap
runOnUiThread(new Runnable() {
public void run() {
mNfcWaitDialog = new MaterialDialog.Builder(RequestLoginActivity.this)
.title("BTChip")
.content("Please tap card")
.build();
mNfcWaitDialog.show();
}
});
return Futures.immediateFuture(null);
}
}
final TextView instructions = UI.find(RequestLoginActivity.this, R.id.firstLoginRequestedInstructionsText);
transport.setDebug(BuildConfig.DEBUG);
final BTChipDongle dongle = new BTChipDongle(transport, true);
try {
dongle.getFirmwareVersion();
} catch (final BTChipException e) {
e.printStackTrace();
// we are in dashboard mode ignore usb
runOnUiThread(new Runnable() {
public void run() {
UI.show(instructions);
instructions.setText(R.string.firstLoginRequestedPleaseOpenBitcoinApp);
}
});
return Futures.immediateFuture(null);
}
mHwWallet = new BTChipHWWallet(transport);
final ProgressBar loginProgress = UI.find(RequestLoginActivity.this, R.id.signingLogin);
runOnUiThread(new Runnable() {
public void run() {
UI.show(loginProgress);
}
});
return mService.login(mHwWallet);
}
}), mOnLoggedIn);
}
private void showPinDialog(final UsbDevice device) {
final SettableFuture<String> pinFuture = SettableFuture.create();
RequestLoginActivity.this.runOnUiThread(new Runnable() {
public void run() {
final View v = getLayoutInflater().inflate(R.layout.dialog_btchip_pin, null, false);
final EditText pinValue = UI.find(v, R.id.btchipPINValue);
final ProgressBar loginProgress = UI.find(RequestLoginActivity.this, R.id.signingLogin);
mBTChipDialog = UI.popup(RequestLoginActivity.this, "BTChip PIN")
.customView(v, true)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(final MaterialDialog dialog, final DialogAction which) {
UI.show(loginProgress);
pinFuture.set(UI.getText(pinValue));
}
})
.onNegative(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(final MaterialDialog dialog, final DialogAction which) {
RequestLoginActivity.this.toast(R.string.err_request_login_no_pin);
RequestLoginActivity.this.finish();
}
}).build();
pinValue.requestFocus();
pinValue.setOnEditorActionListener(
UI.getListenerRunOnEnter(new Runnable() {
public void run() {
UI.show(loginProgress);
mBTChipDialog.hide();
pinFuture.set(UI.getText(pinValue));
}
})
);
UI.showDialog(mBTChipDialog);
}
});
Futures.addCallback(Futures.transform(mService.onConnected, new AsyncFunction<Void, LoginData>() {
@Override
public ListenableFuture<LoginData> apply(final Void input) throws Exception {
return Futures.transform(pinFuture, new AsyncFunction<String, LoginData>() {
@Override
public ListenableFuture<LoginData> apply(final String pin) throws Exception {
mTransportFuture = SettableFuture.create();
if (device != null) {
final UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
mTransportFuture.set(BTChipTransportAndroid.open(manager, device));
} else {
// If the tag was already tapped, work with it
final BTChipTransport transport = getTransport(mTag);
if (transport != null)
mTransportFuture.set(transport);
else {
// Prompt the user to tap
mNfcWaitDialog = new MaterialDialog.Builder(RequestLoginActivity.this)
.title("BTChip")
.content("Please tap card")
.build();
mNfcWaitDialog.show();
}
}
return Futures.transform(mTransportFuture, new AsyncFunction<BTChipTransport, LoginData>() {
@Override
public ListenableFuture<LoginData> apply(final BTChipTransport transport) {
transport.setDebug(BuildConfig.DEBUG);
final SettableFuture<Integer> remainingAttemptsFuture = SettableFuture.create();
mHwWallet = new BTChipHWWallet(transport, pin, remainingAttemptsFuture);
return Futures.transform(remainingAttemptsFuture, new AsyncFunction<Integer, LoginData>() {
@Override
public ListenableFuture<LoginData> apply(final Integer remainingAttempts) {
if (remainingAttempts == -1)
return mService.login(mHwWallet); // -1 means success, so login
final String msg;
if (remainingAttempts > 0)
msg = getString(R.string.btchipInvalidPIN, remainingAttempts);
else
msg = getString(R.string.btchipNotSetup);
RequestLoginActivity.this.runOnUiThread(new Runnable() {
public void run() {
RequestLoginActivity.this.toast(msg);
RequestLoginActivity.this.finish();
}
});
return Futures.immediateFuture(null);
}
});
}
});
}
});
}
}), mOnLoggedIn);
}
private final FutureCallback<LoginData> mOnLoggedIn = new FutureCallback<LoginData>() {
@Override
public void onSuccess(final LoginData result) {
if (result != null)
RequestLoginActivity.this.onLoginSuccess();
}
@Override
public void onFailure(final Throwable t) {
t.printStackTrace();
if (Throwables.getRootCause(t) instanceof LoginFailed) {
// Attempt auto register
try {
final BTChipPublicKey masterPublicKey = mHwWallet.getDongle().getWalletPublicKey("");
final BTChipPublicKey loginPublicKey = mHwWallet.getDongle().getWalletPublicKey("18241'");
Futures.addCallback(mService.signup(mHwWallet, KeyUtils.compressPublicKey(masterPublicKey.getPublicKey()), masterPublicKey.getChainCode(), KeyUtils.compressPublicKey(loginPublicKey.getPublicKey()), loginPublicKey.getChainCode()),
new FutureCallback<LoginData>() {
@Override
public void onSuccess(final LoginData result) {
RequestLoginActivity.this.onLoginSuccess();
}
@Override
public void onFailure(final Throwable t) {
t.printStackTrace();
finishOnUiThread();
}
});
return;
} catch (final Exception e) {
e.printStackTrace();
}
}
finishOnUiThread();
}
};
@Override
public void onDestroy() {
super.onDestroy();
if (mBTChipDialog != null)
mBTChipDialog.dismiss();
}
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
setResult(resultCode, data);
finish();
}
@Override
public void onResumeWithService() {
registerReceiver(mOnUsb, new IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED));
mTag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
mTagDispatcher = TagDispatcher.get(this, this);
if (((mTag != null) && (NfcAdapter.ACTION_TECH_DISCOVERED.equals(getIntent().getAction()))) ||
(getIntent().getAction() != null &&
getIntent().getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED))) {
onUsbDeviceDetected(getIntent());
return;
}
if (mService.cfg("pin").getString("ident", null) != null)
startActivityForResult(new Intent(this, PinActivity.class), 0);
else
startActivityForResult(new Intent(this, MnemonicActivity.class), 0);
mTagDispatcher.enableExclusiveNfc();
}
@Override
public void onPauseWithService() {
unregisterReceiver(mOnUsb);
mTagDispatcher.disableExclusiveNfc();
}
private final BroadcastReceiver mOnUsb = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
onUsbDeviceDetected(intent);
}
};
private BTChipTransport getTransport(final Tag t) {
BTChipTransport transport = null;
if (t != null) {
AndroidCard card = null;
Log.d(TAG, "Start checking NFC transport");
try {
card = AndroidCard.get(t);
transport = new BTChipTransportAndroidNFC(card);
transport.setDebug(BuildConfig.DEBUG);
transport.exchange(DUMMY_COMMAND).get();
Log.d(TAG, "NFC transport checked");
}
catch (final Exception e) {
Log.d(TAG, "Tag was lost", e);
if (card != null) {
try {
transport.close();
}
catch (final Exception e1) {
}
transport = null;
}
}
}
return transport;
}
@Override
public void tagDiscovered(final Tag t) {
Log.d(TAG, "tagDiscovered " + t);
mTag = t;
if (mTransportFuture == null)
return;
final BTChipTransport transport = getTransport(t);
if (transport == null)
return;
if (mTransportFuture.set(transport)) {
if (mNfcWaitDialog == null)
return;
runOnUiThread(new Runnable() { public void run() { mNfcWaitDialog.hide(); } });
}
}
}