@Override
    public void onDoneReceive(AsyncReport report) {
      if (delegate != null) {
        delegate.onDoneReceive(report);
      }

      if (finished.getAndSet(true)) {
        errors.add("onDoneReceive has been called multiple times.");
      }

      if (!taskExecutor.isExecutingInThis()) {
        errors.add("onDoneReceive must be executed from the context of the executor.");
      }

      reportRef.set(report);
    }
    @Override
    public void onDataArrive(ImageResult data) {
      if (delegate != null) {
        delegate.onDataArrive(data);
      }

      if (finished.get()) {
        errors.add("onDataArrive has been called after onDoneReceive.");
      }

      if (!taskExecutor.isExecutingInThis()) {
        errors.add("onDataArrive must be executed from the context of the executor.");
      }

      lastResultRef.set(data);
      numberOfImages.incrementAndGet();
    }
    /** Confined to the eventScheduler. */
    private void tryEndReceive() {
      assert eventScheduler.isExecutingInThis();

      AsyncReport report1;
      AsyncReport report2;

      mainLock.lock();
      try {
        if (sessionReport == null || endReport == null || finished) {
          return;
        }

        finished = true;
        report1 = sessionReport;
        report2 = endReport;
      } finally {
        mainLock.unlock();
      }

      boolean wasCanceled = report1.isCanceled() || report2.isCanceled();
      Throwable ex1 = report1.getException();
      Throwable ex2 = report2.getException();

      Throwable exception;
      if (ex1 == null) {
        exception = ex2;
      } else if (ex2 == null) {
        exception = ex1;
      } else {
        exception = new DataTransferException();
        exception.addSuppressed(ex1);
        exception.addSuppressed(ex2);
      }

      AsyncReport report = AsyncReport.getReport(exception, wasCanceled);
      outputListener.onDoneReceive(report);
    }