private void notifyCommitComposition() {
    // Gecko already committed its composition, and
    // we should remove the composition on our side as well.
    boolean wasComposing = false;
    final Object[] spans = mText.getSpans(0, mText.length(), Object.class);

    for (Object span : spans) {
      if ((mText.getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) {
        mText.removeSpan(span);
        wasComposing = true;
      }
    }

    if (!wasComposing) {
      return;
    }

    // Generate a text change notification if we actually cleared the composition.
    final CharSequence text = TextUtils.stringOrSpannedString(mText);
    geckoPostToIc(
        new Runnable() {
          @Override
          public void run() {
            mListener.onTextChange(text, 0, text.length(), text.length());
          }
        });
  }
  private void geckoActionReply() {
    if (DEBUG) {
      // GeckoEditableListener methods should all be called from the Gecko thread
      ThreadUtils.assertOnGeckoThread();
    }

    final Action action = mActionQueue.peek();
    if (action == null) {
      throw new IllegalStateException("empty actions queue");
    }

    if (DEBUG) {
      Log.d(LOGTAG, "reply: Action(" + getConstantName(Action.class, "TYPE_", action.mType) + ")");
    }
    switch (action.mType) {
      case Action.TYPE_SET_SELECTION:
        final int len = mText.length();
        final int curStart = Selection.getSelectionStart(mText);
        final int curEnd = Selection.getSelectionEnd(mText);
        // start == -1 when the start offset should remain the same
        // end == -1 when the end offset should remain the same
        final int selStart = Math.min(action.mStart < 0 ? curStart : action.mStart, len);
        final int selEnd = Math.min(action.mEnd < 0 ? curEnd : action.mEnd, len);

        if (selStart < action.mStart || selEnd < action.mEnd) {
          Log.w(LOGTAG, "IME sync error: selection out of bounds");
        }
        Selection.setSelection(mText, selStart, selEnd);
        geckoPostToIc(
            new Runnable() {
              @Override
              public void run() {
                mActionQueue.syncWithGecko();
                final int start = Selection.getSelectionStart(mText);
                final int end = Selection.getSelectionEnd(mText);
                if (selStart == start && selEnd == end) {
                  // There has not been another new selection in the mean time that
                  // made this notification out-of-date
                  mListener.onSelectionChange(start, end);
                }
              }
            });
        break;

      case Action.TYPE_SET_SPAN:
        mText.setSpan(action.mSpanObject, action.mStart, action.mEnd, action.mSpanFlags);
        break;

      case Action.TYPE_REMOVE_SPAN:
        mText.removeSpan(action.mSpanObject);
        break;

      case Action.TYPE_SET_HANDLER:
        geckoSetIcHandler(action.mHandler);
        break;
    }
    if (action.mShouldUpdate) {
      geckoUpdateGecko(false);
    }
  }
 private void clearCachedMark(SpannableStringBuilder ssb) {
   Cached[] cs = ssb.getSpans(0, ssb.length(), Cached.class);
   if (cs != null && cs.length > 0) {
     for (Cached c : cs) {
       ssb.removeSpan(c);
     }
   }
 }
 private void resetLinkSpan(SpannableStringBuilder ssb, RichTextConfig config, URLSpan urlSpan) {
   int start = ssb.getSpanStart(urlSpan);
   int end = ssb.getSpanEnd(urlSpan);
   ssb.removeSpan(urlSpan);
   LinkHolder linkHolder = new LinkHolder(urlSpan.getURL());
   if (config.linkFixCallback != null) {
     config.linkFixCallback.fix(linkHolder);
   }
   LongClickableURLSpan longClickableURLSpan =
       new LongClickableURLSpan(
           linkHolder, config.onUrlClickListener, config.onUrlLongClickListener);
   ssb.setSpan(longClickableURLSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 }
  protected void makeLinkClickable(
      SpannableStringBuilder strBuilder, final URLSpan span, final Activity c) {
    int start = strBuilder.getSpanStart(span);
    int end = strBuilder.getSpanEnd(span);
    int flags = strBuilder.getSpanFlags(span);
    final ClickableSpan clickable =
        new ClickableSpan() {

          public void onClick(View view) {
            // TODO make clickable ContentOpen.openingText(url, true, c);

          }
        };

    strBuilder.setSpan(clickable, start, end, flags);
    strBuilder.removeSpan(span);
  }
  public static void setTextWithIcon(Context context, TextView textView, String text) {
    Spanned html = Html.fromHtml(text);
    SpannableStringBuilder builder = new SpannableStringBuilder(html);

    ImageSpan[] images = builder.getSpans(0, builder.length(), ImageSpan.class);
    if (images != null && images.length > 0) {
      ImageSpan imagePlaceholder = images[0];
      int start = builder.getSpanStart(imagePlaceholder);
      int end = builder.getSpanEnd(imagePlaceholder);
      int flags = builder.getSpanFlags(imagePlaceholder);

      ImageSpan imageSpan = new ImageSpan(context, R.drawable.lock_icon, ImageSpan.ALIGN_BASELINE);
      builder.setSpan(imageSpan, start, end, flags);
      builder.removeSpan(imagePlaceholder);
    }

    textView.setText(builder);
  }
 private void handleClick(SpannableStringBuilder ssb, RichTextConfig config, boolean cached) {
   if (cached) {
     LongClickableURLSpan[] lcus = ssb.getSpans(0, ssb.length(), LongClickableURLSpan.class);
     if (lcus != null && lcus.length > 0) {
       for (LongClickableURLSpan lcu : lcus) {
         resetLinkSpan(ssb, config, lcu);
       }
     }
   } else {
     if (config.clickable >= 0) {
       // 处理超链接点击事件
       URLSpan[] urlSpans = ssb.getSpans(0, ssb.length(), URLSpan.class);
       for (int i = 0, size = urlSpans == null ? 0 : urlSpans.length; i < size; i++) {
         resetLinkSpan(ssb, config, urlSpans[i]);
       }
     } else {
       // 移除URLSpan
       URLSpan[] urlSpans = ssb.getSpans(0, ssb.length(), URLSpan.class);
       for (int i = 0, size = urlSpans == null ? 0 : urlSpans.length; i < size; i++) {
         ssb.removeSpan(urlSpans[i]);
       }
     }
   }
 }
  private int handleImage(
      SpannableStringBuilder ssb,
      ImageGetterWrapper imageGetterWrapper,
      RichTextConfig config,
      boolean cached) {
    if (cached) {
      ClickableImageSpan[] cis = ssb.getSpans(0, ssb.length(), ClickableImageSpan.class);
      if (cis != null && cis.length > 0) {
        for (ClickableImageSpan ci : cis) {
          int start = ssb.getSpanStart(ci);
          int end = ssb.getSpanEnd(ci);
          ssb.removeSpan(ci);
          OnImageClickListener onImageClickListener = null;
          OnImageLongClickListener onImageLongClickListener = null;
          if (config.clickable > 0) {
            onImageClickListener = config.onImageClickListener;
            onImageLongClickListener = config.onImageLongClickListener;
          }
          Drawable drawable = imageGetterWrapper.getDrawable(ci.getSource());
          if (drawable == null) {
            drawable = new ColorDrawable(Color.TRANSPARENT);
          }
          ClickableImageSpan nci =
              new ClickableImageSpan(drawable, ci, onImageClickListener, onImageLongClickListener);
          ssb.setSpan(nci, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        return cis.length;
      }
    } else if (!config.noImage) {
      ImageSpan[] iss = ssb.getSpans(0, ssb.length(), ImageSpan.class);
      if (iss != null && iss.length > 0) {
        ArrayList<String> imageUrls = new ArrayList<>(iss.length);
        for (int i = 0; i < iss.length; i++) {
          ImageSpan imageSpan = iss[i];
          String imageUrl = imageSpan.getSource();
          imageUrls.add(imageUrl);

          int start = ssb.getSpanStart(imageSpan);
          int end = ssb.getSpanEnd(imageSpan);
          ClickableSpan[] clickableSpans = ssb.getSpans(start, end, ClickableSpan.class);
          if (clickableSpans != null && clickableSpans.length != 0) {
            for (ClickableSpan cs : clickableSpans) {
              ssb.removeSpan(cs);
            }
          }
          OnImageClickListener onImageClickListener = null;
          OnImageLongClickListener onImageLongClickListener = null;
          if (config.clickable > 0) {
            onImageClickListener = config.onImageClickListener;
            onImageLongClickListener = config.onImageLongClickListener;
          }
          Drawable drawable = imageGetterWrapper.getDrawable(imageUrl);
          if (drawable == null) {
            drawable = new ColorDrawable(Color.TRANSPARENT);
          }
          ClickableImageSpan cacheImageSpan =
              new ClickableImageSpan(
                  drawable, imageUrls, i, onImageClickListener, onImageLongClickListener);
          ssb.removeSpan(imageSpan);
          ssb.setSpan(cacheImageSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        return iss.length;
      }
    }
    return 0;
  }