Loading core/java/android/text/SpannableStringBuilder.java +144 −76 Original line number Diff line number Diff line Loading @@ -257,45 +257,50 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable return append(String.valueOf(text)); } private void change(int start, int end, CharSequence tb, int tbstart, int tbend) { checkRange("replace", start, end); private void change(int start, int end, CharSequence cs, int csStart, int csEnd) { // Can be negative final int nbNewChars = (csEnd - csStart) - (end - start); for (int i = mSpanCount - 1; i >= 0; i--) { if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) { int st = mSpanStarts[i]; if (st > mGapStart) st -= mGapLength; int spanStart = mSpanStarts[i]; if (spanStart > mGapStart) spanStart -= mGapLength; int en = mSpanEnds[i]; if (en > mGapStart) en -= mGapLength; int spanEnd = mSpanEnds[i]; if (spanEnd > mGapStart) spanEnd -= mGapLength; int ost = st; int oen = en; if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) { int ost = spanStart; int oen = spanEnd; int clen = length(); if (st > start && st <= end) { for (st = end; st < clen; st++) if (st > end && charAt(st - 1) == '\n') if (spanStart > start && spanStart <= end) { for (spanStart = end; spanStart < clen; spanStart++) if (spanStart > end && charAt(spanStart - 1) == '\n') break; } if (en > start && en <= end) { for (en = end; en < clen; en++) if (en > end && charAt(en - 1) == '\n') if (spanEnd > start && spanEnd <= end) { for (spanEnd = end; spanEnd < clen; spanEnd++) if (spanEnd > end && charAt(spanEnd - 1) == '\n') break; } if (st != ost || en != oen) setSpan(false, mSpans[i], st, en, mSpanFlags[i]); if (spanStart != ost || spanEnd != oen) setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]); } int flags = 0; if (spanStart == start) flags |= SPAN_START_AT_START; else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END; if (spanEnd == start) flags |= SPAN_END_AT_START; else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END; mSpanFlags[i] |= flags; } moveGapTo(end); // Can be negative final int nbNewChars = (tbend - tbstart) - (end - start); if (nbNewChars >= mGapLength) { resizeFor(mText.length + nbNewChars - mGapLength); } Loading @@ -306,7 +311,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (mGapLength < 1) new Exception("mGapLength < 1").printStackTrace(); TextUtils.getChars(tb, tbstart, tbend, mText, start); TextUtils.getChars(cs, csStart, csEnd, mText, start); if (end > start) { // no need for span fixup on pure insertion Loading Loading @@ -340,20 +345,22 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } } if (tb instanceof Spanned) { Spanned sp = (Spanned) tb; Object[] spans = sp.getSpans(tbstart, tbend, Object.class); mSpanCountBeforeAdd = mSpanCount; if (cs instanceof Spanned) { Spanned sp = (Spanned) cs; Object[] spans = sp.getSpans(csStart, csEnd, Object.class); for (int i = 0; i < spans.length; i++) { int st = sp.getSpanStart(spans[i]); int en = sp.getSpanEnd(spans[i]); if (st < tbstart) st = tbstart; if (en > tbend) en = tbend; if (st < csStart) st = csStart; if (en > csEnd) en = csEnd; // Add span only if this object is not yet used as a span in this string if (getSpanStart(spans[i]) < 0) { setSpan(false, spans[i], st - tbstart + start, en - tbstart + start, if (getSpanStart(spans[i]) < 0 && !(spans[i] instanceof SpanWatcher)) { setSpan(false, spans[i], st - csStart + start, en - csStart + start, sp.getSpanFlags(spans[i])); } } Loading Loading @@ -390,6 +397,8 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // Documentation from interface public SpannableStringBuilder replace(final int start, final int end, CharSequence tb, int tbstart, int tbend) { checkRange("replace", start, end); int filtercount = mFilters.length; for (int i = 0; i < filtercount; i++) { CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end); Loading @@ -404,10 +413,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable final int origLen = end - start; final int newLen = tbend - tbstart; if (origLen == 0 && newLen == 0) { return this; } TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class); sendBeforeTextChanged(textWatchers, start, origLen, newLen); Loading @@ -415,43 +420,101 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // a text replacement. If replaced or replacement text length is zero, this // is already taken care of. boolean adjustSelection = origLen != 0 && newLen != 0; int selstart = 0; int selend = 0; int selectionStart = 0; int selectionEnd = 0; if (adjustSelection) { selstart = Selection.getSelectionStart(this); selend = Selection.getSelectionEnd(this); selectionStart = Selection.getSelectionStart(this); selectionEnd = Selection.getSelectionEnd(this); } checkRange("replace", start, end); change(start, end, tb, tbstart, tbend); if (adjustSelection) { if (selstart > start && selstart < end) { long off = selstart - start; off = off * newLen / origLen; selstart = (int) off + start; if (selectionStart > start && selectionStart < end) { final int offset = (selectionStart - start) * newLen / origLen; selectionStart = start + offset; setSpan(false, Selection.SELECTION_START, selstart, selstart, setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart, Spanned.SPAN_POINT_POINT); } if (selend > start && selend < end) { long off = selend - start; if (selectionEnd > start && selectionEnd < end) { final int offset = (selectionEnd - start) * newLen / origLen; selectionEnd = start + offset; off = off * newLen / origLen; selend = (int) off + start; setSpan(false, Selection.SELECTION_END, selend, selend, Spanned.SPAN_POINT_POINT); setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd, Spanned.SPAN_POINT_POINT); } } sendTextChanged(textWatchers, start, origLen, newLen); sendAfterTextChanged(textWatchers); // Span watchers need to be called after text watchers, which may update the layout sendToSpanWatchers(start, end, newLen - origLen); return this; } private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) { for (int i = 0; i < mSpanCountBeforeAdd; i++) { int spanStart = mSpanStarts[i]; int spanEnd = mSpanEnds[i]; if (spanStart > mGapStart) spanStart -= mGapLength; if (spanEnd > mGapStart) spanEnd -= mGapLength; int spanFlags = mSpanFlags[i]; int newReplaceEnd = replaceEnd + nbNewChars; boolean spanChanged = false; int previousSpanStart = spanStart; if (spanStart > newReplaceEnd) { if (nbNewChars != 0) { previousSpanStart -= nbNewChars; spanChanged = true; } } else if (spanStart >= replaceStart) { // No change if span start was already at replace interval boundaries before replace if ((spanStart != replaceStart || ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) && (spanStart != newReplaceEnd || ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) { // TODO previousSpanStart is incorrect, but we would need to save all the // previous spans' positions before replace to provide it spanChanged = true; } } int previousSpanEnd = spanEnd; if (spanEnd > newReplaceEnd) { if (nbNewChars != 0) { previousSpanEnd -= nbNewChars; spanChanged = true; } } else if (spanEnd >= replaceStart) { // No change if span start was already at replace interval boundaries before replace if ((spanEnd != replaceStart || ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) && (spanEnd != newReplaceEnd || ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) { // TODO same as above for previousSpanEnd spanChanged = true; } } if (spanChanged) { sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd); } mSpanFlags[i] &= ~SPAN_START_END_MASK; } // The spans starting at mIntermediateSpanCount were added from the replacement text for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) { int spanStart = mSpanStarts[i]; int spanEnd = mSpanEnds[i]; if (spanStart > mGapStart) spanStart -= mGapLength; if (spanEnd > mGapStart) spanEnd -= mGapLength; sendSpanAdded(mSpans[i], spanStart, spanEnd); } } /** * Mark the specified range of text with the specified object. * The flags determine how the span will behave when text is Loading Loading @@ -788,8 +851,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (end <= mGapStart) { System.arraycopy(mText, start, dest, destoff, end - start); } else if (start >= mGapStart) { System.arraycopy(mText, start + mGapLength, dest, destoff, end - start); System.arraycopy(mText, start + mGapLength, dest, destoff, end - start); } else { System.arraycopy(mText, start, dest, destoff, mGapStart - start); System.arraycopy(mText, mGapStart + mGapLength, Loading Loading @@ -863,12 +925,14 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } } private void sendSpanChanged(Object what, int s, int e, int st, int en) { SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), SpanWatcher.class); int n = recip.length; private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) { // The bounds of a possible SpanWatcher are guaranteed to be set before this method is // called, so that the order of the span does not affect this broadcast. SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start), Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class); int n = spanWatchers.length; for (int i = 0; i < n; i++) { recip[i].onSpanChanged(this, what, s, e, st, en); spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end); } } Loading @@ -879,22 +943,19 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private void checkRange(final String operation, int start, int end) { if (end < start) { throw new IndexOutOfBoundsException(operation + " " + region(start, end) + " has end before start"); region(start, end) + " has end before start"); } int len = length(); if (start > len || end > len) { throw new IndexOutOfBoundsException(operation + " " + region(start, end) + " ends beyond length " + len); region(start, end) + " ends beyond length " + len); } if (start < 0 || end < 0) { throw new IndexOutOfBoundsException(operation + " " + region(start, end) + " starts before 0"); region(start, end) + " starts before 0"); } } Loading Loading @@ -1059,8 +1120,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (end <= mGapStart) { ret = p.getTextWidths(mText, start, end - start, widths); } else if (start >= mGapStart) { ret = p.getTextWidths(mText, start + mGapLength, end - start, widths); ret = p.getTextWidths(mText, start + mGapLength, end - start, widths); } else { char[] buf = TextUtils.obtain(end - start); Loading Loading @@ -1205,6 +1265,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private int[] mSpanEnds; private int[] mSpanFlags; private int mSpanCount; private int mSpanCountBeforeAdd; // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned} private static final int MARK = 1; Loading @@ -1214,4 +1275,11 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private static final int START_MASK = 0xF0; private static final int END_MASK = 0x0F; private static final int START_SHIFT = 4; // These bits are not (currently) used by SPANNED flags private static final int SPAN_START_AT_START = 0x1000; private static final int SPAN_START_AT_END = 0x2000; private static final int SPAN_END_AT_START = 0x4000; private static final int SPAN_END_AT_END = 0x8000; private static final int SPAN_START_END_MASK = 0xF000; } Loading
core/java/android/text/SpannableStringBuilder.java +144 −76 Original line number Diff line number Diff line Loading @@ -257,45 +257,50 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable return append(String.valueOf(text)); } private void change(int start, int end, CharSequence tb, int tbstart, int tbend) { checkRange("replace", start, end); private void change(int start, int end, CharSequence cs, int csStart, int csEnd) { // Can be negative final int nbNewChars = (csEnd - csStart) - (end - start); for (int i = mSpanCount - 1; i >= 0; i--) { if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) { int st = mSpanStarts[i]; if (st > mGapStart) st -= mGapLength; int spanStart = mSpanStarts[i]; if (spanStart > mGapStart) spanStart -= mGapLength; int en = mSpanEnds[i]; if (en > mGapStart) en -= mGapLength; int spanEnd = mSpanEnds[i]; if (spanEnd > mGapStart) spanEnd -= mGapLength; int ost = st; int oen = en; if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) { int ost = spanStart; int oen = spanEnd; int clen = length(); if (st > start && st <= end) { for (st = end; st < clen; st++) if (st > end && charAt(st - 1) == '\n') if (spanStart > start && spanStart <= end) { for (spanStart = end; spanStart < clen; spanStart++) if (spanStart > end && charAt(spanStart - 1) == '\n') break; } if (en > start && en <= end) { for (en = end; en < clen; en++) if (en > end && charAt(en - 1) == '\n') if (spanEnd > start && spanEnd <= end) { for (spanEnd = end; spanEnd < clen; spanEnd++) if (spanEnd > end && charAt(spanEnd - 1) == '\n') break; } if (st != ost || en != oen) setSpan(false, mSpans[i], st, en, mSpanFlags[i]); if (spanStart != ost || spanEnd != oen) setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]); } int flags = 0; if (spanStart == start) flags |= SPAN_START_AT_START; else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END; if (spanEnd == start) flags |= SPAN_END_AT_START; else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END; mSpanFlags[i] |= flags; } moveGapTo(end); // Can be negative final int nbNewChars = (tbend - tbstart) - (end - start); if (nbNewChars >= mGapLength) { resizeFor(mText.length + nbNewChars - mGapLength); } Loading @@ -306,7 +311,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (mGapLength < 1) new Exception("mGapLength < 1").printStackTrace(); TextUtils.getChars(tb, tbstart, tbend, mText, start); TextUtils.getChars(cs, csStart, csEnd, mText, start); if (end > start) { // no need for span fixup on pure insertion Loading Loading @@ -340,20 +345,22 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } } if (tb instanceof Spanned) { Spanned sp = (Spanned) tb; Object[] spans = sp.getSpans(tbstart, tbend, Object.class); mSpanCountBeforeAdd = mSpanCount; if (cs instanceof Spanned) { Spanned sp = (Spanned) cs; Object[] spans = sp.getSpans(csStart, csEnd, Object.class); for (int i = 0; i < spans.length; i++) { int st = sp.getSpanStart(spans[i]); int en = sp.getSpanEnd(spans[i]); if (st < tbstart) st = tbstart; if (en > tbend) en = tbend; if (st < csStart) st = csStart; if (en > csEnd) en = csEnd; // Add span only if this object is not yet used as a span in this string if (getSpanStart(spans[i]) < 0) { setSpan(false, spans[i], st - tbstart + start, en - tbstart + start, if (getSpanStart(spans[i]) < 0 && !(spans[i] instanceof SpanWatcher)) { setSpan(false, spans[i], st - csStart + start, en - csStart + start, sp.getSpanFlags(spans[i])); } } Loading Loading @@ -390,6 +397,8 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // Documentation from interface public SpannableStringBuilder replace(final int start, final int end, CharSequence tb, int tbstart, int tbend) { checkRange("replace", start, end); int filtercount = mFilters.length; for (int i = 0; i < filtercount; i++) { CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end); Loading @@ -404,10 +413,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable final int origLen = end - start; final int newLen = tbend - tbstart; if (origLen == 0 && newLen == 0) { return this; } TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class); sendBeforeTextChanged(textWatchers, start, origLen, newLen); Loading @@ -415,43 +420,101 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // a text replacement. If replaced or replacement text length is zero, this // is already taken care of. boolean adjustSelection = origLen != 0 && newLen != 0; int selstart = 0; int selend = 0; int selectionStart = 0; int selectionEnd = 0; if (adjustSelection) { selstart = Selection.getSelectionStart(this); selend = Selection.getSelectionEnd(this); selectionStart = Selection.getSelectionStart(this); selectionEnd = Selection.getSelectionEnd(this); } checkRange("replace", start, end); change(start, end, tb, tbstart, tbend); if (adjustSelection) { if (selstart > start && selstart < end) { long off = selstart - start; off = off * newLen / origLen; selstart = (int) off + start; if (selectionStart > start && selectionStart < end) { final int offset = (selectionStart - start) * newLen / origLen; selectionStart = start + offset; setSpan(false, Selection.SELECTION_START, selstart, selstart, setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart, Spanned.SPAN_POINT_POINT); } if (selend > start && selend < end) { long off = selend - start; if (selectionEnd > start && selectionEnd < end) { final int offset = (selectionEnd - start) * newLen / origLen; selectionEnd = start + offset; off = off * newLen / origLen; selend = (int) off + start; setSpan(false, Selection.SELECTION_END, selend, selend, Spanned.SPAN_POINT_POINT); setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd, Spanned.SPAN_POINT_POINT); } } sendTextChanged(textWatchers, start, origLen, newLen); sendAfterTextChanged(textWatchers); // Span watchers need to be called after text watchers, which may update the layout sendToSpanWatchers(start, end, newLen - origLen); return this; } private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) { for (int i = 0; i < mSpanCountBeforeAdd; i++) { int spanStart = mSpanStarts[i]; int spanEnd = mSpanEnds[i]; if (spanStart > mGapStart) spanStart -= mGapLength; if (spanEnd > mGapStart) spanEnd -= mGapLength; int spanFlags = mSpanFlags[i]; int newReplaceEnd = replaceEnd + nbNewChars; boolean spanChanged = false; int previousSpanStart = spanStart; if (spanStart > newReplaceEnd) { if (nbNewChars != 0) { previousSpanStart -= nbNewChars; spanChanged = true; } } else if (spanStart >= replaceStart) { // No change if span start was already at replace interval boundaries before replace if ((spanStart != replaceStart || ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) && (spanStart != newReplaceEnd || ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) { // TODO previousSpanStart is incorrect, but we would need to save all the // previous spans' positions before replace to provide it spanChanged = true; } } int previousSpanEnd = spanEnd; if (spanEnd > newReplaceEnd) { if (nbNewChars != 0) { previousSpanEnd -= nbNewChars; spanChanged = true; } } else if (spanEnd >= replaceStart) { // No change if span start was already at replace interval boundaries before replace if ((spanEnd != replaceStart || ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) && (spanEnd != newReplaceEnd || ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) { // TODO same as above for previousSpanEnd spanChanged = true; } } if (spanChanged) { sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd); } mSpanFlags[i] &= ~SPAN_START_END_MASK; } // The spans starting at mIntermediateSpanCount were added from the replacement text for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) { int spanStart = mSpanStarts[i]; int spanEnd = mSpanEnds[i]; if (spanStart > mGapStart) spanStart -= mGapLength; if (spanEnd > mGapStart) spanEnd -= mGapLength; sendSpanAdded(mSpans[i], spanStart, spanEnd); } } /** * Mark the specified range of text with the specified object. * The flags determine how the span will behave when text is Loading Loading @@ -788,8 +851,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (end <= mGapStart) { System.arraycopy(mText, start, dest, destoff, end - start); } else if (start >= mGapStart) { System.arraycopy(mText, start + mGapLength, dest, destoff, end - start); System.arraycopy(mText, start + mGapLength, dest, destoff, end - start); } else { System.arraycopy(mText, start, dest, destoff, mGapStart - start); System.arraycopy(mText, mGapStart + mGapLength, Loading Loading @@ -863,12 +925,14 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } } private void sendSpanChanged(Object what, int s, int e, int st, int en) { SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), SpanWatcher.class); int n = recip.length; private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) { // The bounds of a possible SpanWatcher are guaranteed to be set before this method is // called, so that the order of the span does not affect this broadcast. SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start), Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class); int n = spanWatchers.length; for (int i = 0; i < n; i++) { recip[i].onSpanChanged(this, what, s, e, st, en); spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end); } } Loading @@ -879,22 +943,19 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private void checkRange(final String operation, int start, int end) { if (end < start) { throw new IndexOutOfBoundsException(operation + " " + region(start, end) + " has end before start"); region(start, end) + " has end before start"); } int len = length(); if (start > len || end > len) { throw new IndexOutOfBoundsException(operation + " " + region(start, end) + " ends beyond length " + len); region(start, end) + " ends beyond length " + len); } if (start < 0 || end < 0) { throw new IndexOutOfBoundsException(operation + " " + region(start, end) + " starts before 0"); region(start, end) + " starts before 0"); } } Loading Loading @@ -1059,8 +1120,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (end <= mGapStart) { ret = p.getTextWidths(mText, start, end - start, widths); } else if (start >= mGapStart) { ret = p.getTextWidths(mText, start + mGapLength, end - start, widths); ret = p.getTextWidths(mText, start + mGapLength, end - start, widths); } else { char[] buf = TextUtils.obtain(end - start); Loading Loading @@ -1205,6 +1265,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private int[] mSpanEnds; private int[] mSpanFlags; private int mSpanCount; private int mSpanCountBeforeAdd; // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned} private static final int MARK = 1; Loading @@ -1214,4 +1275,11 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private static final int START_MASK = 0xF0; private static final int END_MASK = 0x0F; private static final int START_SHIFT = 4; // These bits are not (currently) used by SPANNED flags private static final int SPAN_START_AT_START = 0x1000; private static final int SPAN_START_AT_END = 0x2000; private static final int SPAN_END_AT_START = 0x4000; private static final int SPAN_END_AT_END = 0x8000; private static final int SPAN_START_END_MASK = 0xF000; }