1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.latin.utils; 18 19 import android.text.Spannable; 20 import android.text.SpannableString; 21 import android.text.Spanned; 22 import android.text.SpannedString; 23 import android.text.TextUtils; 24 import android.text.style.SuggestionSpan; 25 import android.text.style.URLSpan; 26 27 public final class SpannableStringUtils { 28 /** 29 * Copies the spans from the region <code>start...end</code> in 30 * <code>source</code> to the region 31 * <code>destoff...destoff+end-start</code> in <code>dest</code>. 32 * Spans in <code>source</code> that begin before <code>start</code> 33 * or end after <code>end</code> but overlap this range are trimmed 34 * as if they began at <code>start</code> or ended at <code>end</code>. 35 * Only SuggestionSpans that don't have the SPAN_PARAGRAPH span are copied. 36 * 37 * This code is almost entirely taken from {@link TextUtils#copySpansFrom}, except for the 38 * kind of span that is copied. 39 * 40 * @throws IndexOutOfBoundsException if any of the copied spans 41 * are out of range in <code>dest</code>. 42 */ copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end, Spannable dest, int destoff)43 public static void copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end, 44 Spannable dest, int destoff) { 45 Object[] spans = source.getSpans(start, end, SuggestionSpan.class); 46 47 for (int i = 0; i < spans.length; i++) { 48 int fl = source.getSpanFlags(spans[i]); 49 // We don't care about the PARAGRAPH flag in LatinIME code. However, if this flag 50 // is set, Spannable#setSpan will throw an exception unless the span is on the edge 51 // of a word. But the spans have been split into two by the getText{Before,After}Cursor 52 // methods, so after concatenation they may end in the middle of a word. 53 // Since we don't use them, we can just remove them and avoid crashing. 54 fl &= ~Spannable.SPAN_PARAGRAPH; 55 56 int st = source.getSpanStart(spans[i]); 57 int en = source.getSpanEnd(spans[i]); 58 59 if (st < start) 60 st = start; 61 if (en > end) 62 en = end; 63 64 dest.setSpan(spans[i], st - start + destoff, en - start + destoff, 65 fl); 66 } 67 } 68 69 /** 70 * Returns a CharSequence concatenating the specified CharSequences, retaining their 71 * SuggestionSpans that don't have the PARAGRAPH flag, but not other spans. 72 * 73 * This code is almost entirely taken from {@link TextUtils#concat(CharSequence...)}, except 74 * it calls copyNonParagraphSuggestionSpansFrom instead of {@link TextUtils#copySpansFrom}. 75 */ concatWithNonParagraphSuggestionSpansOnly(CharSequence... text)76 public static CharSequence concatWithNonParagraphSuggestionSpansOnly(CharSequence... text) { 77 if (text.length == 0) { 78 return ""; 79 } 80 81 if (text.length == 1) { 82 return text[0]; 83 } 84 85 boolean spanned = false; 86 for (int i = 0; i < text.length; i++) { 87 if (text[i] instanceof Spanned) { 88 spanned = true; 89 break; 90 } 91 } 92 93 StringBuilder sb = new StringBuilder(); 94 for (int i = 0; i < text.length; i++) { 95 sb.append(text[i]); 96 } 97 98 if (!spanned) { 99 return sb.toString(); 100 } 101 102 SpannableString ss = new SpannableString(sb); 103 int off = 0; 104 for (int i = 0; i < text.length; i++) { 105 int len = text[i].length(); 106 107 if (text[i] instanceof Spanned) { 108 copyNonParagraphSuggestionSpansFrom((Spanned) text[i], 0, len, ss, off); 109 } 110 111 off += len; 112 } 113 114 return new SpannedString(ss); 115 } 116 hasUrlSpans(final CharSequence text, final int startIndex, final int endIndex)117 public static boolean hasUrlSpans(final CharSequence text, 118 final int startIndex, final int endIndex) { 119 if (!(text instanceof Spanned)) { 120 return false; // Not spanned, so no link 121 } 122 final Spanned spanned = (Spanned)text; 123 // getSpans(x, y) does not return spans that start on x or end on y. x-1, y+1 does the 124 // trick, and works in all cases even if startIndex <= 0 or endIndex >= text.length(). 125 final URLSpan[] spans = spanned.getSpans(startIndex - 1, endIndex + 1, URLSpan.class); 126 return null != spans && spans.length > 0; 127 } 128 } 129