diff --git a/res/layout/preset_picker_item.xml b/res/layout/preset_picker_item.xml index 6f99980fd54..1b66970b8f0 100644 --- a/res/layout/preset_picker_item.xml +++ b/res/layout/preset_picker_item.xml @@ -27,9 +27,9 @@ diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 44981152342..e58107af82a 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -735,11 +735,11 @@ - 6.0 12.0 24.0 - 32.0 48.0 + 72.0 + 96.0 @@ -854,8 +854,8 @@ - Black on white White on black + Black on white Yellow on black Yellow on blue Custom diff --git a/src/com/android/settings/accessibility/CaptioningTextView.java b/src/com/android/settings/accessibility/CaptioningTextView.java index 7bb677e83e1..d720e8fce1e 100644 --- a/src/com/android/settings/accessibility/CaptioningTextView.java +++ b/src/com/android/settings/accessibility/CaptioningTextView.java @@ -18,51 +18,273 @@ package com.android.settings.accessibility; import android.content.ContentResolver; import android.content.Context; -import android.content.res.ColorStateList; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Paint.Cap; import android.graphics.Paint.Join; import android.graphics.Paint.Style; -import android.os.Parcel; -import android.support.v4.view.ViewCompat; -import android.text.Editable; -import android.text.ParcelableSpan; +import android.graphics.RectF; +import android.graphics.Typeface; +import android.text.Layout.Alignment; +import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; -import android.text.style.CharacterStyle; -import android.text.style.UpdateAppearance; import android.util.AttributeSet; -import android.view.accessibility.CaptioningManager; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.View; import android.view.accessibility.CaptioningManager.CaptionStyle; -import android.widget.TextView; -public class CaptioningTextView extends TextView { - private MutableBackgroundColorSpan mBackgroundSpan; - private ColorStateList mOutlineColorState; - private float mOutlineWidth; - private int mOutlineColor; +public class CaptioningTextView extends View { + // Ratio of inner padding to font size. + private static final float INNER_PADDING_RATIO = 0.125f; - private int mEdgeType = CaptionStyle.EDGE_TYPE_NONE; - private int mEdgeColor = Color.TRANSPARENT; - private float mEdgeWidth = 0; + // Default style dimensions in dips. + private static final float CORNER_RADIUS = 2.0f; + private static final float OUTLINE_WIDTH = 2.0f; + private static final float SHADOW_RADIUS = 2.0f; + private static final float SHADOW_OFFSET_X = 2.0f; + private static final float SHADOW_OFFSET_Y = 2.0f; - private boolean mHasBackground = false; + // Styled dimensions. + private final float mCornerRadius; + private final float mOutlineWidth; + private final float mShadowRadius; + private final float mShadowOffsetX; + private final float mShadowOffsetY; - public CaptioningTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } + /** Temporary rectangle used for computing line bounds. */ + private final RectF mLineBounds = new RectF(); + + /** Temporary array used for computing line wrapping. */ + private float[] mTextWidths; + + /** Reusable string builder used for holding text. */ + private final StringBuilder mText = new StringBuilder(); + private final StringBuilder mBreakText = new StringBuilder(); + + private TextPaint mPaint; + + private int mForegroundColor; + private int mBackgroundColor; + private int mEdgeColor; + private int mEdgeType; + + private boolean mHasMeasurements; + private int mLastMeasuredWidth; + private StaticLayout mLayout; + + private float mSpacingMult = 1; + private float mSpacingAdd = 0; + private int mInnerPaddingX = 0; public CaptioningTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CaptioningTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs); + + final Theme theme = context.getTheme(); + final TypedArray a = theme.obtainStyledAttributes( + attrs, android.R.styleable.TextView, defStyle, 0); + + CharSequence text = ""; + int textSize = 15; + + final int n = a.getIndexCount(); + for (int i = 0; i < n; i++) { + int attr = a.getIndex(i); + + switch (attr) { + case android.R.styleable.TextView_text: + text = a.getText(attr); + break; + case android.R.styleable.TextView_lineSpacingExtra: + mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); + break; + case android.R.styleable.TextView_lineSpacingMultiplier: + mSpacingMult = a.getFloat(attr, mSpacingMult); + break; + case android.R.styleable.TextAppearance_textSize: + textSize = a.getDimensionPixelSize(attr, textSize); + break; + } + } + + // Set up density-dependent properties. + // TODO: Move these to a default style. + final DisplayMetrics m = getContext().getResources().getDisplayMetrics(); + mCornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS, m); + mOutlineWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, OUTLINE_WIDTH, m); + mShadowRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SHADOW_RADIUS, m); + mShadowOffsetX = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SHADOW_OFFSET_Y, m); + mShadowOffsetY = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, SHADOW_OFFSET_X, m); + + final TextPaint paint = new TextPaint(); + paint.setAntiAlias(true); + paint.setSubpixelText(true); + + mPaint = paint; + + setText(text); + setTextSize(textSize); } - public CaptioningTextView(Context context) { - super(context); + public void setText(int resId) { + final CharSequence text = getContext().getText(resId); + setText(text); } - public void applyStyleAndFontSize(int styleId) { + public void setText(CharSequence text) { + mText.setLength(0); + mText.append(text); + + mHasMeasurements = false; + + requestLayout(); + } + + public void setForegroundColor(int color) { + mForegroundColor = color; + + invalidate(); + } + + @Override + public void setBackgroundColor(int color) { + mBackgroundColor = color; + + invalidate(); + } + + public void setEdgeType(int edgeType) { + mEdgeType = edgeType; + + invalidate(); + } + + public void setEdgeColor(int color) { + mEdgeColor = color; + + invalidate(); + } + + public void setTextSize(float size) { + final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics(); + final float pixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, size, metrics); + if (mPaint.getTextSize() != size) { + mHasMeasurements = false; + mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f); + mPaint.setTextSize(size); + + requestLayout(); + } + } + + public void setTypeface(Typeface typeface) { + if (mPaint.getTypeface() != typeface) { + mHasMeasurements = false; + mPaint.setTypeface(typeface); + + requestLayout(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int widthSpec = MeasureSpec.getSize(widthMeasureSpec); + + if (computeMeasurements(widthSpec)) { + final StaticLayout layout = mLayout; + + // Account for padding. + final int paddingX = mPaddingLeft + mPaddingRight + mInnerPaddingX * 2; + final int width = layout.getWidth() + paddingX; + final int height = layout.getHeight() + mPaddingTop + mPaddingBottom; + setMeasuredDimension(width, height); + } else { + setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL); + } + } + + @Override + public void onLayout(boolean changed, int l, int t, int r, int b) { + final int width = r - l; + + computeMeasurements(width); + } + + private boolean computeMeasurements(int maxWidth) { + if (mHasMeasurements && maxWidth == mLastMeasuredWidth) { + return true; + } + + // Account for padding. + final int paddingX = mPaddingLeft + mPaddingRight + mInnerPaddingX; + maxWidth -= paddingX; + + if (maxWidth <= 0) { + return false; + } + + final TextPaint paint = mPaint; + final CharSequence text = mText; + final int textLength = text.length(); + if (mTextWidths == null || mTextWidths.length < textLength) { + mTextWidths = new float[textLength]; + } + + final float[] textWidths = mTextWidths; + paint.getTextWidths(text, 0, textLength, textWidths); + + // Compute total length. + float runLength = 0; + for (int i = 0; i < textLength; i++) { + runLength += textWidths[i]; + } + + final int lineCount = (int) (runLength / maxWidth) + 1; + final int lineLength = (int) (runLength / lineCount); + + // Build line break buffer. + final StringBuilder breakText = mBreakText; + breakText.setLength(0); + + int line = 0; + int lastBreak = 0; + int maxRunLength = 0; + runLength = 0; + for (int i = 0; i < textLength; i++) { + if (runLength > lineLength) { + final CharSequence sequence = text.subSequence(lastBreak, i); + final int trimmedLength = TextUtils.getTrimmedLength(sequence); + breakText.append(sequence, 0, trimmedLength); + breakText.append('\n'); + lastBreak = i; + runLength = 0; + } + + runLength += textWidths[i]; + + if (runLength > maxRunLength) { + maxRunLength = (int) Math.ceil(runLength); + } + } + breakText.append(text.subSequence(lastBreak, textLength)); + + mHasMeasurements = true; + mLastMeasuredWidth = maxWidth; + + mLayout = new StaticLayout(breakText, paint, maxRunLength, Alignment.ALIGN_LEFT, + mSpacingMult, mSpacingAdd, true); + + return true; + } + + public void setStyle(int styleId) { final Context context = mContext; final ContentResolver cr = context.getContentResolver(); final CaptionStyle style; @@ -72,238 +294,77 @@ public class CaptioningTextView extends TextView { style = CaptionStyle.PRESETS[styleId]; } - setTextColor(style.foregroundColor); - setBackgroundColor(style.backgroundColor); - setTypeface(style.getTypeface()); + mForegroundColor = style.foregroundColor; + mBackgroundColor = style.backgroundColor; + mEdgeType = style.edgeType; + mEdgeColor = style.edgeColor; + mHasMeasurements = false; - // Clears all outlines. - applyEdge(style.edgeType, style.edgeColor, 4.0f); + final Typeface typeface = style.getTypeface(); + setTypeface(typeface); - final float fontSize = CaptioningManager.getFontSize(cr); - if (fontSize != 0) { - setTextSize(fontSize); - } - } - - /** - * Applies an edge preset using a combination of {@link #setOutlineLayer} - * and {@link #setShadowLayer}. Any subsequent calls to either of these - * methods will invalidate the applied preset. - * - * @param type Type of edge to apply, one of: - * - * @param color Edge color as a packed 32-bit ARGB color. - * @param width Width of the edge in pixels. - */ - public void applyEdge(int type, int color, float width) { - if (mEdgeType != type || mEdgeColor != color || mEdgeWidth != width) { - final int textColor = getTextColors().getDefaultColor(); - switch (type) { - case CaptionStyle.EDGE_TYPE_DROP_SHADOW: - setOutlineLayer(0, 0); - super.setShadowLayer(width, width, width, color); - break; - case CaptionStyle.EDGE_TYPE_OUTLINE: - setOutlineLayer(width, color); - super.setShadowLayer(0, 0, 0, 0); - break; - default: - super.setShadowLayer(0, 0, 0, 0); - setOutlineLayer(0, 0); - } - - mEdgeType = type; - mEdgeColor = color; - mEdgeWidth = width; - } - } - - @Override - public void setShadowLayer(float radius, float dx, float dy, int color) { - mEdgeType = CaptionStyle.EDGE_TYPE_NONE; - - super.setShadowLayer(radius, dx, dy, color); - } - - /** - * Gives the text an outline of the specified pixel width and color. - */ - public void setOutlineLayer(float width, int color) { - width *= 2.0f; - - mEdgeType = CaptionStyle.EDGE_TYPE_NONE; - - if (mOutlineColor != color || mOutlineWidth != width) { - mOutlineColorState = ColorStateList.valueOf(color); - mOutlineColor = color; - mOutlineWidth = width; - invalidate(); - - // TODO: Remove after display list bug is fixed. - if (width > 0 && Color.alpha(color) != 0) { - setLayerType(ViewCompat.LAYER_TYPE_SOFTWARE, null); - } else { - setLayerType(ViewCompat.LAYER_TYPE_HARDWARE, null); - } - } - } - - /** - * @return the color of the outline layer - * @see #setOutlineLayer(float, int) - */ - public int getOutlineColor() { - return mOutlineColor; - } - - /** - * @return the width of the outline layer - * @see #setOutlineLayer(float, int) - */ - public float getOutlineWidth() { - return mOutlineWidth; - } - - @Override - public Editable getEditableText() { - final CharSequence text = getText(); - if (text instanceof Editable) { - return (Editable) text; - } - - setText(text, BufferType.EDITABLE); - return (Editable) getText(); - } - - @Override - public void setBackgroundColor(int color) { - if (Color.alpha(color) == 0) { - if (mHasBackground) { - mHasBackground = false; - getEditableText().removeSpan(mBackgroundSpan); - } - } else { - if (mBackgroundSpan == null) { - mBackgroundSpan = new MutableBackgroundColorSpan(color); - } else { - mBackgroundSpan.setColor(color); - } - - if (mHasBackground) { - invalidate(); - } else { - mHasBackground = true; - getEditableText().setSpan(mBackgroundSpan, 0, length(), 0); - } - } - } - - @Override - protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { - super.onTextChanged(text, start, lengthBefore, lengthAfter); - - if (mBackgroundSpan != null) { - getEditableText().setSpan(mBackgroundSpan, 0, lengthAfter, 0); - } + requestLayout(); } @Override protected void onDraw(Canvas c) { - if (mOutlineWidth > 0 && Color.alpha(mOutlineColor) > 0) { - final TextPaint textPaint = getPaint(); - final Paint.Style previousStyle = textPaint.getStyle(); - final ColorStateList previousColors = getTextColors(); - textPaint.setStyle(Style.STROKE); - textPaint.setStrokeWidth(mOutlineWidth); - textPaint.setStrokeCap(Cap.ROUND); - textPaint.setStrokeJoin(Join.ROUND); + final StaticLayout layout = mLayout; + if (layout == null) { + return; + } - setTextColor(mOutlineColorState); + final int saveCount = c.save(); + final int innerPaddingX = mInnerPaddingX; + c.translate(mPaddingLeft + innerPaddingX, mPaddingTop); - // Remove the shadow. - final float shadowRadius = getShadowRadius(); - final float shadowDx = getShadowDx(); - final float shadowDy = getShadowDy(); - final int shadowColor = getShadowColor(); - if (shadowRadius > 0) { - setShadowLayer(0, 0, 0, 0); + final RectF bounds = mLineBounds; + final int lineCount = layout.getLineCount(); + final Paint paint = layout.getPaint(); + paint.setShadowLayer(0, 0, 0, 0); + + final int backgroundColor = mBackgroundColor; + if (Color.alpha(backgroundColor) > 0) { + paint.setColor(backgroundColor); + paint.setStyle(Style.FILL); + + final float cornerRadius = mCornerRadius; + float previousBottom = layout.getLineTop(0); + + for (int i = 0; i < lineCount; i++) { + bounds.left = layout.getLineLeft(i) - innerPaddingX; + bounds.right = layout.getLineRight(i) + innerPaddingX; + bounds.top = previousBottom; + bounds.bottom = layout.getLineBottom(i); + + previousBottom = bounds.bottom; + + c.drawRoundRect(bounds, cornerRadius, cornerRadius, paint); } + } - // Draw outline and background only. - super.onDraw(c); + final int edgeType = mEdgeType; + if (edgeType == CaptionStyle.EDGE_TYPE_OUTLINE) { + paint.setColor(mEdgeColor); + paint.setStyle(Style.FILL_AND_STROKE); + paint.setStrokeJoin(Join.ROUND); + paint.setStrokeWidth(mOutlineWidth); - // Restore the shadow. - if (shadowRadius > 0) { - setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor); + for (int i = 0; i < lineCount; i++) { + layout.drawText(c, i, i); } - - // Restore original settings. - textPaint.setStyle(previousStyle); - setTextColor(previousColors); - - // Remove the background. - final int color; - if (mBackgroundSpan != null) { - color = mBackgroundSpan.getBackgroundColor(); - mBackgroundSpan.setColor(Color.TRANSPARENT); - } else { - color = 0; - } - - // Draw foreground only. - super.onDraw(c); - - // Restore the background. - if (mBackgroundSpan != null) { - mBackgroundSpan.setColor(color); - } - } else { - super.onDraw(c); - } - } - - public static class MutableBackgroundColorSpan extends CharacterStyle - implements UpdateAppearance, ParcelableSpan { - private int mColor; - - public MutableBackgroundColorSpan(int color) { - mColor = color; } - public MutableBackgroundColorSpan(Parcel src) { - mColor = src.readInt(); + if (edgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) { + paint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor); } - public void setColor(int color) { - mColor = color; + paint.setColor(mForegroundColor); + paint.setStyle(Style.FILL); + + for (int i = 0; i < lineCount; i++) { + layout.drawText(c, i, i); } - @Override - public int getSpanTypeId() { - return TextUtils.BACKGROUND_COLOR_SPAN; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mColor); - } - - public int getBackgroundColor() { - return mColor; - } - - @Override - public void updateDrawState(TextPaint ds) { - ds.bgColor = mColor; - } + c.restoreToCount(saveCount); } } diff --git a/src/com/android/settings/accessibility/EdgeTypePreference.java b/src/com/android/settings/accessibility/EdgeTypePreference.java index d0dee1d41ef..d146960f052 100644 --- a/src/com/android/settings/accessibility/EdgeTypePreference.java +++ b/src/com/android/settings/accessibility/EdgeTypePreference.java @@ -50,12 +50,14 @@ public class EdgeTypePreference extends ListDialogPreference { protected void onBindListItem(View view, int index) { final float fontSize = CaptioningManager.getFontSize(getContext().getContentResolver()); final CaptioningTextView preview = (CaptioningTextView) view.findViewById(R.id.preview); - preview.setTextColor(Color.WHITE); + + preview.setForegroundColor(Color.WHITE); preview.setBackgroundColor(Color.TRANSPARENT); preview.setTextSize(fontSize); final int value = getValueAt(index); - preview.applyEdge(value, Color.BLACK, 4.0f); + preview.setEdgeType(value); + preview.setEdgeColor(Color.BLACK); final CharSequence title = getTitleAt(index); if (title != null) { diff --git a/src/com/android/settings/accessibility/ToggleCaptioningPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleCaptioningPreferenceFragment.java index d0daf663d09..e248d14bb3a 100644 --- a/src/com/android/settings/accessibility/ToggleCaptioningPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleCaptioningPreferenceFragment.java @@ -86,10 +86,13 @@ public class ToggleCaptioningPreferenceFragment extends Fragment { } public static void applyCaptionProperties(CaptioningTextView previewText, int styleId) { - previewText.applyStyleAndFontSize(styleId); + previewText.setStyle(styleId); final Context context = previewText.getContext(); final ContentResolver cr = context.getContentResolver(); + final float fontSize = CaptioningManager.getFontSize(cr); + previewText.setTextSize(fontSize); + final Locale locale = CaptioningManager.getLocale(cr); if (locale != null) { final CharSequence localizedText = AccessibilityUtils.getTextForLocale(