package org.multibit.hd.ui.views.wizards; import com.google.common.base.Optional; 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.ListeningExecutorService; import org.multibit.commons.concurrent.SafeExecutors; import org.multibit.commons.utils.Dates; import org.multibit.hd.core.dto.WalletMode; import org.multibit.hd.core.services.ApplicationEventService; import org.multibit.hd.core.services.CoreServices; import org.multibit.hd.hardware.core.HardwareWalletService; import org.multibit.hd.hardware.core.events.HardwareWalletEvent; import org.multibit.hd.ui.languages.MessageKey; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.util.concurrent.Callable; /** * <p>Abstract base class wizard model:</p> * <ul> * <li>Access to standard implementations of required methods to support hardware wallets</li> * </ul> * * @param <S> The state object type * * @since 0.0.1 */ public abstract class AbstractHardwareWalletWizardModel<S> extends AbstractWizardModel<S> { private static final Logger log = LoggerFactory.getLogger(AbstractHardwareWalletWizardModel.class); /** * Hardware wallet requests have their own executor service which is shared across all hardware wizards */ protected static final ListeningExecutorService hardwareWalletRequestService = SafeExecutors.newSingleThreadExecutor("hardware-requests"); /** * The current wallet mode (e.g. TREZOR, KEEP_KEY etc) */ private WalletMode walletMode; /** * The hardware wallet report message key */ private Optional<MessageKey> reportMessageKey = Optional.absent(); /** * The hardware wallet message status (true if successful, false if failed) */ private boolean reportMessageStatus = false; protected AbstractHardwareWalletWizardModel(S state) { super(state); // Keep track of the current wallet mode this.walletMode = WalletMode.of(CoreServices.getCurrentHardwareWalletService()); } /** * @return The current wallet mode from when this wizard was created */ public WalletMode getWalletMode() { return walletMode; } /** * @param walletMode The new wallet mode (devices may attach/detach during wizard presentation) */ public void setWalletMode(WalletMode walletMode) { this.walletMode = walletMode; } /** * Handles state transition to a "device failed" panel * * Usually this will be an "Report" panel following a "Request" and the * panel will show a report indicating that Trezor communication has failed * * Clicking "Next" or "Finish" will trigger the end of the wizard or a transition * to a fallback (e.g. password entry) * * @param event The hardware wallet event containing payload and context */ public void showDeviceFailed(HardwareWalletEvent event) { // Do nothing } /** * Handles state transition to a "device ready" panel * * Usually this will be a "use hardware wallet" panel which will show a collection * of options to determine what happens next * * Note that an "operation failure" will reset the device back to its * initialised state leading to a "device ready" so implementers should be * aware of the context * * Clicking "Next" will trigger the next step * * @param event The hardware wallet event containing payload and context */ public void showDeviceReady(HardwareWalletEvent event) { // Do nothing } /** * Handles state transition to a "device detached" panel * * Usually this will be an "Report" panel which will show a report * indicating that Trezor communication has been detached * * Clicking "Next" will trigger the next step * * @param event The hardware wallet event containing payload and context */ public void showDeviceDetached(HardwareWalletEvent event) { // Do nothing } /** * Handles state transition to a "device stopped" panel * * Usually this will be seen during a switch wallet operation where a * device has been shut down * * There may be no user interaction required * * @param event The hardware wallet event containing payload and context */ public void showDeviceStopped(HardwareWalletEvent event) { // Do nothing } /** * Handles state transition to a "PIN entry" panel * * Usually this will be an "EnterCurrentPin" panel following a "Request" and the * panel will show a PIN matrix * * Clicking "Next" or "Unlock" will trigger the sending of PIN positions to the device * and subsequent state transitions * * @param event The hardware wallet event containing payload and context */ public void showPINEntry(HardwareWalletEvent event) { // Do nothing } /** * Handles state transition to a "passphrase entry" panel * * Usually this will be an "EnterCurrentPassphrase" panel following a "Request" and the * panel will show a passphrase dialog (this is currently unsupported see #4 in MultiBit Hardware) * * Clicking "Next" or "Unlock" will trigger the sending of passphrase to the device * and subsequent state transitions * * @param event The hardware wallet event containing payload and context */ public void showPassphraseEntry(HardwareWalletEvent event) { // Do nothing } /** * Handles state transition to a "button press" panel * * Usually this will be a "Confirm" panel following a "Request" and the * panel will show text mirroring the Trezor * * Clicking a button on the device will trigger further state transitions * * @param event The hardware wallet event containing payload and context */ public void showButtonPress(HardwareWalletEvent event) { // Do nothing } /** * Handles state transition to an "operation succeeded" panel * * @param event The hardware wallet event containing payload and context */ public void showOperationSucceeded(HardwareWalletEvent event) { // Do nothing } /** * Handles state transition to an "operation failed" panel * * Typically a wizard would restart with fresh state since the Trezor will fall back * to its initialised state * * @param event The hardware wallet event containing payload and context */ public void showOperationFailed(HardwareWalletEvent event) { // Do nothing } /** * Handles state transition to a "word entry" panel * * @param event The hardware wallet event containing payload and context */ public void showWordEntry(HardwareWalletEvent event) { // Do nothing } /** * Handles state transition to a "provide entropy" panel * * @param event The hardware wallet event containing payload and context */ public void showProvideEntropy(HardwareWalletEvent event) { // Do nothing } /** * Handles provision of an Address from the device * * @param event The hardware wallet event containing payload and context */ public void receivedAddress(HardwareWalletEvent event) { // Do nothing } /** * Handles provision of a public key from the device * * @param event The hardware wallet event containing payload and context */ public void receivedPublicKey(HardwareWalletEvent event) { // Do nothing } /** * Handles provision of a deterministic hierarchy from the device * * @param event The hardware wallet event containing payload and context */ public void receivedDeterministicHierarchy(HardwareWalletEvent event) { // Do nothing } /** * Handles provision of a message signature from the device * * @param event The hardware wallet event containing payload and context */ public void receivedMessageSignature(HardwareWalletEvent event) { // Do nothing } /** * @return The report panel view message key for the hardware wallet operation */ public Optional<MessageKey> getReportMessageKey() { return reportMessageKey; } /** * @param reportMessageKey The report panel view message key for the hardware wallet operation */ public void setReportMessageKey(MessageKey reportMessageKey) { this.reportMessageKey = Optional.fromNullable(reportMessageKey); } /** * @return True if the operation was successful */ public boolean getReportMessageStatus() { return reportMessageStatus; } /** * @param reportMessageStatus True if the operation was successful */ public void setReportMessageStatus(boolean reportMessageStatus) { this.reportMessageStatus = reportMessageStatus; } /** * Request cancellation of current operation * Only sent if the hardware wallet is present */ public void requestCancel() { // Attempt the cancellation operation on a separate thread to avoid UI lockup // if a hardware wallet is not present // Start the request ListenableFuture future = hardwareWalletRequestService.submit( new Callable<Boolean>() { @Override public Boolean call() throws Exception { // See if the attached trezor is initialised - no need to perform a cancel if there is no wallet final Optional<HardwareWalletService> hardwareWalletService = CoreServices.getCurrentHardwareWalletService(); if (hardwareWalletService.isPresent()) { // Cancel the current Trezor operation // The Trezor should respond quickly to a cancel ApplicationEventService.setIgnoreHardwareWalletEventsThreshold(Dates.nowUtc().plusMillis(100)); log.debug("Sending 'request cancel'"); // Cancel the operation hardwareWalletService.get().requestCancel(); log.debug("Request was successful"); } // Must have successfully sent the message to be here return true; } }); Futures.addCallback( future, new FutureCallback() { @Override public void onSuccess(@Nullable Object result) { // We successfully made the request so wait for the result } @Override public void onFailure(Throwable t) { log.warn("Hardware wallet cancel failed", t); } }); } }