private void sendTextToGecko(CharSequence text, int caretPos) {
    if (DEBUG) Log.d(LOGTAG, "IME: sendTextToGecko(\"" + text + "\")");

    // Handle composition text styles
    if (text != null && text instanceof Spanned) {
      Spanned span = (Spanned) text;
      int spanStart = 0, spanEnd = 0;
      boolean pastSelStart = false, pastSelEnd = false;

      do {
        int rangeType = GeckoEvent.IME_RANGE_CONVERTEDTEXT;
        int rangeStyles = 0, rangeForeColor = 0, rangeBackColor = 0;

        // Find next offset where there is a style transition
        spanEnd = span.nextSpanTransition(spanStart + 1, text.length(), CharacterStyle.class);

        // Empty range, continue
        if (spanEnd <= spanStart) continue;

        // Get and iterate through list of span objects within range
        CharacterStyle[] styles = span.getSpans(spanStart, spanEnd, CharacterStyle.class);

        for (CharacterStyle style : styles) {
          if (style instanceof UnderlineSpan) {
            // Text should be underlined
            rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE;
          } else if (style instanceof ForegroundColorSpan) {
            // Text should be of a different foreground color
            rangeStyles |= GeckoEvent.IME_RANGE_FORECOLOR;
            rangeForeColor = ((ForegroundColorSpan) style).getForegroundColor();
          } else if (style instanceof BackgroundColorSpan) {
            // Text should be of a different background color
            rangeStyles |= GeckoEvent.IME_RANGE_BACKCOLOR;
            rangeBackColor = ((BackgroundColorSpan) style).getBackgroundColor();
          }
        }

        // Add range to array, the actual styles are
        //  applied when IME_SET_TEXT is sent
        if (DEBUG) {
          Log.d(
              LOGTAG,
              String.format(
                  ". . . sendTextToGecko: IME_ADD_RANGE, %d, %d, %d, %d, %d, %d",
                  spanStart,
                  spanEnd - spanStart,
                  rangeType,
                  rangeStyles,
                  rangeForeColor,
                  rangeBackColor));
        }

        GeckoAppShell.sendEventToGecko(
            GeckoEvent.createIMERangeEvent(
                spanStart,
                spanEnd - spanStart,
                rangeType,
                rangeStyles,
                rangeForeColor,
                rangeBackColor));

        spanStart = spanEnd;
      } while (spanStart < text.length());
    } else {
      if (DEBUG)
        Log.d(
            LOGTAG,
            ". . . sendTextToGecko: IME_ADD_RANGE, 0, "
                + text.length()
                + ", IME_RANGE_RAWINPUT, IME_RANGE_UNDERLINE)");
      GeckoAppShell.sendEventToGecko(
          GeckoEvent.createIMERangeEvent(
              0,
              text == null ? 0 : text.length(),
              GeckoEvent.IME_RANGE_RAWINPUT,
              GeckoEvent.IME_RANGE_UNDERLINE,
              0,
              0));
    }

    // Change composition (treating selection end as where the caret is)
    if (DEBUG) {
      Log.d(
          LOGTAG,
          ". . . sendTextToGecko: IME_SET_TEXT, IME_RANGE_CARETPOSITION, \"" + text + "\")");
    }

    GeckoAppShell.sendEventToGecko(
        GeckoEvent.createIMERangeEvent(
            caretPos, 0, GeckoEvent.IME_RANGE_CARETPOSITION, 0, 0, 0, text.toString()));
  }
  /**
   * Renders and/or measures a directional run of text on a single line. Unlike {@link
   * #drawUniformRun}, this can render runs that cross style boundaries. Returns the signed advance
   * width, if requested.
   *
   * <p>
   *
   * <p>The x position is the leading edge of the text. In a right-to-left paragraph, this will be
   * to the right of the text to be drawn. Paint should not have an Align value other than LEFT or
   * positioning will isCancelled confused.
   *
   * <p>
   *
   * <p>This optimizes for unstyled text and so workPaint might not be modified by this call.
   *
   * <p>
   *
   * <p>The returned advance width will be < 0 if the paragraph direction is right-to-left.
   */
  private static float drawDirectionalRun(
      Canvas canvas,
      CharSequence text,
      int start,
      int end,
      int dir,
      boolean runIsRtl,
      float x,
      int top,
      int y,
      int bottom,
      Paint.FontMetricsInt fmi,
      TextPaint paint,
      TextPaint workPaint,
      boolean needWidth) {

    // XXX: It looks like all calls to this API match dir and runIsRtl, so
    // having both parameters is redundant and confusing.

    // fast path for unstyled text
    if (!(text instanceof Spanned)) {
      float ret = 0;

      if (runIsRtl) {
        CharSequence tmp = TextUtils.getReverse(text, start, end);
        // XXX: this assumes getReverse doesn't tweak the length of
        // the text
        int tmpend = end - start;

        if (canvas != null || needWidth) {
          ret = paint.measureText(tmp, 0, tmpend);
        }

        if (canvas != null) {
          canvas.drawText(tmp, 0, tmpend, x - ret, y, paint);
        }
      } else {
        if (needWidth) {
          ret = paint.measureText(text, start, end);
        }

        if (canvas != null) {
          canvas.drawText(text, start, end, x, y, paint);
        }
      }

      if (fmi != null) {
        paint.getFontMetricsInt(fmi);
      }

      return ret * dir; // Layout.DIR_RIGHT_TO_LEFT == -1
    }

    float ox = x;
    int minAscent = 0, maxDescent = 0, minTop = 0, maxBottom = 0;

    Spanned sp = (Spanned) text;
    Class<?> division;

    if (canvas == null) {
      division = MetricAffectingSpan.class;
    } else {
      division = CharacterStyle.class;
    }

    int next;
    for (int i = start; i < end; i = next) {
      next = sp.nextSpanTransition(i, end, division);

      // XXX: if dir and runIsRtl were not the same, this would onDraw
      // spans in the wrong order, but no one appears to call it this
      // way.
      x +=
          drawUniformRun(
              canvas,
              sp,
              i,
              next,
              dir,
              runIsRtl,
              x,
              top,
              y,
              bottom,
              fmi,
              paint,
              workPaint,
              needWidth || next != end);

      if (fmi != null) {
        if (fmi.ascent < minAscent) {
          minAscent = fmi.ascent;
        }
        if (fmi.descent > maxDescent) {
          maxDescent = fmi.descent;
        }

        if (fmi.top < minTop) {
          minTop = fmi.top;
        }
        if (fmi.bottom > maxBottom) {
          maxBottom = fmi.bottom;
        }
      }
    }

    if (fmi != null) {
      if (start == end) {
        paint.getFontMetricsInt(fmi);
      } else {
        fmi.ascent = minAscent;
        fmi.descent = maxDescent;
        fmi.top = minTop;
        fmi.bottom = maxBottom;
      }
    }

    return x - ox;
  }