private boolean sendBatch(String batchedEvents, IStorage storage) {
    // This is "" if we upload an empty file which we should just skip
    if (batchedEvents.equals("")) {
      removedStorages.add(storage);
      return true;
    }

    byte[] compressedBatchedEvents = compressor.compress(batchedEvents);

    // Write event string
    try {
      if (compressedBatchedEvents != null) {
        sender.sendEvent(compressedBatchedEvents, true);
      } else {
        sender.sendEvent(batchedEvents);
      }
    } catch (IOException e) {
      logger.error(TAG, "Cannot send event: " + e.getMessage());
      int newPeriod = period * 2;
      if (newPeriod > SettingsStore.getCllSettingsAsInt(SettingsStore.Settings.MAXRETRYPERIOD)) {
        // The next scheduled drain is coming soon (~2.5 min so going higher exponentially won't
        // help)
        return false;
      }

      // If we don't remove these then on next call the drain method will end up creating a new
      // empty file by this name.
      storages.removeAll(removedStorages);

      EventQueueWriter r =
          new EventQueueWriter(
              endpoint, storages, clientTelemetry, cllEvents, logger, executorService, newPeriod);
      r.setSender(sender);
      future = executorService.schedule(r, newPeriod, TimeUnit.SECONDS);
      return false; // If we run into an error sending events we just return. This ensures we don't
                    // lose events
    }

    return true;
  }
  /** Sends a real time event by itself */
  protected void sendRealTimeEvent(SerializedEvent singleEvent) {
    // Check to see if this single serialized event is greater than MAX_BUFFER_SIZE, if it is we
    // drop it.
    if (singleEvent.getSerializedData().length()
        > SettingsStore.getCllSettingsAsInt(SettingsStore.Settings.MAXEVENTSIZEINBYTES)) {
      return;
    }

    try {
      sender.sendEvent(singleEvent.getSerializedData());
    } catch (IOException e) {
      // Edge case for real time events that try to send but don't have network.
      // In this case we need to write to disk
      // Force Normal latency so we don't keep looping back to here
      handler.log(singleEvent);
      logger.error(TAG, "Cannot send event");
    }

    for (ICllEvents event : cllEvents) {
      event.sendComplete();
    }
  }
  /** Serializes, batches, and sends events */
  protected void send() {
    // Ensure that the serialized event string is under MAXEVENTSIZEINBYTES.
    // If it is over MAXEVENTSIZEINBYTES then we should use 2 or more strings and send them
    for (IStorage storage : storages) {
      if (executorService.isShutdown()) {
        return;
      }

      List<String> events = storage.drain();
      for (String event : events) {
        this.clientTelemetry.IncrementEventsQueuedForUpload();

        // Check to see if this single serialized event is greater than MAX_BUFFER_SIZE, if it is we
        // drop it.
        if (event.length()
            > SettingsStore.getCllSettingsAsInt(SettingsStore.Settings.MAXEVENTSIZEINBYTES)) {

          // This could cause big problems if the host application decides to do a ton of processing
          // for each
          // dropped event.
          for (ICllEvents cllEvent : cllEvents) {
            cllEvent.eventDropped(event);
          }

          continue;
        }

        if (batcher.canAddToBatch(event)) {
          try {
            batcher.addEventToBatch(event);
          } catch (EventBatcher.BatchFullException e) {
            logger.error(TAG, "Could not add to batch");
          }
        } else {
          // Full batch, send events
          String batchedEvents = batcher.getBatchedEvents();

          try {
            batcher.addEventToBatch(event);
          } catch (EventBatcher.BatchFullException e) {
            logger.error(TAG, "Could not add to batch");
          }

          boolean sendResult = sendBatch(batchedEvents, storage);
          if (sendResult == false) {
            storage.close();
            return;
          }
        }
      }

      // Send remaining events that didn't fill a whole batch
      String batchedEvents = batcher.getBatchedEvents();
      boolean sendResult = sendBatch(batchedEvents, storage);
      if (sendResult == false) {
        storage.close();
        return;
      }

      storage.discard();
    }

    logger.info(TAG, "Sent " + clientTelemetry.snapshot.getEventsQueuedForUpload() + " events.");

    for (ICllEvents event : cllEvents) {
      event.sendComplete();
    }
  }