content: /*
   * 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;
  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.SpannableStringBuilder;
  import android.text.StaticLayout;
  import android.text.TextPaint;
  import android.util.AttributeSet;
  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;
  
      /** Color used for the shadowed edge of a bevel. */
      private static final int COLOR_BEVEL_DARK = 0x80000000;
  
      /** Color used for the illuminated edge of a bevel. */
      private static final int COLOR_BEVEL_LIGHT = 0x80FFFFFF;
  
      // 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();
  
      /** Reusable spannable string builder used for holding text. */
      private final SpannableStringBuilder mText = new SpannableStringBuilder();
  
      private Alignment mAlignment = Alignment.ALIGN_CENTER;
      private TextPaint mTextPaint;
      private Paint 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) {
          this(context, null);
      }
  
      public SubtitleView(Context context, AttributeSet attrs) {
          this(context, attrs, 0);
      }
  
      public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
          this(context, attrs, defStyleAttr, 0);
      }
  
      public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs);
  
          final TypedArray a = context.obtainStyledAttributes(
                      attrs, android.R.styleable.TextView, defStyleAttr, defStyleRes);
  
          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();
          mCornerRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_corner_radius);
          mOutlineWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_outline_width);
          mShadowRadius = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_radius);
          mShadowOffsetX = res.getDimensionPixelSize(com.android.internal.R.dimen.subtitle_shadow_offset);
          mShadowOffsetY = mShadowOffsetX;
  
          mTextPaint = new TextPaint();
          mTextPaint.setAntiAlias(true);
          mTextPaint.setSubpixelText(true);
  
          mPaint = new Paint();
          mPaint.setAntiAlias(true);
  
          setText(text);
          setTextSize(textSize);
      }
  
      public void setText(int resId) {
          final CharSequence text = getContext().getText(resId);
          setText(text);
      }
  
      public void setText(CharSequence text) {
          mText.clear();
          mText.append(text);
  
          mHasMeasurements = false;
  
          requestLayout();
          invalidate();
      }
  
      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();
      }
  
      /**
       * Sets the text size in pixels.
       *
       * @param size the text size in pixels
       */
      public void setTextSize(float size) {
          if (mTextPaint.getTextSize() != size) {
              mTextPaint.setTextSize(size);
              mInnerPaddingX = (int) (size * INNER_PADDING_RATIO + 0.5f);
  
              mHasMeasurements = false;
  
              requestLayout();
              invalidate();
          }
      }
  
      public void setTypeface(Typeface typeface) {
          if (mTextPaint.getTypeface() != typeface) {
              mTextPaint.setTypeface(typeface);
  
              mHasMeasurements = false;
  
              requestLayout();
              invalidate();
          }
      }
  
      public void setAlignment(Alignment textAlignment) {
          if (mAlignment != textAlignment) {
              mAlignment = textAlignment;
  
              mHasMeasurements = false;
  
              requestLayout();
              invalidate();
          }
      }
  
      @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 * 2;
          maxWidth -= paddingX;
          if (maxWidth <= 0) {
              return false;
          }
  
          // TODO: Implement minimum-difference line wrapping. Adding the results
          // of Paint.getTextWidths() seems to return different values than
          // StaticLayout.getWidth(), so this is non-trivial.
          mHasMeasurements = true;
          mLastMeasuredWidth = maxWidth;
          mLayout = StaticLayout.Builder.obtain(mText, 0, mText.length(), mTextPaint, maxWidth)
                  .setAlignment(mAlignment)
                  .setLineSpacing(mSpacingAdd, mSpacingMult)
                  .setUseLineSpacingFromFallbacks(true)
                  .build();
  
          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];
          }
  
          final CaptionStyle defStyle = CaptionStyle.DEFAULT;
          mForegroundColor = style.hasForegroundColor() ?
                  style.foregroundColor : defStyle.foregroundColor;
          mBackgroundColor = style.hasBackgroundColor() ?
                  style.backgroundColor : defStyle.backgroundColor;
          mEdgeType = style.hasEdgeType() ? style.edgeType : defStyle.edgeType;
          mEdgeColor = style.hasEdgeColor() ? style.edgeColor : defStyle.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 int lineCount = layout.getLineCount();
          final Paint textPaint = mTextPaint;
          final Paint paint = mPaint;
          final RectF bounds = mLineBounds;
  
          if (Color.alpha(mBackgroundColor) > 0) {
              final float cornerRadius = mCornerRadius;
              float previousBottom = layout.getLineTop(0);
  
              paint.setColor(mBackgroundColor);
              paint.setStyle(Style.FILL);
  
              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) {
              textPaint.setStrokeJoin(Join.ROUND);
              textPaint.setStrokeWidth(mOutlineWidth);
              textPaint.setColor(mEdgeColor);
              textPaint.setStyle(Style.FILL_AND_STROKE);
  
              for (int i = 0; i < lineCount; i++) {
                  layout.drawText(c, i, i);
              }
          } else if (edgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
              textPaint.setShadowLayer(mShadowRadius, mShadowOffsetX, mShadowOffsetY, mEdgeColor);
          } else if (edgeType == CaptionStyle.EDGE_TYPE_RAISED
                  || edgeType == CaptionStyle.EDGE_TYPE_DEPRESSED) {
              final boolean raised = edgeType == CaptionStyle.EDGE_TYPE_RAISED;
              final int colorUp = raised ? Color.WHITE : mEdgeColor;
              final int colorDown = raised ? mEdgeColor : Color.WHITE;
              final float offset = mShadowRadius / 2f;
  
              textPaint.setColor(mForegroundColor);
              textPaint.setStyle(Style.FILL);
              textPaint.setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
  
              for (int i = 0; i < lineCount; i++) {
                  layout.drawText(c, i, i);
              }
  
              textPaint.setShadowLayer(mShadowRadius, offset, offset, colorDown);
          }
  
          textPaint.setColor(mForegroundColor);
          textPaint.setStyle(Style.FILL);
  
          for (int i = 0; i < lineCount; i++) {
              layout.drawText(c, i, i);
          }
  
          textPaint.setShadowLayer(0, 0, 0, 0);
          c.restoreToCount(saveCount);
      }
  }
  