Fixed
Status Update
Comments
bp...@googlemail.com <bp...@googlemail.com>
pl...@gmail.com <pl...@gmail.com> #2
Looks like we found the same bug (https://code.google.com/p/android/issues/detail?id=35412 ) - at the moment we're working around it by automatically inserting spaces before and after every styling span, though this mucks up our text formatting a bit.
Another option that occurs to me would be to give every paragraph of the text its own TextView and jam them all together in a ScrollView, but that's likely to seriously impact performance.
Another option that occurs to me would be to give every paragraph of the text its own TextView and jam them all together in a ScrollView, but that's likely to seriously impact performance.
ta...@gmail.com <ta...@gmail.com> #3
Thank you Comment 2. That workaround, adding a space before and after each span, works well for now.
md...@gmail.com <md...@gmail.com> #4
To cope with the bug, we are using a patched version (subclass) of TextView. If the bug is triggered, it downgrades to plain text.
public class PatchedTextView extends TextView {
public PatchedTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public PatchedTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PatchedTextView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}catch (ArrayIndexOutOfBoundsException e){
setText(getText().toString());
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
public void setGravity(int gravity){
try{
super.setGravity(gravity);
}catch (ArrayIndexOutOfBoundsException e){
setText(getText().toString());
super.setGravity(gravity);
}
}
@Override
public void setText(CharSequence text, BufferType type) {
try{
super.setText(text, type);
}catch (ArrayIndexOutOfBoundsException e){
setText(text.toString());
}
}
}
public class PatchedTextView extends TextView {
public PatchedTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public PatchedTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PatchedTextView(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
try{
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}catch (ArrayIndexOutOfBoundsException e){
setText(getText().toString());
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
public void setGravity(int gravity){
try{
super.setGravity(gravity);
}catch (ArrayIndexOutOfBoundsException e){
setText(getText().toString());
super.setGravity(gravity);
}
}
@Override
public void setText(CharSequence text, BufferType type) {
try{
super.setText(text, type);
}catch (ArrayIndexOutOfBoundsException e){
setText(text.toString());
}
}
}
ka...@gmail.com <ka...@gmail.com> #5
A minor correction to Comment 4, should be catching IndexOutOfBounds, not ArrayIndexOutOfBounds.
Thanks for the code sample.
Thanks for the code sample.
do...@gmail.com <do...@gmail.com> #6
Thanks for providing the PatchedTextView (and the change to IndexOutOfBounds)
Does anyone verified that it does not crash with this workaround?
Does anyone verified that it does not crash with this workaround?
ka...@gmail.com <ka...@gmail.com> #7
Yes, I've patched and it works fine.
ro...@android.com <ro...@android.com>
le...@gmail.com <le...@gmail.com> #8
Thank you comment 2 for the workaround.
Although both workarounds avoids the issue, they are not for my case.
I have an EditText which contains many editable spans. So adding space is not appropriate.
Break the text into multiple EditText will make the code very complicated.
So I think I need to find a workaround by my own.
I understand why breaking the paragraphs into many TextViews can solve the problem. But I don't understand why adding spaces can solve the problem?
Could you explain? If I fully understand the bug, I might be able to find another workaround. I will share what I find.
Although both workarounds avoids the issue, they are not for my case.
I have an EditText which contains many editable spans. So adding space is not appropriate.
Break the text into multiple EditText will make the code very complicated.
So I think I need to find a workaround by my own.
I understand why breaking the paragraphs into many TextViews can solve the problem. But I don't understand why adding spaces can solve the problem?
Could you explain? If I fully understand the bug, I might be able to find another workaround. I will share what I find.
le...@gmail.com <le...@gmail.com> #9
Hey Romain,
You changed the status to future release, does that meanwe don't need to find a workaround, because this will be fixed by Android team (for example, in Android 4.1.2) in the near future?
You changed the status to future release, does that meanwe don't need to find a workaround, because this will be fixed by Android team (for example, in Android 4.1.2) in the near future?
pl...@gmail.com <pl...@gmail.com> #10
@9: Adding spaces helps because it guarantees that the system will be able to break the line immediately before / after the span change; it would be better if we could do this with invisible spaces / line break characters, but StaticLayout.java doesn't seem to handle those properly, and spaces have a special status because they can break without adding any width (see StaticLayout.java line 440).
Without the space, the system might encounter the end of the line in the middle of your span and have to roll back to before your span to get to the last break location; this triggers the bug, since the buggy call to measured.setPos() on StaticLayout.java line 449 is intended to deal with that situation.
Without the space, the system might encounter the end of the line in the middle of your span and have to roll back to before your span to get to the last break location; this triggers the bug, since the buggy call to measured.setPos() on StaticLayout.java line 449 is intended to deal with that situation.
lo...@gmail.com <lo...@gmail.com> #11
In my case I can trigger the exception when calling setText with a SpannedString:
I since employed a slightly difference approach which tries to successively remove styled spans until it no longer encounters an error, this hopefully preserves some of the spans.
@Override
public void setText(CharSequence text, BufferType type) {
try{
super.setText(text, type);
} catch (IndexOutOfBoundsException e){
if(text instanceof SpannedString){
SpannedString s = (SpannedString) text;
SpannableStringBuilder ss = new SpannableStringBuilder(s);
StyleSpan[] a = s.getSpans(0, s.length(), StyleSpan.class);
if(a.length>1)
ss.removeSpan(a[0]);
super.setText(ss, type);
}
}
}
I since employed a slightly difference approach which tries to successively remove styled spans until it no longer encounters an error, this hopefully preserves some of the spans.
@Override
public void setText(CharSequence text, BufferType type) {
try{
super.setText(text, type);
} catch (IndexOutOfBoundsException e){
if(text instanceof SpannedString){
SpannedString s = (SpannedString) text;
SpannableStringBuilder ss = new SpannableStringBuilder(s);
StyleSpan[] a = s.getSpans(0, s.length(), StyleSpan.class);
if(a.length>1)
ss.removeSpan(a[0]);
super.setText(ss, type);
}
}
}
py...@gmail.com <py...@gmail.com> #12
Hey there,
As comment #2 suggested, adding spaces works great. We don't need to add spaces everywhere though, only in places that are causing the exception.
Here is an implementation that does just that:https://gist.github.com/3424004
It only fix the onMeasure() method, since that's where my problems came from, but it shouldn't be hard to adapt this to your needs.
As
Here is an implementation that does just that:
It only fix the onMeasure() method, since that's where my problems came from, but it shouldn't be hard to adapt this to your needs.
an...@gmail.com <an...@gmail.com> #13
Comment to #13:
You code "should" be able to improved by using U+200b (Zero Width Space) instead of the normal U+0020 space, and thus avoid the display artifacts. BUT, the line breaking code doesn't seem to handle anything other than U+0020, so none of the space characters from U+2000 - U+2060 are handled correctly.
Will create a separate issue for that.
You code "should" be able to improved by using U+200b (Zero Width Space) instead of the normal U+0020 space, and thus avoid the display artifacts. BUT, the line breaking code doesn't seem to handle anything other than U+0020, so none of the space characters from U+2000 - U+2060 are handled correctly.
Will create a separate issue for that.
zv...@gmail.com <zv...@gmail.com> #14
[Comment deleted]
ma...@gmail.com <ma...@gmail.com> #16
Does anyone know if this has been fixed in 4.1.2?
pl...@gmail.com <pl...@gmail.com> #19
Right, that looks like it is in 4.1.2 (https://github.com/android/platform_frameworks_base/blob/android-4.1.2_r1/core/java/android/text/MeasuredText.java ) - strange that they'd consider the failure to be in MeasuredText ignoring the offset rather than StaticLayout, but there you go. Will confirm on a device build as soon as they release one.
pl...@gmail.com <pl...@gmail.com> #20
ma...@gmail.com <ma...@gmail.com> #21
I tried to implement a reliable test to check if a device is affected by this bug (testing for API level 16 is not good enough because 4.1.2 is still API 16).
This is what I came up with so far:
TextView tv = new TextView(getApplicationContext());
tv.setText(Html.fromHtml("crash<br/><br/>test <i>dummy</i>."));
tv.setTypeface(Typeface.SANS_SERIF);
tv.setPadding(0, 0, 0, 0);
tv.setTextSize(10);
tv.setMaxLines(100);
tv.setMaxWidth(500);
tv.measure(MeasureSpec.makeMeasureSpec(107, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(1000, MeasureSpec.AT_MOST));
It crashes on my Galaxy Nexus but I'm not sure if it is reliable enough.
Does anyone have a better idea?
This is what I came up with so far:
TextView tv = new TextView(getApplicationContext());
tv.setText(Html.fromHtml("crash<br/><br/>test <i>dummy</i>."));
tv.setTypeface(Typeface.SANS_SERIF);
tv.setPadding(0, 0, 0, 0);
tv.setTextSize(10);
tv.setMaxLines(100);
tv.setMaxWidth(500);
tv.measure(MeasureSpec.makeMeasureSpec(107, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(1000, MeasureSpec.AT_MOST));
It crashes on my Galaxy Nexus but I'm not sure if it is reliable enough.
Does anyone have a better idea?
pl...@gmail.com <pl...@gmail.com> #22
I don't think this would be guaranteed to set off the crash since the crash required that the affected range overlap a line break - are you sure the crash was an (Array/)IndexOutOfBoundsException like the original one?
Why not just check for the OS version? Build.VERSION has other fields too, if you just check Build.VERSION.RELEASE and mark the system as bugged if it's "4.1" or "4.1.1" I believe that would cover everyone except a few people with internal Google beta builds who are probably already on 4.1.2 now anyway.
Or, since we know this bug is never going to be on more than a small share of devices (assuming most of the devices that get 4.1 upgrades now get 4.1.2), you could simply deploy a lower-impact fix like a TextView subclass that catches these exceptions and removes all spans (see post #12 here) - that way, on a device without the bug the exception is (hopefully) never thrown and so every field keeps its formatting.
Why not just check for the OS version? Build.VERSION has other fields too, if you just check Build.VERSION.RELEASE and mark the system as bugged if it's "4.1" or "4.1.1" I believe that would cover everyone except a few people with internal Google beta builds who are probably already on 4.1.2 now anyway.
Or, since we know this bug is never going to be on more than a small share of devices (assuming most of the devices that get 4.1 upgrades now get 4.1.2), you could simply deploy a lower-impact fix like a TextView subclass that catches these exceptions and removes all spans (see post #12 here) - that way, on a device without the bug the exception is (hopefully) never thrown and so every field keeps its formatting.
go...@umito.nl <go...@umito.nl> #23
My app triggered this all over the place, but in 4.2 it looks like its fixed (emulator)
la...@gmail.com <la...@gmail.com> #24
Here's some more details on the bug and how to work around it. I've attempted accuracy but please call out any mistakes.
Crash Manifestation:
When Android is rendering long lines of text to the screen, it needs to figure out where to perform a line-wrap. A crash will occur if all of the following are true:
1. The text contains spans that are MetricAffectingSpans (ex: StyleSpan is one such subclass). Other types of spans do not invoke the crash (ex: URLSpan).
2. There is a word where a MetricAffectingSpans starts or stops in the middle of that word (ex: the first half of a word is bolded while the second half is not).
3. A line-wrap is needed in the non-spanned part of the word.
4. The code is running on Android 4.1 or 4.1.1.
What makes this bug show up on some devices and not others depends on several factors such as screen pixel dimensions, dpi, font size, the text and spans themselves, and others.
This shows up both in custom layouts and in simple dialogs (ex: dialog's built by AlertDialogBuilder).
So, what exactly is meant by a "word" in this case? A word is a sequence of characters that can not be split across a line-wrap. So then, where can line-wraps occurs? It has already been pointed out that spaces allow line-wraps to occur. However, there are other characters that also allow line-wraps.
1. Spaces, Tabs, and Newline characters always allow line-wraps (i.e. ' ', '\t', and '\n').
2. The characters '.' ',' ';' and ':' allow line-wraps IF there is not a digit immedidately before or after them (as defined by Character.isDigit()). When these 4 characters separate two words, these characters are considered part of the first of the two words.
3. The characters '/' and '-' allow line-wraps IF there is not a digit immediately after them (as defined by Character.isDigit()). As above, when these 2 characters separate two words, these characters are considered part of the first of the two words.
4. Ideographs allow line-wraps if they are adjacent, except for non-starters while only wrap after the non-starter. See Android's 4.1.1 StaticLayout.java file for details.
Here are some example strings that were put through textView.setText( Html.fromHtml(TEXT) ):
"hello <b>world</b>" Safe
"hello.<b>world</b>" Safe
"hello.<b>1orld</b>" May crash
"hell7.<b>world</b>" May crash
"hello-<b>world</b>" Safe
"hello-<b>1orld</b>" May crash
"<b>hello</b> world" Safe
"<b>hello.</b>world" Safe
"<b>hello</b>.world" May crash
"<b>hell7.</b>world" May crash
"<b>hello.</b>1orld" May crash
"<b>hello-</b>world" Safe
"<b>hello</b>-world" May crash
"<b>hell7-</b>world" Safe
"<b>hello-</b>1orld" May crash
The crashes are limited to any span that is or derived from MetricAffectingSpans. This means that the following spans are SAFE to use because they are not derived from MetricAffectingSpans:
MaskFilterSpan, RasterizerSpan, clickableSpan, URLSpan, BackgroundColorSpan, ForegroundColorSpan, StrikethroughSpan, SuggestionSpan, UnderlineSpan
There are two ways for detecting if the crash can even occur:
1. Look for Build.VERSION to be equal to "4.1" or "4.1.1". This check is easy, but your code might implement a work around on such device's where the crash wouldn't show up (ex: screen size meant that line-wraps didn't occur in any of the bad places).
2. Wrap the call to setText() for the View with a try{ ... } catch( IndexOutOfBoundsException e ){ ... }. Do this either around the call to setText or create a subclass that overrides setText() and calls super.setText().
Work-arounds if detected (from simplist to complex):
0. If you have complete control over the text, re-write the text to fit the "characters that allow line-wraps" rules listed above.
1. Remove all the spans from the text (ex: text.toString()). This is easy but it removes ALL spans.
2. Remove just the MetricAffectingSpans. Call text.getSpans(0, text.length(), MetricAffectingSpans.class) and then text.removeSpan() for each one returned.
3. Find all the MetricAffectingSpans and detect whether each span has a space/tab/newline before and after them. If not, then insert a space/tab/newline before and/or after each span.
4. Find all the MetricAffectingSpans and do the following:
If the span is a StyleSpan for bold, replace the span with a MaskFilterSpan(
new BlurMaskFilter((float) 0.5, BlurMaskFilter.Blur.SOLID ) )
If the span is a StyleSpan for italic, make the so-so replacement with ForegroundColorSpan(0xFF808080)
If the span is something else, remove the span entirely.
5. Find all the MetricAffectingSpans and detect whether each span allows a line-wrap immediately before and after them according to the "characters that allow line-wraps" rules listed above. Modify the string accordingly.
6. Other work-arounds are possible.
Crash Manifestation:
When Android is rendering long lines of text to the screen, it needs to figure out where to perform a line-wrap. A crash will occur if all of the following are true:
1. The text contains spans that are MetricAffectingSpans (ex: StyleSpan is one such subclass). Other types of spans do not invoke the crash (ex: URLSpan).
2. There is a word where a MetricAffectingSpans starts or stops in the middle of that word (ex: the first half of a word is bolded while the second half is not).
3. A line-wrap is needed in the non-spanned part of the word.
4. The code is running on Android 4.1 or 4.1.1.
What makes this bug show up on some devices and not others depends on several factors such as screen pixel dimensions, dpi, font size, the text and spans themselves, and others.
This shows up both in custom layouts and in simple dialogs (ex: dialog's built by AlertDialogBuilder).
So, what exactly is meant by a "word" in this case? A word is a sequence of characters that can not be split across a line-wrap. So then, where can line-wraps occurs? It has already been pointed out that spaces allow line-wraps to occur. However, there are other characters that also allow line-wraps.
1. Spaces, Tabs, and Newline characters always allow line-wraps (i.e. ' ', '\t', and '\n').
2. The characters '.' ',' ';' and ':' allow line-wraps IF there is not a digit immedidately before or after them (as defined by Character.isDigit()). When these 4 characters separate two words, these characters are considered part of the first of the two words.
3. The characters '/' and '-' allow line-wraps IF there is not a digit immediately after them (as defined by Character.isDigit()). As above, when these 2 characters separate two words, these characters are considered part of the first of the two words.
4. Ideographs allow line-wraps if they are adjacent, except for non-starters while only wrap after the non-starter. See Android's 4.1.1 StaticLayout.java file for details.
Here are some example strings that were put through textView.setText( Html.fromHtml(TEXT) ):
"hello <b>world</b>" Safe
"hello.<b>world</b>" Safe
"hello.<b>1orld</b>" May crash
"hell7.<b>world</b>" May crash
"hello-<b>world</b>" Safe
"hello-<b>1orld</b>" May crash
"<b>hello</b> world" Safe
"<b>hello.</b>world" Safe
"<b>hello</b>.world" May crash
"<b>hell7.</b>world" May crash
"<b>hello.</b>1orld" May crash
"<b>hello-</b>world" Safe
"<b>hello</b>-world" May crash
"<b>hell7-</b>world" Safe
"<b>hello-</b>1orld" May crash
The crashes are limited to any span that is or derived from MetricAffectingSpans. This means that the following spans are SAFE to use because they are not derived from MetricAffectingSpans:
MaskFilterSpan, RasterizerSpan, clickableSpan, URLSpan, BackgroundColorSpan, ForegroundColorSpan, StrikethroughSpan, SuggestionSpan, UnderlineSpan
There are two ways for detecting if the crash can even occur:
1. Look for Build.VERSION to be equal to "4.1" or "4.1.1". This check is easy, but your code might implement a work around on such device's where the crash wouldn't show up (ex: screen size meant that line-wraps didn't occur in any of the bad places).
2. Wrap the call to setText() for the View with a try{ ... } catch( IndexOutOfBoundsException e ){ ... }. Do this either around the call to setText or create a subclass that overrides setText() and calls super.setText().
Work-arounds if detected (from simplist to complex):
0. If you have complete control over the text, re-write the text to fit the "characters that allow line-wraps" rules listed above.
1. Remove all the spans from the text (ex: text.toString()). This is easy but it removes ALL spans.
2. Remove just the MetricAffectingSpans. Call text.getSpans(0, text.length(), MetricAffectingSpans.class) and then text.removeSpan() for each one returned.
3. Find all the MetricAffectingSpans and detect whether each span has a space/tab/newline before and after them. If not, then insert a space/tab/newline before and/or after each span.
4. Find all the MetricAffectingSpans and do the following:
If the span is a StyleSpan for bold, replace the span with a MaskFilterSpan(
new BlurMaskFilter((float) 0.5, BlurMaskFilter.Blur.SOLID ) )
If the span is a StyleSpan for italic, make the so-so replacement with ForegroundColorSpan(0xFF808080)
If the span is something else, remove the span entirely.
5. Find all the MetricAffectingSpans and detect whether each span allows a line-wrap immediately before and after them according to the "characters that allow line-wraps" rules listed above. Modify the string accordingly.
6. Other work-arounds are possible.
de...@gmail.com <de...@gmail.com> #25
Great, thanks for the workaround! Nice to know that I can keep my text colors!
ru...@gmail.com <ru...@gmail.com> #26
Thanks for the comment #4 ! It works really fine and I'm not wasting my time any longer..
[Deleted User] <[Deleted User]> #27
Occurred on 6.0.1 also. Issue still persists.
Description
• Create a spannable string with multiple paragraphs.
• In a paragraph other than the first one, add a styling span that starts within a word such that the line will be broken to the start of the word.
An IndexOutOfBoundsException will be thrown with the following backtrace:
E/AndroidRuntime( 3070): java.lang.IndexOutOfBoundsException
E/AndroidRuntime( 3070): at android.graphics.Paint.getTextRunAdvances(Paint.java:1731)
E/AndroidRuntime( 3070): at android.graphics.Paint.getTextRunAdvances(Paint.java:1704)
E/AndroidRuntime( 3070): at android.text.MeasuredText.addStyleRun(MeasuredText.java:164)
E/AndroidRuntime( 3070): at android.text.MeasuredText.addStyleRun(MeasuredText.java:204)
E/AndroidRuntime( 3070): at android.text.StaticLayout.generate(StaticLayout.java:281)
...
I think the offending bit of code is this section in StaticLayout.java:
if (here < spanStart) {
// The text was cut before the beginning of the current span range.
// Exit the span loop, and get spanStart to start over from here.
measured.setPos(here);
spanEnd = here;
break;
}
The 'here' variable is an offset from the start of the string. The MeasuredText.setPos method just sets the mPos member of the object. Within MeasuredText mPos is treated as an offset from the start of the current paragraph. MeasuredText has a char[] array that just contains the subsection of the text for the current paragraph. Thus when the loop is restarted addSpan will be called and it will try to index the char[] array using mPos which will be way off the end of the array.
Maybe it should be measured.setPos(here - paraStart) instead?
I'll attach an example Activity which replicates the problem. The example depends on the size of the display which in my case is WVGA800.