class AndroidLogcatReceiver extends AndroidOutputReceiver implements Disposable {

  @Override
  public void processNewLine(@NotNull String line) {
    // Really, the user's log should never put any system characters in it ever - that will cause
    // it to get filtered by our strict regex patterns (see AndroidLogcatFormatter). The reason
    // this might happen in practice is due to a bug where either adb or logcat (not sure which)
    // is too aggressive about converting \n's to \r\n's, including those that are quoted. This
    // means that a user's log, if it uses \r\n itself, is converted to \r\r\n. Then, when
    // MultiLineReceiver, which expects valid input, strips out \r\n, it leaves behind an extra \r.
    //
    // Unfortunately this isn't a case where we can fix the root cause because adb and logcat are
    // both external to Android Studio. In fact, the latest adb/logcat versions have already fixed
    // this issue! But we still need to run properly with older versions. Also, putting this fix in
    // MultiLineReceiver isn't right either because it is used for more than just receiving logcat.
    line = CARRIAGE_RETURN.matcher(line).replaceAll("");

    if (line.isEmpty()) {
      myDelayedNewlineCount++;
      return;
    }

    LogCatHeader header = myLongEpochParser.processLogHeader(line, myDevice);

    if (header == null) {
      header = myLongParser.processLogHeader(line, myDevice);
    }

    if (header != null) {
      myStackTraceExpander.reset();
      myActiveHeader = header;
      myLineIndex = 0;
      // Intentionally drop any trailing newlines once we hit a new header. Usually, logcat
      // separates log entries with a single newline but sometimes it outputs more than one. As we
      // can't know which is user newlines vs. system newlines, just drop all of them.
      myDelayedNewlineCount = 0;
    }
    else if (myActiveHeader != null) {
      if (myDelayedNewlineCount > 0 && myLineIndex == 0) {
        // Note: Since we trim trailing newlines, we trim leading newlines too. Most users won't
        // use them intentionally and they don't look great, anyway.
        myDelayedNewlineCount = 0;
      }
      else {
        processAnyDelayedNewlines(myActiveHeader);
      }
      for (String processedLine : myStackTraceExpander.process(line)) {
        notifyLine(myActiveHeader, processedLine);
      }
    }
  }

    AndroidLogcatReceiver(@NotNull IDevice device, @NotNull LogcatListener listener);

}