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

Commit a052d5a7 authored by Alan Viverette's avatar Alan Viverette Committed by Android (Google) Code Review
Browse files

Merge "Move caption view from Settings into framework" into klp-dev

parents 67a4d32c 398ec036
Loading
Loading
Loading
Loading
+365 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.widget;

import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources.Theme;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Join;
import android.graphics.Paint.Style;
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.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.accessibility.CaptioningManager.CaptionStyle;

public class SubtitleView extends View {
    // Ratio of inner padding to font size.
    private static final float INNER_PADDING_RATIO = 0.125f;

    // Styled dimensions.
    private final float mCornerRadius;
    private final float mOutlineWidth;
    private final float mShadowRadius;
    private final float mShadowOffsetX;
    private final float mShadowOffsetY;

    /** 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 SubtitleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SubtitleView(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 Resources res = getContext().getResources();
        final DisplayMetrics m = res.getDisplayMetrics();
        mCornerRadius = res.getDimension(com.android.internal.R.dimen.subtitle_corner_radius);
        mOutlineWidth = res.getDimension(com.android.internal.R.dimen.subtitle_outline_width);
        mShadowRadius = res.getDimension(com.android.internal.R.dimen.subtitle_shadow_radius);
        mShadowOffsetX = res.getDimension(com.android.internal.R.dimen.subtitle_shadow_offset);
        mShadowOffsetY = mShadowOffsetX;

        final TextPaint paint = new TextPaint();
        paint.setAntiAlias(true);
        paint.setSubpixelText(true);

        mPaint = paint;

        setText(text);
        setTextSize(textSize);
    }

    public void setText(int resId) {
        final CharSequence text = getContext().getText(resId);
        setText(text);
    }

    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;
        if (styleId == CaptionStyle.PRESET_CUSTOM) {
            style = CaptionStyle.getCustomStyle(cr);
        } else {
            style = CaptionStyle.PRESETS[styleId];
        }

        mForegroundColor = style.foregroundColor;
        mBackgroundColor = style.backgroundColor;
        mEdgeType = style.edgeType;
        mEdgeColor = style.edgeColor;
        mHasMeasurements = false;

        final Typeface typeface = style.getTypeface();
        setTypeface(typeface);

        requestLayout();
    }

    @Override
    protected void onDraw(Canvas c) {
        final StaticLayout layout = mLayout;
        if (layout == null) {
            return;
        }

        final int saveCount = c.save();
        final int innerPaddingX = mInnerPaddingX;
        c.translate(mPaddingLeft + innerPaddingX, mPaddingTop);

        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);
            }
        }

        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);

            for (int i = 0; i < lineCount; i++) {
                layout.drawText(c, i, i);
            }
        }

        if (edgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
            paint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor);
        }

        paint.setColor(mForegroundColor);
        paint.setStyle(Style.FILL);

        for (int i = 0; i < lineCount; i++) {
            layout.drawText(c, i, i);
        }

        c.restoreToCount(saveCount);
    }
}
+12 −0
Original line number Diff line number Diff line
@@ -344,4 +344,16 @@
    security mode. -->
    <dimen name="kg_small_widget_height">160dp</dimen>

    <!-- Rounded corner radius for video subtitles. -->
    <dimen name="subtitle_corner_radius">2dp</dimen>

    <!-- Shadow radius for video subtitles. -->
    <dimen name="subtitle_shadow_radius">2dp</dimen>

    <!-- Shadow offset for video subtitles. -->
    <dimen name="subtitle_shadow_offset">2dp</dimen>

    <!-- Outline width for video subtitles. -->
    <dimen name="subtitle_outline_width">2dp</dimen>

</resources>
+5 −0
Original line number Diff line number Diff line
@@ -1661,4 +1661,9 @@
  <java-symbol type="attr" name="actionModeWebSearchDrawable" />
  <java-symbol type="string" name="websearch" />

  <!-- From SubtitleView -->
  <java-symbol type="dimen" name="subtitle_corner_radius" />
  <java-symbol type="dimen" name="subtitle_shadow_radius" />
  <java-symbol type="dimen" name="subtitle_shadow_offset" />
  <java-symbol type="dimen" name="subtitle_outline_width" />
</resources>