package org.multibit.hd.ui.views.wizards; import com.google.common.base.Optional; import com.google.common.eventbus.Subscribe; 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.hardware.core.events.HardwareWalletEvents; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; /** * <p>Abstract base class to provide the following to UI:</p> * <ul> * <li>Provision of common methods to wizards that support hardware wallet operations</li> * </ul> * * @param <M> the hardware wallet wizard model * * @since 0.0.1 */ public abstract class AbstractHardwareWalletWizard<M extends AbstractHardwareWalletWizardModel> extends AbstractWizard<M> { private static final Logger log = LoggerFactory.getLogger(AbstractHardwareWalletWizard.class); /** * @param wizardModel The overall wizard data model containing the aggregate information of all components in the wizard * @param isExiting True if the exit button should trigger an application shutdown * @param wizardParameter An optional parameter that can be referenced during construction */ protected AbstractHardwareWalletWizard(M wizardModel, boolean isExiting, Optional wizardParameter) { this(wizardModel, isExiting, wizardParameter, true); } /** * @param wizardModel The overall wizard data model containing the aggregate information of all components in the wizard * @param isExiting True if the exit button should trigger an application shutdown * @param wizardParameter An optional parameter that can be referenced during construction * @param escapeIsCancel A press of the ESC key cancels the wizard */ protected AbstractHardwareWalletWizard(M wizardModel, boolean isExiting, Optional wizardParameter, boolean escapeIsCancel) { super(wizardModel, isExiting, wizardParameter, escapeIsCancel); // All hardware wallet wizards can receive hardware wallet events HardwareWalletEvents.subscribe(this); } /** * Unregister from hardware wallet events - called during the hide process */ @Override public void unsubscribe() { super.unsubscribe(); HardwareWalletEvents.unsubscribe(this); } @Override public void hide(final String panelName, final boolean isExitCancel) { log.debug("Hide requested for {} with exitCancel {} ", panelName, isExitCancel); if (!wizardViewMap.containsKey(panelName)) { log.error("'{}' is not a valid panel name. Check the panel has been registered in the view map. Registered panels are\n{}", wizardViewMap.keySet()); return; } final AbstractWizardPanelView wizardPanelView = wizardViewMap.get(panelName); // Provide warning that the panel is about to be hidden if (wizardPanelView.beforeHide(isExitCancel)) { // Ensure the hardware wallet is reset getWizardModel().requestCancel(); // No cancellation so go ahead with the hide handleHide(panelName, isExitCancel, wizardPanelView); } } /** * <p>Inform the wizard model of a "device failed"</p> * * @param event The originating event containing payload and context */ public void handleDeviceFailed(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } // Move to the "device failed" state getWizardModel().showDeviceFailed(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of a "device ready"</p> * * @param event The originating event containing payload and context */ public void handleDeviceReady(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } String oldPanel = getWizardModel().getPanelName(); // Move to the "device ready" state getWizardModel().showDeviceReady(event); // Show the panel if different String newPanel = getWizardModel().getPanelName(); if (oldPanel == null || !oldPanel.equals(newPanel)) { show(getWizardModel().getPanelName()); } } }); } /** * <p>Inform the wizard model of a "device detached"</p> * * @param event The originating event containing payload and context */ public void handleDeviceDetached(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } // Move to the "device detached" state getWizardModel().showDeviceDetached(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of a "device stopped"</p> * * @param event The originating event containing payload and context */ public void handleDeviceStopped(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } // Move to the "device stopped" state getWizardModel().showDeviceStopped(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of a "PIN entry"</p> * * @param event The originating event containing payload and context */ public void handlePINEntry(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } // Move to the "PIN entry" state getWizardModel().showPINEntry(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of a "passphrase entry"</p> * * @param event The originating event containing payload and context */ public void handlePassphraseEntry(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } // Move to the "passphrase entry" state getWizardModel().showPassphraseEntry(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of a "button press"</p> * * @param event The originating event containing payload and context */ public void handleButtonPress(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { log.debug("Wizard panel name {}", getWizardModel().getPanelName()); if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } // Move to the "button press" state getWizardModel().showButtonPress(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of an "operation succeeded"</p> * * @param event The originating event containing payload and context */ private void handleOperationSucceeded(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } // Move to the "operation succeeded" state getWizardModel().showOperationSucceeded(event); // Show the panel try { show(getWizardModel().getPanelName()); } catch (IllegalStateException ise) { // Carry on log.debug(ise.getMessage()); } } }); } /** * <p>Inform the wizard model of an "operation failed"</p> * * @param event The originating event containing payload and context */ public void handleOperationFailed(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } // Move to the "operation failed" state getWizardModel().showOperationFailed(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of a "provide entropy"</p> * * @param event The originating event containing payload and context */ public void handleProvideEntropy(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); // Move to the "operation failed" state getWizardModel().showProvideEntropy(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of a "received address"</p> * * @param event The originating event containing payload and context */ public void handleReceivedAddress(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); // Move to the "received address" state getWizardModel().receivedAddress(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of a "received public key"</p> * * @param event The originating event containing payload and context */ public void handleReceivedPublicKey(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } // Move to the "received public key" state getWizardModel().receivedPublicKey(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of a "received deterministic hierarchy"</p> * * @param event The originating event containing payload and context */ public void handleReceivedDeterministicHierarchy(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) if (getWizardModel().getPanelName() != null) { if (getWizardPanelView(getWizardModel().getPanelName()) != null) { getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); } } // Move to the "received deterministic hierarchy" state getWizardModel().receivedDeterministicHierarchy(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Inform the wizard model of a "received message signature"</p> * * @param event The originating event containing payload and context */ public void handleReceivedMessageSignature(final HardwareWalletEvent event) { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { // Ensure the panel updates its model (the button is outside of the panel itself) getWizardPanelView(getWizardModel().getPanelName()).updateFromComponentModels(Optional.absent()); // Move to the "received message signature" state getWizardModel().receivedMessageSignature(event); // Show the panel show(getWizardModel().getPanelName()); } }); } /** * <p>Respond to hardware wallet events. Not SHOW_DEVICE_* since the MainController handles them.</p> * <p>Translate the hardware event into an action on the EDT for easier integration with the existing * framework</p> * * @param event The hardware wallet event indicating a state change */ @Subscribe public void onHardwareWalletEvent(HardwareWalletEvent event) { log.debug("{} Received hardware event: '{}'.", this, event.getEventType().name()); if (!ApplicationEventService.isHardwareWalletEventAllowed()) { log.debug("Ignoring device event due to 'ignore threshold' still in force", event); return; } // Check if this is the first event from the hardware wallet Optional<HardwareWalletService> currentHardwareWalletService = CoreServices.getCurrentHardwareWalletService(); if (!currentHardwareWalletService.isPresent()) { // Allow time for the current hardware wallet to initialise CoreServices.useFirstReadyHardwareWalletService(); } switch (event.getEventType()) { case SHOW_DEVICE_FAILED: handleDeviceFailed(event); break; case SHOW_DEVICE_READY: handleDeviceReady(event); break; case SHOW_DEVICE_DETACHED: handleDeviceDetached(event); break; case SHOW_DEVICE_STOPPED: handleDeviceStopped(event); break; case SHOW_PIN_ENTRY: handlePINEntry(event); break; case SHOW_PASSPHRASE_ENTRY: handlePassphraseEntry(event); break; case SHOW_BUTTON_PRESS: handleButtonPress(event); break; case SHOW_OPERATION_SUCCEEDED: handleOperationSucceeded(event); break; case SHOW_OPERATION_FAILED: handleOperationFailed(event); break; case PROVIDE_ENTROPY: handleProvideEntropy(event); break; case ADDRESS: handleReceivedAddress(event); break; case PUBLIC_KEY: handleReceivedPublicKey(event); break; case DETERMINISTIC_HIERARCHY: handleReceivedDeterministicHierarchy(event); break; case MESSAGE_SIGNATURE: handleReceivedMessageSignature(event); break; case SHOW_WORD_ENTRY: break; default: log.warn("Unknown hardware wallet event type: {}", event.getEventType().name()); break; } } }