diff options
author | Doug Felt <dougfelt@google.com> | 2011-07-07 11:57:48 -0700 |
---|---|---|
committer | Doug Felt <dougfelt@google.com> | 2011-07-14 11:24:33 -0700 |
commit | cb379120456d8065d742021fc5c66748fc8a11a8 (patch) | |
tree | 980cb4378ded06d096f8606073a2f32245f6df87 | |
parent | ad3f935ce9f3308edc62d56a0059e0761c720077 (diff) | |
download | frameworks_base-cb379120456d8065d742021fc5c66748fc8a11a8.zip frameworks_base-cb379120456d8065d742021fc5c66748fc8a11a8.tar.gz frameworks_base-cb379120456d8065d742021fc5c66748fc8a11a8.tar.bz2 |
Implement textDirection heuristic selection.
Change-Id: I2fcf18de573f2d66494fa5ed61e4273c3c6078c7
-rw-r--r-- | core/java/android/text/BoringLayout.java | 28 | ||||
-rw-r--r-- | core/java/android/text/DynamicLayout.java | 31 | ||||
-rw-r--r-- | core/java/android/text/Layout.java | 39 | ||||
-rw-r--r-- | core/java/android/text/MeasuredText.java | 20 | ||||
-rw-r--r-- | core/java/android/text/StaticLayout.java | 53 | ||||
-rw-r--r-- | core/java/android/text/TextDirectionHeuristic.java | 13 | ||||
-rw-r--r-- | core/java/android/text/TextDirectionHeuristics.java | 310 | ||||
-rw-r--r-- | core/java/android/text/TextUtils.java | 55 | ||||
-rw-r--r-- | core/java/android/view/View.java | 75 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 32 | ||||
-rw-r--r-- | core/java/android/widget/TextView.java | 210 | ||||
-rw-r--r-- | core/tests/coretests/src/android/widget/TextViewTest.java | 10 | ||||
-rw-r--r-- | tests/BiDiTests/res/layout/textview_direction_ltr.xml | 305 | ||||
-rw-r--r-- | tests/BiDiTests/res/layout/textview_direction_rtl.xml | 305 | ||||
-rw-r--r-- | tests/BiDiTests/res/values/strings.xml | 2 |
15 files changed, 1053 insertions, 435 deletions
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java index 757a8c3..5a244f1 100644 --- a/core/java/android/text/BoringLayout.java +++ b/core/java/android/text/BoringLayout.java @@ -226,7 +226,17 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback */ public static Metrics isBoring(CharSequence text, TextPaint paint) { - return isBoring(text, paint, null); + return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null); + } + + /** + * Returns null if not boring; the width, ascent, and descent if boring. + * @hide + */ + public static Metrics isBoring(CharSequence text, + TextPaint paint, + TextDirectionHeuristic textDir) { + return isBoring(text, paint, textDir, null); } /** @@ -235,6 +245,17 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback * if boring. */ public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) { + return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics); + } + + /** + * Returns null if not boring; the width, ascent, and descent in the + * provided Metrics object (or a new one if the provided one was null) + * if boring. + * @hide + */ + public static Metrics isBoring(CharSequence text, TextPaint paint, + TextDirectionHeuristic textDir, Metrics metrics) { char[] temp = TextUtils.obtain(500); int length = text.length(); boolean boring = true; @@ -258,6 +279,11 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback break outer; } } + + if (textDir.isRtl(temp, 0, n)) { + boring = false; + break outer; + } } TextUtils.recycle(temp); diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index f196b34..cb96969 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -75,12 +75,31 @@ extends Layout float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { - super((ellipsize == null) - ? display - : (display instanceof Spanned) - ? new SpannedEllipsizer(display) + this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR, + spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth); + } + + /** + * Make a layout for the transformed text (password transformation + * being the primary example of a transformation) + * that will be updated as the base text is changed. + * If ellipsize is non-null, the Layout will ellipsize the text + * down to ellipsizedWidth. + * * + * *@hide + */ + public DynamicLayout(CharSequence base, CharSequence display, + TextPaint paint, + int width, Alignment align, TextDirectionHeuristic textDir, + float spacingmult, float spacingadd, + boolean includepad, + TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + super((ellipsize == null) + ? display + : (display instanceof Spanned) + ? new SpannedEllipsizer(display) : new Ellipsizer(display), - paint, width, align, spacingmult, spacingadd); + paint, width, align, textDir, spacingmult, spacingadd); mBase = base; mDisplay = display; @@ -259,7 +278,7 @@ extends Layout reflowed = new StaticLayout(true); reflowed.generate(text, where, where + after, - getPaint(), getWidth(), getAlignment(), + getPaint(), getWidth(), getAlignment(), getTextDirectionHeuristic(), getSpacingMultiplier(), getSpacingAdd(), false, true, mEllipsizedWidth, mEllipsizeAt); int n = reflowed.getLineCount(); diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index aae9ccf..eabeef0 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -16,8 +16,6 @@ package android.text; -import com.android.internal.util.ArrayUtils; - import android.emoji.EmojiFactory; import android.graphics.Canvas; import android.graphics.Paint; @@ -32,6 +30,8 @@ import android.text.style.ParagraphStyle; import android.text.style.ReplacementSpan; import android.text.style.TabStopSpan; +import com.android.internal.util.ArrayUtils; + import java.util.Arrays; /** @@ -113,6 +113,29 @@ public abstract class Layout { protected Layout(CharSequence text, TextPaint paint, int width, Alignment align, float spacingMult, float spacingAdd) { + this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR, + spacingMult, spacingAdd); + } + + /** + * Subclasses of Layout use this constructor to set the display text, + * width, and other standard properties. + * @param text the text to render + * @param paint the default paint for the layout. Styles can override + * various attributes of the paint. + * @param width the wrapping width for the text. + * @param align whether to left, right, or center the text. Styles can + * override the alignment. + * @param spacingMult factor by which to scale the font size to get the + * default line spacing + * @param spacingAdd amount to add to the default line spacing + * + * @hide + */ + protected Layout(CharSequence text, TextPaint paint, + int width, Alignment align, TextDirectionHeuristic textDir, + float spacingMult, float spacingAdd) { + if (width < 0) throw new IllegalArgumentException("Layout: " + width + " < 0"); @@ -133,6 +156,7 @@ public abstract class Layout { mSpacingMult = spacingMult; mSpacingAdd = spacingAdd; mSpannedText = text instanceof Spanned; + mTextDir = textDir; } /** @@ -531,6 +555,14 @@ public abstract class Layout { } /** + * Return the heuristic used to determine paragraph text direction. + * @hide + */ + public final TextDirectionHeuristic getTextDirectionHeuristic() { + return mTextDir; + } + + /** * Return the number of lines of text in this layout. */ public abstract int getLineCount(); @@ -1419,7 +1451,7 @@ public abstract class Layout { MeasuredText mt = MeasuredText.obtain(); TextLine tl = TextLine.obtain(); try { - mt.setPara(text, start, end, DIR_REQUEST_LTR); + mt.setPara(text, start, end, TextDirectionHeuristics.LTR); Directions directions; int dir; if (mt.mEasy) { @@ -1769,6 +1801,7 @@ public abstract class Layout { private float mSpacingAdd; private static final Rect sTempRect = new Rect(); private boolean mSpannedText; + private TextDirectionHeuristic mTextDir; public static final int DIR_LEFT_TO_RIGHT = 1; public static final int DIR_RIGHT_TO_LEFT = -1; diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index a81be09..2920ac5 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -85,7 +85,7 @@ class MeasuredText { * Analyzes text for bidirectional runs. Allocates working buffers. */ /* package */ - void setPara(CharSequence text, int start, int end, int bidiRequest) { + void setPara(CharSequence text, int start, int end, TextDirectionHeuristic textDir) { mText = text; mTextStart = start; @@ -115,13 +115,29 @@ class MeasuredText { } } - if (TextUtils.doesNotNeedBidi(mChars, 0, len)) { + if ((textDir == TextDirectionHeuristics.LTR || + textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR || + textDir == TextDirectionHeuristics.ANYRTL_LTR) && + TextUtils.doesNotNeedBidi(mChars, 0, len)) { mDir = Layout.DIR_LEFT_TO_RIGHT; mEasy = true; } else { if (mLevels == null || mLevels.length < len) { mLevels = new byte[ArrayUtils.idealByteArraySize(len)]; } + int bidiRequest; + if (textDir == TextDirectionHeuristics.LTR) { + bidiRequest = Layout.DIR_REQUEST_LTR; + } else if (textDir == TextDirectionHeuristics.RTL) { + bidiRequest = Layout.DIR_REQUEST_RTL; + } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) { + bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR; + } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) { + bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL; + } else { + boolean isRtl = textDir.isRtl(mChars, 0, len); + bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR; + } mDir = AndroidBidi.bidi(bidiRequest, mChars, mLevels, len, false); mEasy = false; } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 9e48eff..f7b9502 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -16,8 +16,6 @@ package android.text; -import com.android.internal.util.ArrayUtils; - import android.graphics.Bitmap; import android.graphics.Paint; import android.text.style.LeadingMarginSpan; @@ -26,6 +24,8 @@ import android.text.style.LineHeightSpan; import android.text.style.MetricAffectingSpan; import android.text.style.TabStopSpan; +import com.android.internal.util.ArrayUtils; + /** * StaticLayout is a Layout for text that will not be edited after it * is laid out. Use {@link DynamicLayout} for text that may change. @@ -46,6 +46,17 @@ public class StaticLayout extends Layout { spacingmult, spacingadd, includepad); } + /** + * @hide + */ + public StaticLayout(CharSequence source, TextPaint paint, + int width, Alignment align, TextDirectionHeuristic textDir, + float spacingmult, float spacingadd, + boolean includepad) { + this(source, 0, source.length(), paint, width, align, textDir, + spacingmult, spacingadd, includepad); + } + public StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, Alignment align, @@ -55,9 +66,35 @@ public class StaticLayout extends Layout { spacingmult, spacingadd, includepad, null, 0); } + /** + * @hide + */ + public StaticLayout(CharSequence source, int bufstart, int bufend, + TextPaint paint, int outerwidth, + Alignment align, TextDirectionHeuristic textDir, + float spacingmult, float spacingadd, + boolean includepad) { + this(source, bufstart, bufend, paint, outerwidth, align, textDir, + spacingmult, spacingadd, includepad, null, 0); +} + + public StaticLayout(CharSequence source, int bufstart, int bufend, + TextPaint paint, int outerwidth, + Alignment align, + float spacingmult, float spacingadd, + boolean includepad, + TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { + this(source, bufstart, bufend, paint, outerwidth, align, + TextDirectionHeuristics.FIRSTSTRONG_LTR, + spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth); + } + + /** + * @hide + */ public StaticLayout(CharSequence source, int bufstart, int bufend, TextPaint paint, int outerwidth, - Alignment align, + Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, boolean includepad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) { @@ -66,7 +103,7 @@ public class StaticLayout extends Layout { : (source instanceof Spanned) ? new SpannedEllipsizer(source) : new Ellipsizer(source), - paint, outerwidth, align, spacingmult, spacingadd); + paint, outerwidth, align, textDir, spacingmult, spacingadd); /* * This is annoying, but we can't refer to the layout until @@ -96,7 +133,7 @@ public class StaticLayout extends Layout { mMeasured = MeasuredText.obtain(); - generate(source, bufstart, bufend, paint, outerwidth, align, + generate(source, bufstart, bufend, paint, outerwidth, align, textDir, spacingmult, spacingadd, includepad, includepad, ellipsizedWidth, ellipsize); @@ -116,7 +153,7 @@ public class StaticLayout extends Layout { /* package */ void generate(CharSequence source, int bufStart, int bufEnd, TextPaint paint, int outerWidth, - Alignment align, + Alignment align, TextDirectionHeuristic textDir, float spacingmult, float spacingadd, boolean includepad, boolean trackpad, float ellipsizedWidth, TextUtils.TruncateAt ellipsize) { @@ -157,7 +194,7 @@ public class StaticLayout extends Layout { LeadingMarginSpan lms = sp[i]; firstWidth -= sp[i].getLeadingMargin(true); restWidth -= sp[i].getLeadingMargin(false); - + // LeadingMarginSpan2 is odd. The count affects all // leading margin spans, not just this particular one, // and start from the top of the span, not the top of the @@ -195,7 +232,7 @@ public class StaticLayout extends Layout { } } - measured.setPara(source, paraStart, paraEnd, DIR_REQUEST_DEFAULT_LTR); + measured.setPara(source, paraStart, paraEnd, textDir); char[] chs = measured.mChars; float[] widths = measured.mWidths; byte[] chdirs = measured.mLevels; diff --git a/core/java/android/text/TextDirectionHeuristic.java b/core/java/android/text/TextDirectionHeuristic.java new file mode 100644 index 0000000..130f879 --- /dev/null +++ b/core/java/android/text/TextDirectionHeuristic.java @@ -0,0 +1,13 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package android.text; + +/** + * Interface for objects that guess at the paragraph direction by examining text. + * + * @hide + */ +public interface TextDirectionHeuristic { + /** @hide */ boolean isRtl(CharSequence text, int start, int end); + /** @hide */ boolean isRtl(char[] text, int start, int count); +} diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java new file mode 100644 index 0000000..5f9ffc5 --- /dev/null +++ b/core/java/android/text/TextDirectionHeuristics.java @@ -0,0 +1,310 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package android.text; + + +/** + * Some objects that implement TextDirectionHeuristic. + * @hide + */ +public class TextDirectionHeuristics { + + /** Always decides that the direction is left to right. */ + public static final TextDirectionHeuristic LTR = + new TextDirectionHeuristicInternal(null /* no algorithm */, false); + + /** Always decides that the direction is right to left. */ + public static final TextDirectionHeuristic RTL = + new TextDirectionHeuristicInternal(null /* no algorithm */, true); + + /** + * Determines the direction based on the first strong directional character, + * including bidi format chars, falling back to left to right if it + * finds none. This is the default behavior of the Unicode Bidirectional + * Algorithm. + */ + public static final TextDirectionHeuristic FIRSTSTRONG_LTR = + new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false); + + /** + * Determines the direction based on the first strong directional character, + * including bidi format chars, falling back to right to left if it + * finds none. This is similar to the default behavior of the Unicode + * Bidirectional Algorithm, just with different fallback behavior. + */ + public static final TextDirectionHeuristic FIRSTSTRONG_RTL = + new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true); + + /** + * If the text contains any strong right to left non-format character, determines + * that the direction is right to left, falling back to left to right if it + * finds none. + */ + public static final TextDirectionHeuristic ANYRTL_LTR = + new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false); + + /** + * If the text contains any strong left to right non-format character, determines + * that the direction is left to right, falling back to right to left if it + * finds none. + */ + public static final TextDirectionHeuristic ANYLTR_RTL = + new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_LTR, true); + + /** + * Examines only the strong directional non-format characters, and if either + * left to right or right to left characters are 60% or more of this total, + * determines that the direction follows the majority of characters. Falls + * back to left to right if neither direction meets this threshold. + */ + public static final TextDirectionHeuristic CHARCOUNT_LTR = + new TextDirectionHeuristicInternal(CharCount.INSTANCE_DEFAULT, false); + + /** + * Examines only the strong directional non-format characters, and if either + * left to right or right to left characters are 60% or more of this total, + * determines that the direction follows the majority of characters. Falls + * back to right to left if neither direction meets this threshold. + */ + public static final TextDirectionHeuristic CHARCOUNT_RTL = + new TextDirectionHeuristicInternal(CharCount.INSTANCE_DEFAULT, true); + + private static enum TriState { + TRUE, FALSE, UNKNOWN; + } + + /** + * Computes the text direction based on an algorithm. Subclasses implement + * {@link #defaultIsRtl} to handle cases where the algorithm cannot determine the + * direction from the text alone. + * @hide + */ + public static abstract class TextDirectionHeuristicImpl implements TextDirectionHeuristic { + private final TextDirectionAlgorithm mAlgorithm; + + public TextDirectionHeuristicImpl(TextDirectionAlgorithm algorithm) { + mAlgorithm = algorithm; + } + + /** + * Return true if the default text direction is rtl. + */ + abstract protected boolean defaultIsRtl(); + + @Override + public boolean isRtl(CharSequence text, int start, int end) { + if (text == null || start < 0 || end < start || text.length() < end) { + throw new IllegalArgumentException(); + } + if (mAlgorithm == null) { + return defaultIsRtl(); + } + text = text.subSequence(start, end); + char[] chars = text.toString().toCharArray(); + return doCheck(chars, 0, chars.length); + } + + @Override + public boolean isRtl(char[] chars, int start, int count) { + if (chars == null || start < 0 || count < 0 || chars.length - count < start) { + throw new IllegalArgumentException(); + } + if (mAlgorithm == null) { + return defaultIsRtl(); + } + return doCheck(chars, start, count); + } + + private boolean doCheck(char[] chars, int start, int count) { + switch(mAlgorithm.checkRtl(chars, start, count)) { + case TRUE: + return true; + case FALSE: + return false; + default: + return defaultIsRtl(); + } + } + } + + private static class TextDirectionHeuristicInternal extends TextDirectionHeuristicImpl { + private final boolean mDefaultIsRtl; + + private TextDirectionHeuristicInternal(TextDirectionAlgorithm algorithm, + boolean defaultIsRtl) { + super(algorithm); + mDefaultIsRtl = defaultIsRtl; + } + + @Override + protected boolean defaultIsRtl() { + return mDefaultIsRtl; + } + } + + private static TriState isRtlText(int directionality) { + switch (directionality) { + case Character.DIRECTIONALITY_LEFT_TO_RIGHT: + return TriState.FALSE; + case Character.DIRECTIONALITY_RIGHT_TO_LEFT: + case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: + return TriState.TRUE; + default: + return TriState.UNKNOWN; + } + } + + private static TriState isRtlTextOrFormat(int directionality) { + switch (directionality) { + case Character.DIRECTIONALITY_LEFT_TO_RIGHT: + case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING: + case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE: + return TriState.FALSE; + case Character.DIRECTIONALITY_RIGHT_TO_LEFT: + case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC: + case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING: + case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE: + return TriState.TRUE; + default: + return TriState.UNKNOWN; + } + } + + /** + * Interface for an algorithm to guess the direction of a paragraph of text. + * + * @hide + */ + public static interface TextDirectionAlgorithm { + /** + * Returns whether the range of text is RTL according to the algorithm. + * + * @hide + */ + TriState checkRtl(char[] text, int start, int count); + } + + /** + * Algorithm that uses the first strong directional character to determine + * the paragraph direction. This is the standard Unicode Bidirectional + * algorithm. + * + * @hide + */ + public static class FirstStrong implements TextDirectionAlgorithm { + @Override + public TriState checkRtl(char[] text, int start, int count) { + TriState result = TriState.UNKNOWN; + for (int i = start, e = start + count; i < e && result == TriState.UNKNOWN; ++i) { + result = isRtlTextOrFormat(Character.getDirectionality(text[i])); + } + return result; + } + + private FirstStrong() { + } + + public static final FirstStrong INSTANCE = new FirstStrong(); + } + + /** + * Algorithm that uses the presence of any strong directional non-format + * character (e.g. excludes LRE, LRO, RLE, RLO) to determine the + * direction of text. + * + * @hide + */ + public static class AnyStrong implements TextDirectionAlgorithm { + private final boolean mLookForRtl; + + @Override + public TriState checkRtl(char[] text, int start, int count) { + boolean haveUnlookedFor = false; + for (int i = start, e = start + count; i < e; ++i) { + switch (isRtlText(Character.getDirectionality(text[i]))) { + case TRUE: + if (mLookForRtl) { + return TriState.TRUE; + } + haveUnlookedFor = true; + break; + case FALSE: + if (!mLookForRtl) { + return TriState.FALSE; + } + haveUnlookedFor = true; + break; + default: + break; + } + } + if (haveUnlookedFor) { + return mLookForRtl ? TriState.FALSE : TriState.TRUE; + } + return TriState.UNKNOWN; + } + + private AnyStrong(boolean lookForRtl) { + this.mLookForRtl = lookForRtl; + } + + public static final AnyStrong INSTANCE_RTL = new AnyStrong(true); + public static final AnyStrong INSTANCE_LTR = new AnyStrong(false); + } + + /** + * Algorithm that uses the relative proportion of strong directional + * characters (excluding LRE, LRO, RLE, RLO) to determine the direction + * of the paragraph, if the proportion exceeds a given threshold. + * + * @hide + */ + public static class CharCount implements TextDirectionAlgorithm { + private final float mThreshold; + + @Override + public TriState checkRtl(char[] text, int start, int count) { + int countLtr = 0; + int countRtl = 0; + for(int i = start, e = start + count; i < e; ++i) { + switch (isRtlText(Character.getDirectionality(text[i]))) { + case TRUE: + ++countLtr; + break; + case FALSE: + ++countRtl; + break; + default: + break; + } + } + int limit = (int)((countLtr + countRtl) * mThreshold); + if (limit > 0) { + if (countLtr > limit) { + return TriState.FALSE; + } + if (countRtl > limit) { + return TriState.TRUE; + } + } + return TriState.UNKNOWN; + } + + private CharCount(float threshold) { + mThreshold = threshold; + } + + public static CharCount withThreshold(float threshold) { + if (threshold < 0 || threshold > 1) { + throw new IllegalArgumentException(); + } + if (threshold == DEFAULT_THRESHOLD) { + return INSTANCE_DEFAULT; + } + return new CharCount(threshold); + } + + public static final float DEFAULT_THRESHOLD = 0.6f; + public static final CharCount INSTANCE_DEFAULT = new CharCount(DEFAULT_THRESHOLD); + } +} diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 6741059..29c9853 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -16,9 +16,6 @@ package android.text; -import com.android.internal.R; -import com.android.internal.util.ArrayUtils; - import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; @@ -45,6 +42,9 @@ import android.text.style.URLSpan; import android.text.style.UnderlineSpan; import android.util.Printer; +import com.android.internal.R; +import com.android.internal.util.ArrayUtils; + import java.lang.reflect.Array; import java.util.Iterator; import java.util.regex.Pattern; @@ -1001,13 +1001,37 @@ public class TextUtils { * will be padded with zero-width spaces to preserve the original * length and offsets instead of truncating. * If <code>callback</code> is non-null, it will be called to - * report the start and end of the ellipsized range. + * report the start and end of the ellipsized range. TextDirection + * is determined by the first strong directional character. */ public static CharSequence ellipsize(CharSequence text, TextPaint paint, float avail, TruncateAt where, boolean preserveLength, EllipsizeCallback callback) { + return ellipsize(text, paint, avail, where, preserveLength, callback, + TextDirectionHeuristics.FIRSTSTRONG_LTR); + } + + /** + * Returns the original text if it fits in the specified width + * given the properties of the specified Paint, + * or, if it does not fit, a copy with ellipsis character added + * at the specified edge or center. + * If <code>preserveLength</code> is specified, the returned copy + * will be padded with zero-width spaces to preserve the original + * length and offsets instead of truncating. + * If <code>callback</code> is non-null, it will be called to + * report the start and end of the ellipsized range. + * + * @hide + */ + public static CharSequence ellipsize(CharSequence text, + TextPaint paint, + float avail, TruncateAt where, + boolean preserveLength, + EllipsizeCallback callback, + TextDirectionHeuristic textDir) { if (sEllipsis == null) { Resources r = Resources.getSystem(); sEllipsis = r.getString(R.string.ellipsis); @@ -1017,8 +1041,7 @@ public class TextUtils { MeasuredText mt = MeasuredText.obtain(); try { - float width = setPara(mt, paint, text, 0, text.length(), - Layout.DIR_REQUEST_DEFAULT_LTR); + float width = setPara(mt, paint, text, 0, text.length(), textDir); if (width <= avail) { if (callback != null) { @@ -1108,11 +1131,20 @@ public class TextUtils { TextPaint p, float avail, String oneMore, String more) { + return commaEllipsize(text, p, avail, oneMore, more, + TextDirectionHeuristics.FIRSTSTRONG_LTR); + } + + /** + * @hide + */ + public static CharSequence commaEllipsize(CharSequence text, TextPaint p, + float avail, String oneMore, String more, TextDirectionHeuristic textDir) { MeasuredText mt = MeasuredText.obtain(); try { int len = text.length(); - float width = setPara(mt, p, text, 0, len, Layout.DIR_REQUEST_DEFAULT_LTR); + float width = setPara(mt, p, text, 0, len, textDir); if (width <= avail) { return text; } @@ -1135,9 +1167,6 @@ public class TextUtils { int count = 0; float[] widths = mt.mWidths; - int request = mt.mDir == 1 ? Layout.DIR_REQUEST_LTR : - Layout.DIR_REQUEST_RTL; - MeasuredText tempMt = MeasuredText.obtain(); for (int i = 0; i < len; i++) { w += widths[i]; @@ -1155,7 +1184,7 @@ public class TextUtils { } // XXX this is probably ok, but need to look at it more - tempMt.setPara(format, 0, format.length(), request); + tempMt.setPara(format, 0, format.length(), textDir); float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null); if (w + moreWid <= avail) { @@ -1175,9 +1204,9 @@ public class TextUtils { } private static float setPara(MeasuredText mt, TextPaint paint, - CharSequence text, int start, int end, int bidiRequest) { + CharSequence text, int start, int end, TextDirectionHeuristic textDir) { - mt.setPara(text, start, end, bidiRequest); + mt.setPara(text, start, end, textDir); float width; Spanned sp = text instanceof Spanned ? (Spanned) text : null; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 74dc100..2f13a85 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16,13 +16,6 @@ package android.view; -import android.util.FloatProperty; -import android.util.LocaleUtil; -import android.util.Property; -import com.android.internal.R; -import com.android.internal.util.Predicate; -import com.android.internal.view.menu.MenuBuilder; - import android.content.ClipData; import android.content.Context; import android.content.res.Configuration; @@ -53,11 +46,14 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemClock; import android.util.AttributeSet; +import android.util.FloatProperty; +import android.util.LocaleUtil; import android.util.Log; import android.util.Pool; import android.util.Poolable; import android.util.PoolableManager; import android.util.Pools; +import android.util.Property; import android.util.SparseArray; import android.util.TypedValue; import android.view.ContextMenu.ContextMenuInfo; @@ -72,6 +68,10 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.ScrollBarDrawable; +import com.android.internal.R; +import com.android.internal.util.Predicate; +import com.android.internal.view.menu.MenuBuilder; + import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -2493,12 +2493,6 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit private boolean mSendingHoverAccessibilityEvents; /** - * Undefined text direction (used by resolution algorithm). - * @hide - */ - public static final int TEXT_DIRECTION_UNDEFINED = -1; - - /** * Text direction is inherited thru {@link ViewGroup} * @hide */ @@ -2507,7 +2501,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit /** * Text direction is using "first strong algorithm". The first strong directional character * determines the paragraph direction. If there is no strong directional character, the - * paragraph direction is the view’s resolved ayout direction. + * paragraph direction is the view's resolved ayout direction. * * @hide */ @@ -2516,7 +2510,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit /** * Text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains * any strong RTL character, otherwise it is LTR if it contains any strong LTR characters. - * If there are neither, the paragraph direction is the view’s resolved layout direction. + * If there are neither, the paragraph direction is the view's resolved layout direction. * * @hide */ @@ -2560,7 +2554,6 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * {@hide} */ @ViewDebug.ExportedProperty(category = "text", mapping = { - @ViewDebug.IntToString(from = TEXT_DIRECTION_UNDEFINED, to = "UNDEFINED"), @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"), @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"), @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), @@ -2568,21 +2561,25 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL") }) - protected int mTextDirection = DEFAULT_TEXT_DIRECTION; + private int mTextDirection = DEFAULT_TEXT_DIRECTION; /** - * The resolved text direction. If resolution has not yet been done or has been reset, it will - * be equal to {@link #TEXT_DIRECTION_UNDEFINED}. Otherwise it will be either {@link #TEXT_DIRECTION_LTR} - * or {@link #TEXT_DIRECTION_RTL}. + * The resolved text direction. This needs resolution if the value is + * TEXT_DIRECTION_INHERIT. The resolution matches mTextDirection if that is + * not TEXT_DIRECTION_INHERIT, otherwise resolution proceeds up the parent + * chain of the view. * * {@hide} */ @ViewDebug.ExportedProperty(category = "text", mapping = { - @ViewDebug.IntToString(from = TEXT_DIRECTION_UNDEFINED, to = "UNDEFINED"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_CHAR_COUNT, to = "CHAR_COUNT"), @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL") }) - protected int mResolvedTextDirection = TEXT_DIRECTION_UNDEFINED; + private int mResolvedTextDirection = TEXT_DIRECTION_INHERIT; /** * Consistency verifier for debugging purposes. @@ -13048,43 +13045,41 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit * * @return the resolved text direction. Return one of: * + * {@link #TEXT_DIRECTION_FIRST_STRONG} + * {@link #TEXT_DIRECTION_ANY_RTL}, + * {@link #TEXT_DIRECTION_CHAR_COUNT}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, * * @hide */ public int getResolvedTextDirection() { - if (!isResolvedTextDirection()) { + if (mResolvedTextDirection == TEXT_DIRECTION_INHERIT) { resolveTextDirection(); } return mResolvedTextDirection; } /** - * Resolve the text direction. Classes that extend View and want to do a specific text direction - * resolution will need to implement this method and set the mResolvedTextDirection to - * either TEXT_DIRECTION_LTR if direction is LTR or TEXT_DIRECTION_RTL if - * direction is RTL. + * Resolve the text direction. */ protected void resolveTextDirection() { + if (mTextDirection != TEXT_DIRECTION_INHERIT) { + mResolvedTextDirection = mTextDirection; + return; + } + if (mParent != null && mParent instanceof ViewGroup) { + mResolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection(); + return; + } + mResolvedTextDirection = TEXT_DIRECTION_FIRST_STRONG; } /** - * Return if the text direction has been resolved or not. - * - * @return true, if resolved and false if not resolved - * - * @hide - */ - public boolean isResolvedTextDirection() { - return (mResolvedTextDirection != TEXT_DIRECTION_UNDEFINED); - } - - /** - * Reset resolved text direction. Will be resolved during a call to getResolvedLayoutDirection(). + * Reset resolved text direction. Will be resolved during a call to getResolvedTextDirection(). */ protected void resetResolvedTextDirection() { - mResolvedTextDirection = TEXT_DIRECTION_UNDEFINED; + mResolvedTextDirection = TEXT_DIRECTION_INHERIT; } // diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 752fd5a..cb3e9c6 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -41,6 +41,7 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.LayoutAnimationController; import android.view.animation.Transformation; + import com.android.internal.R; import com.android.internal.util.Predicate; @@ -5012,37 +5013,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } - /** - * This method will be called during text direction resolution (text direction resolution - * inheritance) - */ - @Override - protected void resolveTextDirection() { - int resolvedTextDirection; - switch(mTextDirection) { - default: - case TEXT_DIRECTION_INHERIT: - // Try to the text direction from the parent layout - if (mParent != null && mParent instanceof ViewGroup) { - resolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection(); - } else { - // We reached the top of the View hierarchy, so set the text direction - // heuristic to "first strong" - resolvedTextDirection = TEXT_DIRECTION_FIRST_STRONG; - } - break; - // Pass down the hierarchy the following text direction values - case TEXT_DIRECTION_FIRST_STRONG: - case TEXT_DIRECTION_ANY_RTL: - case TEXT_DIRECTION_CHAR_COUNT: - case TEXT_DIRECTION_LTR: - case TEXT_DIRECTION_RTL: - resolvedTextDirection = mTextDirection; - break; - } - mResolvedTextDirection = resolvedTextDirection; - } - @Override protected void resetResolvedTextDirection() { super.resetResolvedTextDirection(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 766b520..07703d3 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -16,11 +16,6 @@ package android.widget; -import com.android.internal.util.FastMath; -import com.android.internal.widget.EditableInputConnection; - -import org.xmlpull.v1.XmlPullParserException; - import android.R; import android.content.ClipData; import android.content.ClipData.Item; @@ -64,6 +59,13 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; import android.text.StaticLayout; +import android.text.TextDirectionHeuristic; +import android.text.TextDirectionHeuristics; +import android.text.TextDirectionHeuristics.AnyStrong; +import android.text.TextDirectionHeuristics.CharCount; +import android.text.TextDirectionHeuristics.FirstStrong; +import android.text.TextDirectionHeuristics.TextDirectionAlgorithm; +import android.text.TextDirectionHeuristics.TextDirectionHeuristicImpl; import android.text.TextPaint; import android.text.TextUtils; import android.text.TextWatcher; @@ -132,6 +134,11 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.RemoteViews.RemoteView; +import com.android.internal.util.FastMath; +import com.android.internal.widget.EditableInputConnection; + +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; import java.lang.ref.WeakReference; import java.text.BreakIterator; @@ -5660,14 +5667,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Layout.Alignment alignment = getLayoutAlignment(); boolean shouldEllipsize = mEllipsize != null && mInput == null; + if (mTextDir == null) { + resolveTextDirection(); + } if (mText instanceof Spannable) { mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w, - alignment, mSpacingMult, + alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null, ellipsisWidth); } else { if (boring == UNKNOWN_BORING) { - boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); if (boring != null) { mBoring = boring; } @@ -5704,23 +5714,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else if (shouldEllipsize) { mLayout = new StaticLayout(mTransformed, 0, mTransformed.length(), - mTextPaint, w, alignment, mSpacingMult, + mTextPaint, w, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mEllipsize, ellipsisWidth); } else { mLayout = new StaticLayout(mTransformed, mTextPaint, - w, alignment, mSpacingMult, mSpacingAdd, + w, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad); } } else if (shouldEllipsize) { mLayout = new StaticLayout(mTransformed, 0, mTransformed.length(), - mTextPaint, w, alignment, mSpacingMult, + mTextPaint, w, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mEllipsize, ellipsisWidth); } else { mLayout = new StaticLayout(mTransformed, mTextPaint, - w, alignment, mSpacingMult, mSpacingAdd, + w, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad); } } @@ -5732,7 +5742,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (shouldEllipsize) hintWidth = w; if (hintBoring == UNKNOWN_BORING) { - hintBoring = BoringLayout.isBoring(mHint, mTextPaint, + hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; @@ -5770,23 +5780,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else if (shouldEllipsize) { mHintLayout = new StaticLayout(mHint, 0, mHint.length(), - mTextPaint, hintWidth, alignment, mSpacingMult, + mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mEllipsize, ellipsisWidth); } else { mHintLayout = new StaticLayout(mHint, mTextPaint, - hintWidth, alignment, mSpacingMult, mSpacingAdd, + hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad); } } else if (shouldEllipsize) { mHintLayout = new StaticLayout(mHint, 0, mHint.length(), - mTextPaint, hintWidth, alignment, mSpacingMult, + mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mEllipsize, ellipsisWidth); } else { mHintLayout = new StaticLayout(mHint, mTextPaint, - hintWidth, alignment, mSpacingMult, mSpacingAdd, + hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad); } } @@ -5887,6 +5897,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener BoringLayout.Metrics boring = UNKNOWN_BORING; BoringLayout.Metrics hintBoring = UNKNOWN_BORING; + if (mTextDir == null) { + resolveTextDirection(); + } + int des = -1; boolean fromexisting = false; @@ -5899,7 +5913,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (des < 0) { - boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); if (boring != null) { mBoring = boring; } @@ -10119,11 +10133,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mInBatchEditControllers; } + private class TextViewDirectionHeuristic extends TextDirectionHeuristicImpl { + private TextViewDirectionHeuristic(TextDirectionAlgorithm algorithm) { + super(algorithm); + } + @Override + protected boolean defaultIsRtl() { + return getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL; + } + } + /** * Resolve the text direction. * * Text direction of paragraphs in a TextView is determined using a heuristic. If the correct - * text direction cannot be determined by the heuristic, the view’s resolved layout direction + * text direction cannot be determined by the heuristic, the view's resolved layout direction * determines the direction. * * This heuristic and result is applied individually to each paragraph in a TextView, based on @@ -10132,157 +10156,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Override protected void resolveTextDirection() { - int resolvedTextDirection = TEXT_DIRECTION_UNDEFINED; - switch(mTextDirection) { + super.resolveTextDirection(); + + int textDir = getResolvedTextDirection(); + switch (textDir) { default: - case TEXT_DIRECTION_INHERIT: - // Try to the text direction from the parent layout. If not possible, then we will - // use the default layout direction to decide later - if (mParent != null && mParent instanceof ViewGroup) { - resolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection(); - } - break; case TEXT_DIRECTION_FIRST_STRONG: - resolvedTextDirection = getTextDirectionFromFirstStrong(mText); + mTextDir = new TextViewDirectionHeuristic(FirstStrong.INSTANCE); break; case TEXT_DIRECTION_ANY_RTL: - resolvedTextDirection = getTextDirectionFromAnyRtl(mText); + mTextDir = new TextViewDirectionHeuristic(AnyStrong.INSTANCE_RTL); break; case TEXT_DIRECTION_CHAR_COUNT: - resolvedTextDirection = getTextDirectionFromCharCount(mText); + mTextDir = new TextViewDirectionHeuristic(CharCount.INSTANCE_DEFAULT); break; case TEXT_DIRECTION_LTR: - resolvedTextDirection = TEXT_DIRECTION_LTR; + mTextDir = TextDirectionHeuristics.LTR; break; case TEXT_DIRECTION_RTL: - resolvedTextDirection = TEXT_DIRECTION_RTL; + mTextDir = TextDirectionHeuristics.RTL; break; } - // if we have been so far unable to get the text direction from the heuristics, then we are - // falling back using the layout direction - if (resolvedTextDirection == TEXT_DIRECTION_UNDEFINED) { - switch(getResolvedLayoutDirection()) { - default: - case LAYOUT_DIRECTION_LTR: - resolvedTextDirection = TEXT_DIRECTION_LTR; - break; - case LAYOUT_DIRECTION_RTL: - resolvedTextDirection = TEXT_DIRECTION_RTL; - break; - } - } - mResolvedTextDirection = resolvedTextDirection; - } - - /** - * Get text direction following the "first strong" heuristic. - * - * @param cs the CharSequence used to get the text direction. - * - * @return {@link #TEXT_DIRECTION_RTL} if direction it RTL, {@link #TEXT_DIRECTION_LTR} if - * direction it LTR or {@link #TEXT_DIRECTION_UNDEFINED} if direction cannot be found. - */ - private static int getTextDirectionFromFirstStrong(final CharSequence cs) { - final int length = cs.length(); - if (length == 0) { - return TEXT_DIRECTION_UNDEFINED; - } - for(int i = 0; i < length; i++) { - final char c = cs.charAt(i); - final byte dir = Character.getDirectionality(c); - if (isStrongLtrChar(dir)) { - return TEXT_DIRECTION_LTR; - } else if (isStrongRtlChar(dir)) { - return TEXT_DIRECTION_RTL; - } - } - return TEXT_DIRECTION_UNDEFINED; - } - - /** - * Get text direction following the "any RTL" heuristic. - * - * @param cs the CharSequence used to get the text direction. - * - * @return {@link #TEXT_DIRECTION_RTL} if direction it RTL, {@link #TEXT_DIRECTION_LTR} if - * direction it LTR or {@link #TEXT_DIRECTION_UNDEFINED} if direction cannot be found. - */ - private static int getTextDirectionFromAnyRtl(final CharSequence cs) { - final int length = cs.length(); - if (length == 0) { - return TEXT_DIRECTION_UNDEFINED; - } - boolean foundStrongLtr = false; - boolean foundStrongRtl = false; - for(int i = 0; i < length; i++) { - final char c = cs.charAt(i); - final byte dir = Character.getDirectionality(c); - if (isStrongLtrChar(dir)) { - foundStrongLtr = true; - } else if (isStrongRtlChar(dir)) { - foundStrongRtl = true; - break; - } - } - if (foundStrongRtl) { - return TEXT_DIRECTION_RTL; - } - if (foundStrongLtr) { - return TEXT_DIRECTION_LTR; - } - return TEXT_DIRECTION_UNDEFINED; - } - - /** - * Get text direction following the "char count" heuristic. - * - * @param cs the CharSequence used to get the text direction. - * - * @return {@link #TEXT_DIRECTION_RTL} if direction it RTL, {@link #TEXT_DIRECTION_LTR} if - * direction it LTR or {@link #TEXT_DIRECTION_UNDEFINED} if direction cannot be found. - */ - private int getTextDirectionFromCharCount(CharSequence cs) { - final int length = cs.length(); - if (length == 0) { - return TEXT_DIRECTION_UNDEFINED; - } - int countLtr = 0; - int countRtl = 0; - for(int i = 0; i < length; i++) { - final char c = cs.charAt(i); - final byte dir = Character.getDirectionality(c); - if (isStrongLtrChar(dir)) { - countLtr++; - } else if (isStrongRtlChar(dir)) { - countRtl++; - } - } - final float percentLtr = ((float) countLtr) / (countLtr + countRtl); - if (percentLtr > DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD) { - return TEXT_DIRECTION_LTR; - } - final float percentRtl = ((float) countRtl) / (countLtr + countRtl); - if (percentRtl > DEFAULT_TEXT_DIRECTION_CHAR_COUNT_THRESHOLD) { - return TEXT_DIRECTION_RTL; - } - return TEXT_DIRECTION_UNDEFINED; - } - - /** - * Return true if the char direction is corresponding to a "strong RTL char" following the - * Unicode Bidirectional Algorithm (UBA). - */ - private static boolean isStrongRtlChar(final byte dir) { - return (dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT || - dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC); - } - - /** - * Return true if the char direction is corresponding to a "strong LTR char" following the - * Unicode Bidirectional Algorithm (UBA). - */ - private static boolean isStrongLtrChar(final byte dir) { - return (dir == Character.DIRECTIONALITY_LEFT_TO_RIGHT); } @ViewDebug.ExportedProperty(category = "text") @@ -10386,6 +10280,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private BoringLayout mSavedLayout, mSavedHintLayout; + private TextDirectionHeuristic mTextDir = null; + private static final InputFilter[] NO_FILTERS = new InputFilter[0]; private InputFilter[] mFilters = NO_FILTERS; private static final Spanned EMPTY_SPANNED = new SpannedString(""); diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java index c54e4a1..7dc95db 100644 --- a/core/tests/coretests/src/android/widget/TextViewTest.java +++ b/core/tests/coretests/src/android/widget/TextViewTest.java @@ -16,13 +16,13 @@ package android.widget; -import com.android.frameworks.coretests.R; - import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.SmallTest; import android.text.GetChars; import android.view.View; +import com.android.frameworks.coretests.R; + /** * TextViewTest tests {@link TextView}. */ @@ -240,12 +240,12 @@ public class TextViewTest extends ActivityInstrumentationTestCase2<TextViewTestA getActivity().runOnUiThread(new Runnable() { public void run() { - tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG); + ll.setTextDirection(View.TEXT_DIRECTION_RTL); + tv.setTextDirection(View.TEXT_DIRECTION_INHERIT); assertEquals(View.TEXT_DIRECTION_RTL, tv.getResolvedTextDirection()); - assertEquals(true, tv.isResolvedTextDirection()); ll.removeView(tv); - assertEquals(false, tv.isResolvedTextDirection()); + assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getResolvedTextDirection()); } }); } diff --git a/tests/BiDiTests/res/layout/textview_direction_ltr.xml b/tests/BiDiTests/res/layout/textview_direction_ltr.xml index f7b7b8e..2c790ec 100644 --- a/tests/BiDiTests/res/layout/textview_direction_ltr.xml +++ b/tests/BiDiTests/res/layout/textview_direction_ltr.xml @@ -18,95 +18,232 @@ android:id="@+id/textview_direction_ltr" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:layoutDirection="ltr"> + android:layoutDirection="ltr" + android:textDirection="ltr"> - <LinearLayout android:orientation="vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textDirection="ltr"> - - <LinearLayout android:orientation="vertical" + <TableLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - android:textDirection="inherit" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - android:textDirection="firstStrong" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - android:textDirection="anyRtl" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - android:textDirection="ltr" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - android:textDirection="rtl" - /> - </LinearLayout> + <TableRow> + <TextView android:text="(unspecified)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> - <LinearLayout android:orientation="vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content"> + <TableRow> + <TextView android:text="inherit" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:textDirection="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:textDirection="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:textDirection="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - android:textDirection="inherit" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - android:textDirection="firstStrong" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - android:textDirection="anyRtl" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - android:textDirection="ltr" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - android:textDirection="rtl" - /> - </LinearLayout> + <TableRow> + <TextView android:text="firstStrong" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:textDirection="firstStrong" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:textDirection="firstStrong" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:textDirection="firstStrong" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="anyRtl" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:textDirection="anyRtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:textDirection="anyRtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:textDirection="anyRtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="ltr" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:textDirection="ltr" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:textDirection="ltr" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:textDirection="ltr" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="rtl" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:textDirection="rtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:textDirection="rtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:textDirection="rtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> - </LinearLayout> + </TableLayout> -</FrameLayout>
\ No newline at end of file +</FrameLayout> diff --git a/tests/BiDiTests/res/layout/textview_direction_rtl.xml b/tests/BiDiTests/res/layout/textview_direction_rtl.xml index 81c5411..1df100d 100644 --- a/tests/BiDiTests/res/layout/textview_direction_rtl.xml +++ b/tests/BiDiTests/res/layout/textview_direction_rtl.xml @@ -18,95 +18,232 @@ android:id="@+id/textview_direction_rtl" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:layoutDirection="rtl"> + android:layoutDirection="rtl" + android:textDirection="rtl"> - <LinearLayout android:orientation="vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textDirection="rtl"> - - <LinearLayout android:orientation="vertical" + <TableLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - android:textDirection="inherit" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - android:textDirection="firstStrong" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - android:textDirection="anyRtl" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - android:textDirection="ltr" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_text" - android:textDirection="rtl" - /> - </LinearLayout> + <TableRow> + <TextView android:text="(unspecified)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> - <LinearLayout android:orientation="vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content"> + <TableRow> + <TextView android:text="inherit" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:textDirection="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:textDirection="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:textDirection="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - android:textDirection="inherit" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - android:textDirection="firstStrong" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - android:textDirection="anyRtl" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - android:textDirection="ltr" - /> - <TextView android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:textSize="24dip" - android:text="@string/textview_hebrew_text" - android:textDirection="rtl" - /> - </LinearLayout> + <TableRow> + <TextView android:text="firstStrong" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:textDirection="firstStrong" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:textDirection="firstStrong" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:textDirection="firstStrong" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="anyRtl" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:textDirection="anyRtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:textDirection="anyRtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:textDirection="anyRtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="ltr" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:textDirection="ltr" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:textDirection="ltr" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:textDirection="ltr" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="rtl" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_hebrew_text" + android:textDirection="rtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_latin_text" + android:textDirection="rtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:textSize="24dip" + android:text="@string/textview_multiline_text" + android:textDirection="rtl" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> - </LinearLayout> + </TableLayout> -</FrameLayout>
\ No newline at end of file +</FrameLayout> diff --git a/tests/BiDiTests/res/values/strings.xml b/tests/BiDiTests/res/values/strings.xml index c0bbe94..9a486c1 100644 --- a/tests/BiDiTests/res/values/strings.xml +++ b/tests/BiDiTests/res/values/strings.xml @@ -40,6 +40,6 @@ <string name="menu_delete">Delete</string> <string name="textview_hebrew_text">םמab?!</string> <string name="textview_latin_text">abםמ?!</string> - <string name="textview_multiline_text">םמ?!\nab?!</string> + <string name="textview_multiline_text">םמ?!\nab?!\n?!</string> </resources> |