/**
   * {@inheritDoc}
   *
   * @throws OperaRunnerException if launcher is shut down or not running
   */
  @Override
  public void startOpera() throws OperaRunnerException {
    assertLauncherAlive();

    try {
      byte[] request = LauncherStartRequest.newBuilder().build().toByteArray();

      ResponseEncapsulation res = protocol.sendRequest(MessageType.MSG_START, request);

      if (handleStatusMessage(res.getResponse()) != StatusType.RUNNING) {
        throw new IOException("launcher unable to start binary");
      }

      // Check Opera hasn't immediately exited (e.g. due to unknown arguments)
      try {
        Thread.sleep(OperaIntervals.PROCESS_START_SLEEP.getMs());
      } catch (InterruptedException e) {
        // nothing
      }

      res = protocol.sendRequest(MessageType.MSG_STATUS, request);

      if (handleStatusMessage(res.getResponse()) != StatusType.RUNNING) {
        throw new IOException(
            "Opera exited immediately; possibly incorrect arguments?  Command: "
                + binary.getCommands());
      }
    } catch (IOException e) {
      throw new OperaRunnerException("Could not start Opera: " + e.getMessage());
    }
  }
  /**
   * {@inheritDoc}
   *
   * @throws OperaRunnerException if launcher is shut down or not running
   */
  @Override
  public void stopOpera() throws OperaRunnerException {
    assertLauncherAlive();

    if (!isOperaRunning()) {
      return;
    }

    try {
      LauncherStopRequest.Builder request = LauncherStopRequest.newBuilder();

      ResponseEncapsulation res =
          protocol.sendRequest(MessageType.MSG_STOP, request.build().toByteArray());

      if (handleStatusMessage(res.getResponse()) == StatusType.RUNNING) {
        throw new IOException("launcher unable to stop binary");
      }
    } catch (IOException e) {
      throw new OperaRunnerException("Could not stop Opera: " + e.getMessage());
    }
  }
  /**
   * Take screenshot using external program. Will not trigger a screen repaint.
   *
   * @throws OperaRunnerException if launcher is shutdown or not running
   */
  @Override
  public ScreenShotReply saveScreenshot(long timeout, String... hashes)
      throws OperaRunnerException {
    assertLauncherAlive();

    String resultMd5;
    byte[] resultBytes;
    boolean blank = false;

    try {
      LauncherScreenshotRequest.Builder request = LauncherScreenshotRequest.newBuilder();
      for (String hash : hashes) {
        request.addKnownMD5S(hash);
      }
      request.setKnownMD5STimeoutMs((int) timeout);

      ResponseEncapsulation res =
          protocol.sendRequest(MessageType.MSG_SCREENSHOT, request.build().toByteArray());
      LauncherScreenshotResponse response = (LauncherScreenshotResponse) res.getResponse();

      resultMd5 = response.getMd5();
      resultBytes = response.getImagedata().toByteArray();

      if (response.hasBlank()) {
        blank = response.getBlank();
      }

    } catch (SocketTimeoutException e) {
      throw new OperaRunnerException("Could not get screenshot from launcher", e);
    } catch (IOException e) {
      throw new OperaRunnerException("Could not get screenshot from launcher", e);
    }

    ScreenShotReply screenshotreply = new ScreenShotReply(resultMd5, resultBytes);
    screenshotreply.setBlank(blank);
    screenshotreply.setCrashed(this.hasOperaCrashed());

    return screenshotreply;
  }