Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 22268868 authored by Fabrice Di Meglio's avatar Fabrice Di Meglio
Browse files

Add textDirection support for TextView and ViewGroup

- use ViewGroup inheritance if defined
- use different heuristics (inherit, firstStrong, anyRtl, ltr, rtl)
- add more unit tests

Change-Id: Ic1325aa7d9e4689b181e0a2d08b7dd7fb3f0dbeb
parent cf93ed0d
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -975,6 +975,7 @@ package android {
    field public static final int textColorTertiary = 16843282; // 0x1010212
    field public static final int textColorTertiaryInverse = 16843283; // 0x1010213
    field public static final int textCursorDrawable = 16843618; // 0x1010362
    field public static final int textDirection = 16843677; // 0x101039d
    field public static final int textEditNoPasteWindowLayout = 16843541; // 0x1010315
    field public static final int textEditPasteWindowLayout = 16843540; // 0x1010314
    field public static final int textEditSideNoPasteWindowLayout = 16843615; // 0x101035f
@@ -21878,6 +21879,7 @@ package android.view {
    method public boolean isHovered();
    method public boolean isInEditMode();
    method public boolean isInTouchMode();
    method protected static boolean isLayoutDirectionRtl(java.util.Locale);
    method public boolean isLayoutRequested();
    method public boolean isLongClickable();
    method public boolean isOpaque();
@@ -21963,8 +21965,10 @@ package android.view {
    method public void requestLayout();
    method public boolean requestRectangleOnScreen(android.graphics.Rect);
    method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean);
    method protected void resetResolvedTextDirection();
    method public static int resolveSize(int, int);
    method public static int resolveSizeAndState(int, int, int);
    method protected void resolveTextDirection();
    method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>);
    method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>);
    method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
@@ -22064,6 +22068,7 @@ package android.view {
    method public boolean willNotCacheDrawing();
    method public boolean willNotDraw();
    field public static android.util.Property ALPHA;
    field protected static int DEFAULT_TEXT_DIRECTION;
    field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
    field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
    field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
+167 −1
Original line number Diff line number Diff line
@@ -2492,6 +2492,84 @@ 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
     */
    public static final int TEXT_DIRECTION_INHERIT = 0;

    /**
     * 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.
     *
     * @hide
     */
    public static final int TEXT_DIRECTION_FIRST_STRONG = 1;

    /**
     * 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.
     *
     * @hide
     */
    public static final int TEXT_DIRECTION_ANY_RTL = 2;

    /**
     * Text direction is forced to LTR.
     *
     * @hide
     */
    public static final int TEXT_DIRECTION_LTR = 3;

    /**
     * Text direction is forced to RTL.
     *
     * @hide
     */
    public static final int TEXT_DIRECTION_RTL = 4;

    /**
     * Default text direction is inherited
     */
    protected static int DEFAULT_TEXT_DIRECTION = TEXT_DIRECTION_INHERIT;

    /**
     * The text direction that has been defined by {@link #setTextDirection(int)}.
     *
     * {@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_LTR, to = "LTR"),
            @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL")
    })
    protected 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}.
     *
     * {@hide}
     */
    @ViewDebug.ExportedProperty(category = "text", mapping = {
            @ViewDebug.IntToString(from = TEXT_DIRECTION_UNDEFINED, to = "UNDEFINED"),
            @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
            @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL")
    })
    protected int mResolvedTextDirection = TEXT_DIRECTION_UNDEFINED;

    /**
     * Consistency verifier for debugging purposes.
     * @hide
@@ -2865,6 +2943,9 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
                case R.styleable.View_layerType:
                    setLayerType(a.getInt(attr, LAYER_TYPE_NONE), null);
                    break;
                case R.styleable.View_textDirection:
                    mTextDirection = a.getInt(attr, DEFAULT_TEXT_DIRECTION);
                    break;
            }
        }

@@ -8951,6 +9032,8 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
        resetLayoutDirectionResolution();
        resolveLayoutDirectionIfNeeded();
        resolvePadding();
        resetResolvedTextDirection();
        resolveTextDirection();
        if (isFocused()) {
            InputMethodManager imm = InputMethodManager.peekInstance();
            imm.focusIn(this);
@@ -9051,7 +9134,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
     * @param locale Locale to check
     * @return true if a Locale is corresponding to a RTL script.
     */
    private static boolean isLayoutDirectionRtl(Locale locale) {
    protected static boolean isLayoutDirectionRtl(Locale locale) {
        return (LocaleUtil.TEXT_LAYOUT_DIRECTION_RTL_DO_NOT_USE ==
                LocaleUtil.getLayoutDirectionFromLocale(locale));
    }
@@ -12898,6 +12981,89 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit
        return getVerticalScrollFactor();
    }

    /**
     * Return the value specifying the text direction or policy that was set with
     * {@link #setTextDirection(int)}.
     *
     * @return the defined text direction. It can be one of:
     *
     * {@link #TEXT_DIRECTION_INHERIT},
     * {@link #TEXT_DIRECTION_FIRST_STRONG}
     * {@link #TEXT_DIRECTION_ANY_RTL},
     * {@link #TEXT_DIRECTION_LTR},
     * {@link #TEXT_DIRECTION_RTL},
     *
     * @hide
     */
    public int getTextDirection() {
        return mTextDirection;
    }

    /**
     * Set the text direction.
     *
     * @param textDirection the direction to set. Should be one of:
     *
     * {@link #TEXT_DIRECTION_INHERIT},
     * {@link #TEXT_DIRECTION_FIRST_STRONG}
     * {@link #TEXT_DIRECTION_ANY_RTL},
     * {@link #TEXT_DIRECTION_LTR},
     * {@link #TEXT_DIRECTION_RTL},
     *
     * @hide
     */
    public void setTextDirection(int textDirection) {
        if (textDirection != mTextDirection) {
            mTextDirection = textDirection;
            resetResolvedTextDirection();
            requestLayout();
        }
    }

    /**
     * Return the resolved text direction.
     *
     * @return the resolved text direction. Return one of:
     *
     * {@link #TEXT_DIRECTION_LTR},
     * {@link #TEXT_DIRECTION_RTL},
     *
     * @hide
     */
    public int getResolvedTextDirection() {
        if (!isResolvedTextDirection()) {
            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.
     */
    protected void resolveTextDirection() {
    }

    /**
     * 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().
     */
    protected void resetResolvedTextDirection() {
        mResolvedTextDirection = TEXT_DIRECTION_UNDEFINED;
    }

    //
    // Properties
    //
+46 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import com.android.internal.util.Predicate;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Locale;

/**
 * <p>
@@ -5015,6 +5016,51 @@ 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 = TEXT_DIRECTION_UNDEFINED;
        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 get the direction from
                    // the Locale
                    resolvedTextDirection = isLayoutDirectionRtl(Locale.getDefault()) ?
                            TEXT_DIRECTION_RTL : TEXT_DIRECTION_LTR;
                }
                break;
            // Pass down the hierarchy the following text direction values
            case TEXT_DIRECTION_FIRST_STRONG:
            case TEXT_DIRECTION_ANY_RTL:
            case TEXT_DIRECTION_LTR:
            case TEXT_DIRECTION_RTL:
                resolvedTextDirection = mTextDirection;
                break;
        }
        mResolvedTextDirection = resolvedTextDirection;
    }

    @Override
    protected void resetResolvedTextDirection() {
        super.resetResolvedTextDirection();

        // Take care of resetting the children resolution too
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getTextDirection() == TEXT_DIRECTION_INHERIT) {
                child.resetResolvedTextDirection();
            }
        }
    }

    /**
     * Return true if the pressed state should be delayed for children or descendants of this
     * ViewGroup. Generally, this should be done for containers that can scroll, such as a List.
+121 −0
Original line number Diff line number Diff line
@@ -10010,6 +10010,127 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return mInBatchEditControllers;
    }

    /**
     * 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
     * determines the direction.
     *
     * This heuristic and result is applied individually to each paragraph in a TextView, based on
     * the text and style content of that paragraph. Paragraph text styles can also be used to force
     * a particular direction.
     */
    @Override
    protected void resolveTextDirection() {
        int resolvedTextDirection = TEXT_DIRECTION_UNDEFINED;
        switch(mTextDirection) {
            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);
                break;
            case TEXT_DIRECTION_ANY_RTL:
                resolvedTextDirection = getTextDirectionFromAnyRtl(mText);
                break;
            case TEXT_DIRECTION_LTR:
                resolvedTextDirection = TEXT_DIRECTION_LTR;
                break;
            case TEXT_DIRECTION_RTL:
                resolvedTextDirection = TEXT_DIRECTION_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();
        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();
        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;
            }
        }
        if (foundStrongRtl) {
            return TEXT_DIRECTION_RTL;
        }
        if (foundStrongLtr) {
            return TEXT_DIRECTION_LTR;
        }
        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")
    private CharSequence            mText;
    private CharSequence            mTransformed;
+18 −0
Original line number Diff line number Diff line
@@ -1972,6 +1972,24 @@
            <!-- Locale -->
            <enum name="locale" value="3" />
        </attr>
        <!-- Direction of the text. A heuristic is used to determine the resolved text direction
             of paragraphs. -->
        <attr name="textDirection" format="integer">
            <!-- Default -->
            <enum name="inherit" value="0" />
            <!-- Default for the root view. The first strong directional character determines the
                 paragraph direction.  If there is o strong directional character, the paragraph
                 direction is the view’s resolved layout direction. -->
            <enum name="firstStrong" value="1" />
            <!-- 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. -->
            <enum name="anyRtl" value="2" />
            <!-- The text direction is left to right. -->
            <enum name="ltr" value="3" />
            <!-- The text direction is right to left. -->
            <enum name="rtl" value="4" />
        </attr>
    </declare-styleable>

    <!-- Attributes that can be used with a {@link android.view.ViewGroup} or any
Loading