1bdfd91024767893829017918ab565f224ccd35ecChong Zhang/*
2bdfd91024767893829017918ab565f224ccd35ecChong Zhang * Copyright (C) 2014 The Android Open Source Project
3bdfd91024767893829017918ab565f224ccd35ecChong Zhang *
4bdfd91024767893829017918ab565f224ccd35ecChong Zhang * Licensed under the Apache License, Version 2.0 (the "License");
5bdfd91024767893829017918ab565f224ccd35ecChong Zhang * you may not use this file except in compliance with the License.
6bdfd91024767893829017918ab565f224ccd35ecChong Zhang * You may obtain a copy of the License at
7bdfd91024767893829017918ab565f224ccd35ecChong Zhang *
8bdfd91024767893829017918ab565f224ccd35ecChong Zhang *      http://www.apache.org/licenses/LICENSE-2.0
9bdfd91024767893829017918ab565f224ccd35ecChong Zhang *
10bdfd91024767893829017918ab565f224ccd35ecChong Zhang * Unless required by applicable law or agreed to in writing, software
11bdfd91024767893829017918ab565f224ccd35ecChong Zhang * distributed under the License is distributed on an "AS IS" BASIS,
12bdfd91024767893829017918ab565f224ccd35ecChong Zhang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bdfd91024767893829017918ab565f224ccd35ecChong Zhang * See the License for the specific language governing permissions and
14bdfd91024767893829017918ab565f224ccd35ecChong Zhang * limitations under the License.
15bdfd91024767893829017918ab565f224ccd35ecChong Zhang */
16bdfd91024767893829017918ab565f224ccd35ecChong Zhang
17bdfd91024767893829017918ab565f224ccd35ecChong Zhangpackage android.media;
18bdfd91024767893829017918ab565f224ccd35ecChong Zhang
19bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.content.Context;
2019ed4a179ccea6915f1852139d520dddba779490Chong Zhangimport android.content.res.Resources;
2119ed4a179ccea6915f1852139d520dddba779490Chong Zhangimport android.graphics.Canvas;
22bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.graphics.Color;
2319ed4a179ccea6915f1852139d520dddba779490Chong Zhangimport android.graphics.Paint;
24bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.graphics.Rect;
25bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.graphics.Typeface;
2619ed4a179ccea6915f1852139d520dddba779490Chong Zhangimport android.text.Spannable;
2719ed4a179ccea6915f1852139d520dddba779490Chong Zhangimport android.text.SpannableStringBuilder;
2819ed4a179ccea6915f1852139d520dddba779490Chong Zhangimport android.text.TextPaint;
2919ed4a179ccea6915f1852139d520dddba779490Chong Zhangimport android.text.style.CharacterStyle;
3019ed4a179ccea6915f1852139d520dddba779490Chong Zhangimport android.text.style.StyleSpan;
3119ed4a179ccea6915f1852139d520dddba779490Chong Zhangimport android.text.style.UnderlineSpan;
3219ed4a179ccea6915f1852139d520dddba779490Chong Zhangimport android.text.style.UpdateAppearance;
33bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.util.AttributeSet;
34bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.util.Log;
35bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.util.TypedValue;
36bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.view.Gravity;
37bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.view.View;
38bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.view.ViewGroup;
39bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.view.accessibility.CaptioningManager;
40bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.view.accessibility.CaptioningManager.CaptionStyle;
41bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.view.accessibility.CaptioningManager.CaptioningChangeListener;
42bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.widget.LinearLayout;
43bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport android.widget.TextView;
44bdfd91024767893829017918ab565f224ccd35ecChong Zhang
45bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport java.util.ArrayList;
46bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport java.util.Arrays;
47bdfd91024767893829017918ab565f224ccd35ecChong Zhangimport java.util.Vector;
48bdfd91024767893829017918ab565f224ccd35ecChong Zhang
49bdfd91024767893829017918ab565f224ccd35ecChong Zhang/** @hide */
50bdfd91024767893829017918ab565f224ccd35ecChong Zhangpublic class ClosedCaptionRenderer extends SubtitleController.Renderer {
51bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private final Context mContext;
52978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    private Cea608CCWidget mCCWidget;
53bdfd91024767893829017918ab565f224ccd35ecChong Zhang
54bdfd91024767893829017918ab565f224ccd35ecChong Zhang    public ClosedCaptionRenderer(Context context) {
55bdfd91024767893829017918ab565f224ccd35ecChong Zhang        mContext = context;
56bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
57bdfd91024767893829017918ab565f224ccd35ecChong Zhang
58bdfd91024767893829017918ab565f224ccd35ecChong Zhang    @Override
59bdfd91024767893829017918ab565f224ccd35ecChong Zhang    public boolean supports(MediaFormat format) {
60bdfd91024767893829017918ab565f224ccd35ecChong Zhang        if (format.containsKey(MediaFormat.KEY_MIME)) {
61978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            String mimeType = format.getString(MediaFormat.KEY_MIME);
62978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            return MediaPlayer.MEDIA_MIMETYPE_TEXT_CEA_608.equals(mimeType);
63bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
64bdfd91024767893829017918ab565f224ccd35ecChong Zhang        return false;
65bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
66bdfd91024767893829017918ab565f224ccd35ecChong Zhang
67bdfd91024767893829017918ab565f224ccd35ecChong Zhang    @Override
68bdfd91024767893829017918ab565f224ccd35ecChong Zhang    public SubtitleTrack createTrack(MediaFormat format) {
69978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        String mimeType = format.getString(MediaFormat.KEY_MIME);
70978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        if (MediaPlayer.MEDIA_MIMETYPE_TEXT_CEA_608.equals(mimeType)) {
71978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            if (mCCWidget == null) {
72978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung                mCCWidget = new Cea608CCWidget(mContext);
73978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            }
74978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            return new Cea608CaptionTrack(mCCWidget, format);
75bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
76978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        throw new RuntimeException("No matching format: " + format.toString());
77bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
78bdfd91024767893829017918ab565f224ccd35ecChong Zhang}
79bdfd91024767893829017918ab565f224ccd35ecChong Zhang
80bdfd91024767893829017918ab565f224ccd35ecChong Zhang/** @hide */
81978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chungclass Cea608CaptionTrack extends SubtitleTrack {
82978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    private final Cea608CCParser mCCParser;
83978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    private final Cea608CCWidget mRenderingWidget;
84bdfd91024767893829017918ab565f224ccd35ecChong Zhang
85978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    Cea608CaptionTrack(Cea608CCWidget renderingWidget, MediaFormat format) {
86bdfd91024767893829017918ab565f224ccd35ecChong Zhang        super(format);
87bdfd91024767893829017918ab565f224ccd35ecChong Zhang
88bdfd91024767893829017918ab565f224ccd35ecChong Zhang        mRenderingWidget = renderingWidget;
89978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        mCCParser = new Cea608CCParser(mRenderingWidget);
90bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
91bdfd91024767893829017918ab565f224ccd35ecChong Zhang
92bdfd91024767893829017918ab565f224ccd35ecChong Zhang    @Override
93bdfd91024767893829017918ab565f224ccd35ecChong Zhang    public void onData(byte[] data, boolean eos, long runID) {
94bdfd91024767893829017918ab565f224ccd35ecChong Zhang        mCCParser.parse(data);
95bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
96bdfd91024767893829017918ab565f224ccd35ecChong Zhang
97bdfd91024767893829017918ab565f224ccd35ecChong Zhang    @Override
98bdfd91024767893829017918ab565f224ccd35ecChong Zhang    public RenderingWidget getRenderingWidget() {
99bdfd91024767893829017918ab565f224ccd35ecChong Zhang        return mRenderingWidget;
100bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
101bdfd91024767893829017918ab565f224ccd35ecChong Zhang
102bdfd91024767893829017918ab565f224ccd35ecChong Zhang    @Override
103bdfd91024767893829017918ab565f224ccd35ecChong Zhang    public void updateView(Vector<Cue> activeCues) {
104bdfd91024767893829017918ab565f224ccd35ecChong Zhang        // Overriding with NO-OP, CC rendering by-passes this
105bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
106bdfd91024767893829017918ab565f224ccd35ecChong Zhang}
107bdfd91024767893829017918ab565f224ccd35ecChong Zhang
108bdfd91024767893829017918ab565f224ccd35ecChong Zhang/**
109978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung * Abstract widget class to render a closed caption track.
110978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung *
111978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung * @hide
112978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung */
113978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chungabstract class ClosedCaptionWidget extends ViewGroup implements SubtitleTrack.RenderingWidget {
114978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
115978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    /** @hide */
116978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    interface ClosedCaptionLayout {
117978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        void setCaptionStyle(CaptionStyle captionStyle);
118978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        void setFontScale(float scale);
119978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
120978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
121978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    private static final CaptionStyle DEFAULT_CAPTION_STYLE = CaptionStyle.DEFAULT;
122978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
123978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    /** Captioning manager, used to obtain and track caption properties. */
124978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    private final CaptioningManager mManager;
125978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
126978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    /** Current caption style. */
127978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    protected CaptionStyle mCaptionStyle;
128978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
129978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    /** Callback for rendering changes. */
130978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    protected OnChangedListener mListener;
131978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
132978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    /** Concrete layout of CC. */
133978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    protected ClosedCaptionLayout mClosedCaptionLayout;
134978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
135978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    /** Whether a caption style change listener is registered. */
136978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    private boolean mHasChangeListener;
137978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
138978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public ClosedCaptionWidget(Context context) {
139978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        this(context, null);
140978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
141978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
142978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public ClosedCaptionWidget(Context context, AttributeSet attrs) {
143978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        this(context, attrs, 0);
144978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
145978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
146978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyle) {
147978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        this(context, attrs, defStyle, 0);
148978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
149978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
150978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public ClosedCaptionWidget(Context context, AttributeSet attrs, int defStyleAttr,
151978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            int defStyleRes) {
152978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        super(context, attrs, defStyleAttr, defStyleRes);
153978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
154978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        // Cannot render text over video when layer type is hardware.
155978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
156978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
157978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        mManager = (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
158978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(mManager.getUserStyle());
159978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
160978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        mClosedCaptionLayout = createCaptionLayout(context);
161978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
162978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        mClosedCaptionLayout.setFontScale(mManager.getFontScale());
163978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        addView((ViewGroup) mClosedCaptionLayout, LayoutParams.MATCH_PARENT,
164978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung                LayoutParams.MATCH_PARENT);
165978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
166978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        requestLayout();
167978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
168978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
169978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public abstract ClosedCaptionLayout createCaptionLayout(Context context);
170978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
171978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    @Override
172978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public void setOnChangedListener(OnChangedListener listener) {
173978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        mListener = listener;
174978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
175978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
176978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    @Override
177978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public void setSize(int width, int height) {
178978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        final int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
179978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        final int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
180978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
181978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        measure(widthSpec, heightSpec);
182978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        layout(0, 0, width, height);
183978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
184978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
185978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    @Override
186978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public void setVisible(boolean visible) {
187978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        if (visible) {
188978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            setVisibility(View.VISIBLE);
189978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        } else {
190978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            setVisibility(View.GONE);
191978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        }
192978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
193978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        manageChangeListener();
194978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
195978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
196978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    @Override
197978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public void onAttachedToWindow() {
198978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        super.onAttachedToWindow();
199978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
200978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        manageChangeListener();
201978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
202978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
203978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    @Override
204978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public void onDetachedFromWindow() {
205978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        super.onDetachedFromWindow();
206978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
207978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        manageChangeListener();
208978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
209978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
210978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    @Override
211978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
212978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
213978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        ((ViewGroup) mClosedCaptionLayout).measure(widthMeasureSpec, heightMeasureSpec);
214978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
215978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
216978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    @Override
217978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    protected void onLayout(boolean changed, int l, int t, int r, int b) {
218978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        ((ViewGroup) mClosedCaptionLayout).layout(l, t, r, b);
219978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
220978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
221978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    /**
222978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung     * Manages whether this renderer is listening for caption style changes.
223978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung     */
224978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    private final CaptioningChangeListener mCaptioningListener = new CaptioningChangeListener() {
225978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        @Override
226978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        public void onUserStyleChanged(CaptionStyle userStyle) {
227978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            mCaptionStyle = DEFAULT_CAPTION_STYLE.applyStyle(userStyle);
228978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            mClosedCaptionLayout.setCaptionStyle(mCaptionStyle);
229978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        }
230978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
231978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        @Override
232978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        public void onFontScaleChanged(float fontScale) {
233978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            mClosedCaptionLayout.setFontScale(fontScale);
234978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        }
235978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    };
236978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
237978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    private void manageChangeListener() {
238978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        final boolean needsListener = isAttachedToWindow() && getVisibility() == View.VISIBLE;
239978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        if (mHasChangeListener != needsListener) {
240978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            mHasChangeListener = needsListener;
241978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
242978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            if (needsListener) {
243978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung                mManager.addCaptioningChangeListener(mCaptioningListener);
244978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            } else {
245978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung                mManager.removeCaptioningChangeListener(mCaptioningListener);
246978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            }
247978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        }
248978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
249978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung}
250978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
251978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung/**
252bdfd91024767893829017918ab565f224ccd35ecChong Zhang * @hide
253bdfd91024767893829017918ab565f224ccd35ecChong Zhang *
254bdfd91024767893829017918ab565f224ccd35ecChong Zhang * CCParser processes CEA-608 closed caption data.
255bdfd91024767893829017918ab565f224ccd35ecChong Zhang *
256bdfd91024767893829017918ab565f224ccd35ecChong Zhang * It calls back into OnDisplayChangedListener upon
257bdfd91024767893829017918ab565f224ccd35ecChong Zhang * display change with styled text for rendering.
258bdfd91024767893829017918ab565f224ccd35ecChong Zhang *
259bdfd91024767893829017918ab565f224ccd35ecChong Zhang */
260978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chungclass Cea608CCParser {
261bdfd91024767893829017918ab565f224ccd35ecChong Zhang    public static final int MAX_ROWS = 15;
262bdfd91024767893829017918ab565f224ccd35ecChong Zhang    public static final int MAX_COLS = 32;
263bdfd91024767893829017918ab565f224ccd35ecChong Zhang
264978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    private static final String TAG = "Cea608CCParser";
265bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
266bdfd91024767893829017918ab565f224ccd35ecChong Zhang
267bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int INVALID = -1;
268bdfd91024767893829017918ab565f224ccd35ecChong Zhang
269bdfd91024767893829017918ab565f224ccd35ecChong Zhang    // EIA-CEA-608: Table 70 - Control Codes
270bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int RCL = 0x20;
271bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int BS  = 0x21;
272bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int AOF = 0x22;
273bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int AON = 0x23;
274bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int DER = 0x24;
275bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int RU2 = 0x25;
276bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int RU3 = 0x26;
277bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int RU4 = 0x27;
278bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int FON = 0x28;
279bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int RDC = 0x29;
280bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int TR  = 0x2a;
281bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int RTD = 0x2b;
282bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int EDM = 0x2c;
283bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int CR  = 0x2d;
284bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int ENM = 0x2e;
285bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int EOC = 0x2f;
286bdfd91024767893829017918ab565f224ccd35ecChong Zhang
287bdfd91024767893829017918ab565f224ccd35ecChong Zhang    // Transparent Space
288bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final char TS = '\u00A0';
289bdfd91024767893829017918ab565f224ccd35ecChong Zhang
290bdfd91024767893829017918ab565f224ccd35ecChong Zhang    // Captioning Modes
291bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int MODE_UNKNOWN = 0;
292bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int MODE_PAINT_ON = 1;
293bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int MODE_ROLL_UP = 2;
294bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int MODE_POP_ON = 3;
295bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final int MODE_TEXT = 4;
296bdfd91024767893829017918ab565f224ccd35ecChong Zhang
297bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private final DisplayListener mListener;
298bdfd91024767893829017918ab565f224ccd35ecChong Zhang
299bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private int mMode = MODE_PAINT_ON;
300bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private int mRollUpSize = 4;
30156a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang    private int mPrevCtrlCode = INVALID;
302bdfd91024767893829017918ab565f224ccd35ecChong Zhang
303bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private CCMemory mDisplay = new CCMemory();
304bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private CCMemory mNonDisplay = new CCMemory();
305bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private CCMemory mTextMem = new CCMemory();
306bdfd91024767893829017918ab565f224ccd35ecChong Zhang
307978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    Cea608CCParser(DisplayListener listener) {
308bdfd91024767893829017918ab565f224ccd35ecChong Zhang        mListener = listener;
309bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
310bdfd91024767893829017918ab565f224ccd35ecChong Zhang
311978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public void parse(byte[] data) {
312bdfd91024767893829017918ab565f224ccd35ecChong Zhang        CCData[] ccData = CCData.fromByteArray(data);
313bdfd91024767893829017918ab565f224ccd35ecChong Zhang
314bdfd91024767893829017918ab565f224ccd35ecChong Zhang        for (int i = 0; i < ccData.length; i++) {
315bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (DEBUG) {
316bdfd91024767893829017918ab565f224ccd35ecChong Zhang                Log.d(TAG, ccData[i].toString());
317bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
318bdfd91024767893829017918ab565f224ccd35ecChong Zhang
319bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (handleCtrlCode(ccData[i])
320bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    || handleTabOffsets(ccData[i])
321bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    || handlePACCode(ccData[i])
322bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    || handleMidRowCode(ccData[i])) {
323bdfd91024767893829017918ab565f224ccd35ecChong Zhang                continue;
324bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
325bdfd91024767893829017918ab565f224ccd35ecChong Zhang
326bdfd91024767893829017918ab565f224ccd35ecChong Zhang            handleDisplayableChars(ccData[i]);
327bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
328bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
329bdfd91024767893829017918ab565f224ccd35ecChong Zhang
330bdfd91024767893829017918ab565f224ccd35ecChong Zhang    interface DisplayListener {
331978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        void onDisplayChanged(SpannableStringBuilder[] styledTexts);
332978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        CaptionStyle getCaptionStyle();
333bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
334bdfd91024767893829017918ab565f224ccd35ecChong Zhang
335bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private CCMemory getMemory() {
336bdfd91024767893829017918ab565f224ccd35ecChong Zhang        // get the CC memory to operate on for current mode
337bdfd91024767893829017918ab565f224ccd35ecChong Zhang        switch (mMode) {
338bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case MODE_POP_ON:
339bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mNonDisplay;
340bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case MODE_TEXT:
341bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // TODO(chz): support only caption mode for now,
342bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // in text mode, dump everything to text mem.
343bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mTextMem;
344bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case MODE_PAINT_ON:
345bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case MODE_ROLL_UP:
346bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mDisplay;
347bdfd91024767893829017918ab565f224ccd35ecChong Zhang        default:
348bdfd91024767893829017918ab565f224ccd35ecChong Zhang            Log.w(TAG, "unrecoginized mode: " + mMode);
349bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
350bdfd91024767893829017918ab565f224ccd35ecChong Zhang        return mDisplay;
351bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
352bdfd91024767893829017918ab565f224ccd35ecChong Zhang
353bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private boolean handleDisplayableChars(CCData ccData) {
354bdfd91024767893829017918ab565f224ccd35ecChong Zhang        if (!ccData.isDisplayableChar()) {
355bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return false;
356bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
357bdfd91024767893829017918ab565f224ccd35ecChong Zhang
358bdfd91024767893829017918ab565f224ccd35ecChong Zhang        // Extended char includes 1 automatic backspace
359bdfd91024767893829017918ab565f224ccd35ecChong Zhang        if (ccData.isExtendedChar()) {
360bdfd91024767893829017918ab565f224ccd35ecChong Zhang            getMemory().bs();
361bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
362bdfd91024767893829017918ab565f224ccd35ecChong Zhang
363bdfd91024767893829017918ab565f224ccd35ecChong Zhang        getMemory().writeText(ccData.getDisplayText());
364bdfd91024767893829017918ab565f224ccd35ecChong Zhang
365bdfd91024767893829017918ab565f224ccd35ecChong Zhang        if (mMode == MODE_PAINT_ON || mMode == MODE_ROLL_UP) {
366bdfd91024767893829017918ab565f224ccd35ecChong Zhang            updateDisplay();
367bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
368bdfd91024767893829017918ab565f224ccd35ecChong Zhang
369bdfd91024767893829017918ab565f224ccd35ecChong Zhang        return true;
370bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
371bdfd91024767893829017918ab565f224ccd35ecChong Zhang
372bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private boolean handleMidRowCode(CCData ccData) {
373bdfd91024767893829017918ab565f224ccd35ecChong Zhang        StyleCode m = ccData.getMidRow();
374bdfd91024767893829017918ab565f224ccd35ecChong Zhang        if (m != null) {
375bdfd91024767893829017918ab565f224ccd35ecChong Zhang            getMemory().writeMidRowCode(m);
376bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return true;
377bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
378bdfd91024767893829017918ab565f224ccd35ecChong Zhang        return false;
379bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
380bdfd91024767893829017918ab565f224ccd35ecChong Zhang
381bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private boolean handlePACCode(CCData ccData) {
382bdfd91024767893829017918ab565f224ccd35ecChong Zhang        PAC pac = ccData.getPAC();
383bdfd91024767893829017918ab565f224ccd35ecChong Zhang
384bdfd91024767893829017918ab565f224ccd35ecChong Zhang        if (pac != null) {
385bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mMode == MODE_ROLL_UP) {
386bdfd91024767893829017918ab565f224ccd35ecChong Zhang                getMemory().moveBaselineTo(pac.getRow(), mRollUpSize);
387bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
388bdfd91024767893829017918ab565f224ccd35ecChong Zhang            getMemory().writePAC(pac);
389bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return true;
390bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
391bdfd91024767893829017918ab565f224ccd35ecChong Zhang
392bdfd91024767893829017918ab565f224ccd35ecChong Zhang        return false;
393bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
394bdfd91024767893829017918ab565f224ccd35ecChong Zhang
395bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private boolean handleTabOffsets(CCData ccData) {
396bdfd91024767893829017918ab565f224ccd35ecChong Zhang        int tabs = ccData.getTabOffset();
397bdfd91024767893829017918ab565f224ccd35ecChong Zhang
398bdfd91024767893829017918ab565f224ccd35ecChong Zhang        if (tabs > 0) {
399bdfd91024767893829017918ab565f224ccd35ecChong Zhang            getMemory().tab(tabs);
400bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return true;
401bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
402bdfd91024767893829017918ab565f224ccd35ecChong Zhang
403bdfd91024767893829017918ab565f224ccd35ecChong Zhang        return false;
404bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
405bdfd91024767893829017918ab565f224ccd35ecChong Zhang
406bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private boolean handleCtrlCode(CCData ccData) {
407bdfd91024767893829017918ab565f224ccd35ecChong Zhang        int ctrlCode = ccData.getCtrlCode();
40856a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang
40956a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang        if (mPrevCtrlCode != INVALID && mPrevCtrlCode == ctrlCode) {
41056a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang            // discard double ctrl codes (but if there's a 3rd one, we still take that)
41156a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang            mPrevCtrlCode = INVALID;
41256a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang            return true;
41356a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang        }
41456a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang
415bdfd91024767893829017918ab565f224ccd35ecChong Zhang        switch(ctrlCode) {
416bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case RCL:
417bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // select pop-on style
418bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mMode = MODE_POP_ON;
419bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
420bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case BS:
421bdfd91024767893829017918ab565f224ccd35ecChong Zhang            getMemory().bs();
422bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
423bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case DER:
424bdfd91024767893829017918ab565f224ccd35ecChong Zhang            getMemory().der();
425bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
426bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case RU2:
427bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case RU3:
428bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case RU4:
429bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mRollUpSize = (ctrlCode - 0x23);
430bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // erase memory if currently in other style
431bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mMode != MODE_ROLL_UP) {
432bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mDisplay.erase();
433bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mNonDisplay.erase();
434bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
435bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // select roll-up style
436bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mMode = MODE_ROLL_UP;
437bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
438bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case FON:
439bdfd91024767893829017918ab565f224ccd35ecChong Zhang            Log.i(TAG, "Flash On");
440bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
441bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case RDC:
442bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // select paint-on style
443bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mMode = MODE_PAINT_ON;
444bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
445bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case TR:
446bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mMode = MODE_TEXT;
447bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mTextMem.erase();
448bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
449bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case RTD:
450bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mMode = MODE_TEXT;
451bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
452bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case EDM:
453bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // erase display memory
454bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mDisplay.erase();
455bdfd91024767893829017918ab565f224ccd35ecChong Zhang            updateDisplay();
456bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
457bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case CR:
458bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mMode == MODE_ROLL_UP) {
459bdfd91024767893829017918ab565f224ccd35ecChong Zhang                getMemory().rollUp(mRollUpSize);
460bdfd91024767893829017918ab565f224ccd35ecChong Zhang            } else {
461bdfd91024767893829017918ab565f224ccd35ecChong Zhang                getMemory().cr();
462bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
463bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mMode == MODE_ROLL_UP) {
464bdfd91024767893829017918ab565f224ccd35ecChong Zhang                updateDisplay();
465bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
466bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
467bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case ENM:
468bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // erase non-display memory
469bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mNonDisplay.erase();
470bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
471bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case EOC:
472bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // swap display/non-display memory
473bdfd91024767893829017918ab565f224ccd35ecChong Zhang            swapMemory();
474bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // switch to pop-on style
475bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mMode = MODE_POP_ON;
476bdfd91024767893829017918ab565f224ccd35ecChong Zhang            updateDisplay();
477bdfd91024767893829017918ab565f224ccd35ecChong Zhang            break;
478bdfd91024767893829017918ab565f224ccd35ecChong Zhang        case INVALID:
479bdfd91024767893829017918ab565f224ccd35ecChong Zhang        default:
48056a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang            mPrevCtrlCode = INVALID;
481bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return false;
482bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
483bdfd91024767893829017918ab565f224ccd35ecChong Zhang
48456a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang        mPrevCtrlCode = ctrlCode;
48556a7ed6982ce5100ed6fa5442931b2f39851a08cChong Zhang
486bdfd91024767893829017918ab565f224ccd35ecChong Zhang        // handled
487bdfd91024767893829017918ab565f224ccd35ecChong Zhang        return true;
488bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
489bdfd91024767893829017918ab565f224ccd35ecChong Zhang
490bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private void updateDisplay() {
491bdfd91024767893829017918ab565f224ccd35ecChong Zhang        if (mListener != null) {
492bdfd91024767893829017918ab565f224ccd35ecChong Zhang            CaptionStyle captionStyle = mListener.getCaptionStyle();
493bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mListener.onDisplayChanged(mDisplay.getStyledText(captionStyle));
494bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
495bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
496bdfd91024767893829017918ab565f224ccd35ecChong Zhang
497bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private void swapMemory() {
498bdfd91024767893829017918ab565f224ccd35ecChong Zhang        CCMemory temp = mDisplay;
499bdfd91024767893829017918ab565f224ccd35ecChong Zhang        mDisplay = mNonDisplay;
500bdfd91024767893829017918ab565f224ccd35ecChong Zhang        mNonDisplay = temp;
501bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
502bdfd91024767893829017918ab565f224ccd35ecChong Zhang
503bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static class StyleCode {
504bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final int COLOR_WHITE = 0;
505bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final int COLOR_GREEN = 1;
506bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final int COLOR_BLUE = 2;
507bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final int COLOR_CYAN = 3;
508bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final int COLOR_RED = 4;
509bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final int COLOR_YELLOW = 5;
510bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final int COLOR_MAGENTA = 6;
511bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final int COLOR_INVALID = 7;
512bdfd91024767893829017918ab565f224ccd35ecChong Zhang
513bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final int STYLE_ITALICS   = 0x00000001;
514bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final int STYLE_UNDERLINE = 0x00000002;
515bdfd91024767893829017918ab565f224ccd35ecChong Zhang
516bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static final String[] mColorMap = {
517bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "WHITE", "GREEN", "BLUE", "CYAN", "RED", "YELLOW", "MAGENTA", "INVALID"
518bdfd91024767893829017918ab565f224ccd35ecChong Zhang        };
519bdfd91024767893829017918ab565f224ccd35ecChong Zhang
520bdfd91024767893829017918ab565f224ccd35ecChong Zhang        final int mStyle;
521bdfd91024767893829017918ab565f224ccd35ecChong Zhang        final int mColor;
522bdfd91024767893829017918ab565f224ccd35ecChong Zhang
523bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static StyleCode fromByte(byte data2) {
524bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int style = 0;
525bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int color = (data2 >> 1) & 0x7;
526bdfd91024767893829017918ab565f224ccd35ecChong Zhang
527bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((data2 & 0x1) != 0) {
528bdfd91024767893829017918ab565f224ccd35ecChong Zhang                style |= STYLE_UNDERLINE;
529bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
530bdfd91024767893829017918ab565f224ccd35ecChong Zhang
531bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (color == COLOR_INVALID) {
532bdfd91024767893829017918ab565f224ccd35ecChong Zhang                // WHITE ITALICS
533bdfd91024767893829017918ab565f224ccd35ecChong Zhang                color = COLOR_WHITE;
534bdfd91024767893829017918ab565f224ccd35ecChong Zhang                style |= STYLE_ITALICS;
535bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
536bdfd91024767893829017918ab565f224ccd35ecChong Zhang
537bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return new StyleCode(style, color);
538bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
539bdfd91024767893829017918ab565f224ccd35ecChong Zhang
540bdfd91024767893829017918ab565f224ccd35ecChong Zhang        StyleCode(int style, int color) {
541bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mStyle = style;
542bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mColor = color;
543bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
544bdfd91024767893829017918ab565f224ccd35ecChong Zhang
545bdfd91024767893829017918ab565f224ccd35ecChong Zhang        boolean isItalics() {
546bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return (mStyle & STYLE_ITALICS) != 0;
547bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
548bdfd91024767893829017918ab565f224ccd35ecChong Zhang
54919ed4a179ccea6915f1852139d520dddba779490Chong Zhang        boolean isUnderline() {
55019ed4a179ccea6915f1852139d520dddba779490Chong Zhang            return (mStyle & STYLE_UNDERLINE) != 0;
55119ed4a179ccea6915f1852139d520dddba779490Chong Zhang        }
55219ed4a179ccea6915f1852139d520dddba779490Chong Zhang
553bdfd91024767893829017918ab565f224ccd35ecChong Zhang        int getColor() {
554bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mColor;
555bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
556bdfd91024767893829017918ab565f224ccd35ecChong Zhang
557bdfd91024767893829017918ab565f224ccd35ecChong Zhang        @Override
558bdfd91024767893829017918ab565f224ccd35ecChong Zhang        public String toString() {
559bdfd91024767893829017918ab565f224ccd35ecChong Zhang            StringBuilder str = new StringBuilder();
560bdfd91024767893829017918ab565f224ccd35ecChong Zhang            str.append("{");
561bdfd91024767893829017918ab565f224ccd35ecChong Zhang            str.append(mColorMap[mColor]);
562bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((mStyle & STYLE_ITALICS) != 0) {
563bdfd91024767893829017918ab565f224ccd35ecChong Zhang                str.append(", ITALICS");
564bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
565bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((mStyle & STYLE_UNDERLINE) != 0) {
566bdfd91024767893829017918ab565f224ccd35ecChong Zhang                str.append(", UNDERLINE");
567bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
568bdfd91024767893829017918ab565f224ccd35ecChong Zhang            str.append("}");
569bdfd91024767893829017918ab565f224ccd35ecChong Zhang
570bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return str.toString();
571bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
572bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
573bdfd91024767893829017918ab565f224ccd35ecChong Zhang
574bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static class PAC extends StyleCode {
575bdfd91024767893829017918ab565f224ccd35ecChong Zhang        final int mRow;
576bdfd91024767893829017918ab565f224ccd35ecChong Zhang        final int mCol;
577bdfd91024767893829017918ab565f224ccd35ecChong Zhang
578bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static PAC fromBytes(byte data1, byte data2) {
579bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int[] rowTable = {11, 1, 3, 12, 14, 5, 7, 9};
580bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int row = rowTable[data1 & 0x07] + ((data2 & 0x20) >> 5);
581bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int style = 0;
582bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((data2 & 1) != 0) {
583bdfd91024767893829017918ab565f224ccd35ecChong Zhang                style |= STYLE_UNDERLINE;
584bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
585bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((data2 & 0x10) != 0) {
586bdfd91024767893829017918ab565f224ccd35ecChong Zhang                // indent code
587bdfd91024767893829017918ab565f224ccd35ecChong Zhang                int indent = (data2 >> 1) & 0x7;
588bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return new PAC(row, indent * 4, style, COLOR_WHITE);
589bdfd91024767893829017918ab565f224ccd35ecChong Zhang            } else {
590bdfd91024767893829017918ab565f224ccd35ecChong Zhang                // style code
591bdfd91024767893829017918ab565f224ccd35ecChong Zhang                int color = (data2 >> 1) & 0x7;
592bdfd91024767893829017918ab565f224ccd35ecChong Zhang
593bdfd91024767893829017918ab565f224ccd35ecChong Zhang                if (color == COLOR_INVALID) {
594bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    // WHITE ITALICS
595bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    color = COLOR_WHITE;
596bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    style |= STYLE_ITALICS;
597bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
598bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return new PAC(row, -1, style, color);
599bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
600bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
601bdfd91024767893829017918ab565f224ccd35ecChong Zhang
602bdfd91024767893829017918ab565f224ccd35ecChong Zhang        PAC(int row, int col, int style, int color) {
603bdfd91024767893829017918ab565f224ccd35ecChong Zhang            super(style, color);
604bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mRow = row;
605bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mCol = col;
606bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
607bdfd91024767893829017918ab565f224ccd35ecChong Zhang
608bdfd91024767893829017918ab565f224ccd35ecChong Zhang        boolean isIndentPAC() {
609bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return (mCol >= 0);
610bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
611bdfd91024767893829017918ab565f224ccd35ecChong Zhang
612bdfd91024767893829017918ab565f224ccd35ecChong Zhang        int getRow() {
613bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mRow;
614bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
615bdfd91024767893829017918ab565f224ccd35ecChong Zhang
616bdfd91024767893829017918ab565f224ccd35ecChong Zhang        int getCol() {
617bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mCol;
618bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
619bdfd91024767893829017918ab565f224ccd35ecChong Zhang
620bdfd91024767893829017918ab565f224ccd35ecChong Zhang        @Override
621bdfd91024767893829017918ab565f224ccd35ecChong Zhang        public String toString() {
622bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return String.format("{%d, %d}, %s",
623bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    mRow, mCol, super.toString());
624bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
625bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
626bdfd91024767893829017918ab565f224ccd35ecChong Zhang
627978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    /**
628978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung     * Mutable version of BackgroundSpan to facilitate text rendering with edge styles.
629978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung     *
630978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung     * @hide
631978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung     */
632978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public static class MutableBackgroundColorSpan extends CharacterStyle
633978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            implements UpdateAppearance {
634978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        private int mColor;
635978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
636978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        public MutableBackgroundColorSpan(int color) {
637978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            mColor = color;
638978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        }
639978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
640978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        public void setBackgroundColor(int color) {
641978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            mColor = color;
642978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        }
643978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
644978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        public int getBackgroundColor() {
645978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            return mColor;
646978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        }
647978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
648978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        @Override
649978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        public void updateDrawState(TextPaint ds) {
650978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            ds.bgColor = mColor;
651978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        }
652978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    }
653978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
654bdfd91024767893829017918ab565f224ccd35ecChong Zhang    /* CCLineBuilder keeps track of displayable chars, as well as
655bdfd91024767893829017918ab565f224ccd35ecChong Zhang     * MidRow styles and PACs, for a single line of CC memory.
656bdfd91024767893829017918ab565f224ccd35ecChong Zhang     *
657bdfd91024767893829017918ab565f224ccd35ecChong Zhang     * It generates styled text via getStyledText() method.
658bdfd91024767893829017918ab565f224ccd35ecChong Zhang     */
659bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static class CCLineBuilder {
660bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private final StringBuilder mDisplayChars;
661bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private final StyleCode[] mMidRowStyles;
662bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private final StyleCode[] mPACStyles;
663bdfd91024767893829017918ab565f224ccd35ecChong Zhang
664bdfd91024767893829017918ab565f224ccd35ecChong Zhang        CCLineBuilder(String str) {
665bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mDisplayChars = new StringBuilder(str);
666bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mMidRowStyles = new StyleCode[mDisplayChars.length()];
667bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mPACStyles = new StyleCode[mDisplayChars.length()];
668bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
669bdfd91024767893829017918ab565f224ccd35ecChong Zhang
670bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void setCharAt(int index, char ch) {
671bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mDisplayChars.setCharAt(index, ch);
672bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mMidRowStyles[index] = null;
673bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
674bdfd91024767893829017918ab565f224ccd35ecChong Zhang
675bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void setMidRowAt(int index, StyleCode m) {
676bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mDisplayChars.setCharAt(index, ' ');
677bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mMidRowStyles[index] = m;
678bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
679bdfd91024767893829017918ab565f224ccd35ecChong Zhang
680bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void setPACAt(int index, PAC pac) {
681bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mPACStyles[index] = pac;
682bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
683bdfd91024767893829017918ab565f224ccd35ecChong Zhang
684bdfd91024767893829017918ab565f224ccd35ecChong Zhang        char charAt(int index) {
685bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mDisplayChars.charAt(index);
686bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
687bdfd91024767893829017918ab565f224ccd35ecChong Zhang
688bdfd91024767893829017918ab565f224ccd35ecChong Zhang        int length() {
689bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mDisplayChars.length();
690bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
691bdfd91024767893829017918ab565f224ccd35ecChong Zhang
692bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void applyStyleSpan(
693bdfd91024767893829017918ab565f224ccd35ecChong Zhang                SpannableStringBuilder styledText,
694bdfd91024767893829017918ab565f224ccd35ecChong Zhang                StyleCode s, int start, int end) {
695bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (s.isItalics()) {
696bdfd91024767893829017918ab565f224ccd35ecChong Zhang                styledText.setSpan(
697bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        new StyleSpan(android.graphics.Typeface.ITALIC),
698bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
699bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
70019ed4a179ccea6915f1852139d520dddba779490Chong Zhang            if (s.isUnderline()) {
70119ed4a179ccea6915f1852139d520dddba779490Chong Zhang                styledText.setSpan(
70219ed4a179ccea6915f1852139d520dddba779490Chong Zhang                        new UnderlineSpan(),
70319ed4a179ccea6915f1852139d520dddba779490Chong Zhang                        start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
70419ed4a179ccea6915f1852139d520dddba779490Chong Zhang            }
705bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
706bdfd91024767893829017918ab565f224ccd35ecChong Zhang
707bdfd91024767893829017918ab565f224ccd35ecChong Zhang        SpannableStringBuilder getStyledText(CaptionStyle captionStyle) {
708bdfd91024767893829017918ab565f224ccd35ecChong Zhang            SpannableStringBuilder styledText = new SpannableStringBuilder(mDisplayChars);
709bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int start = -1, next = 0;
710bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int styleStart = -1;
711bdfd91024767893829017918ab565f224ccd35ecChong Zhang            StyleCode curStyle = null;
712bdfd91024767893829017918ab565f224ccd35ecChong Zhang            while (next < mDisplayChars.length()) {
713bdfd91024767893829017918ab565f224ccd35ecChong Zhang                StyleCode newStyle = null;
714bdfd91024767893829017918ab565f224ccd35ecChong Zhang                if (mMidRowStyles[next] != null) {
715bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    // apply mid-row style change
716bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    newStyle = mMidRowStyles[next];
717bdfd91024767893829017918ab565f224ccd35ecChong Zhang                } else if (mPACStyles[next] != null
718bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && (styleStart < 0 || start < 0)) {
719bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    // apply PAC style change, only if:
720bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    // 1. no style set, or
721bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    // 2. style set, but prev char is none-displayable
722bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    newStyle = mPACStyles[next];
723bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
724bdfd91024767893829017918ab565f224ccd35ecChong Zhang                if (newStyle != null) {
725bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    curStyle = newStyle;
726bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    if (styleStart >= 0 && start >= 0) {
727bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        applyStyleSpan(styledText, newStyle, styleStart, next);
728bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    }
729bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    styleStart = next;
730bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
731bdfd91024767893829017918ab565f224ccd35ecChong Zhang
732bdfd91024767893829017918ab565f224ccd35ecChong Zhang                if (mDisplayChars.charAt(next) != TS) {
733bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    if (start < 0) {
734bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        start = next;
735bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    }
736bdfd91024767893829017918ab565f224ccd35ecChong Zhang                } else if (start >= 0) {
737bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    int expandedStart = mDisplayChars.charAt(start) == ' ' ? start : start - 1;
738bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    int expandedEnd = mDisplayChars.charAt(next - 1) == ' ' ? next : next + 1;
739bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    styledText.setSpan(
74019ed4a179ccea6915f1852139d520dddba779490Chong Zhang                            new MutableBackgroundColorSpan(captionStyle.backgroundColor),
741bdfd91024767893829017918ab565f224ccd35ecChong Zhang                            expandedStart, expandedEnd,
742bdfd91024767893829017918ab565f224ccd35ecChong Zhang                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
743bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    if (styleStart >= 0) {
744bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        applyStyleSpan(styledText, curStyle, styleStart, expandedEnd);
745bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    }
746bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    start = -1;
747bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
748bdfd91024767893829017918ab565f224ccd35ecChong Zhang                next++;
749bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
750bdfd91024767893829017918ab565f224ccd35ecChong Zhang
751bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return styledText;
752bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
753bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
754bdfd91024767893829017918ab565f224ccd35ecChong Zhang
755bdfd91024767893829017918ab565f224ccd35ecChong Zhang    /*
756bdfd91024767893829017918ab565f224ccd35ecChong Zhang     * CCMemory models a console-style display.
757bdfd91024767893829017918ab565f224ccd35ecChong Zhang     */
758bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static class CCMemory {
759bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private final String mBlankLine;
760bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private final CCLineBuilder[] mLines = new CCLineBuilder[MAX_ROWS + 2];
761bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private int mRow;
762bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private int mCol;
763bdfd91024767893829017918ab565f224ccd35ecChong Zhang
764bdfd91024767893829017918ab565f224ccd35ecChong Zhang        CCMemory() {
765bdfd91024767893829017918ab565f224ccd35ecChong Zhang            char[] blank = new char[MAX_COLS + 2];
766bdfd91024767893829017918ab565f224ccd35ecChong Zhang            Arrays.fill(blank, TS);
767bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mBlankLine = new String(blank);
768bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
769bdfd91024767893829017918ab565f224ccd35ecChong Zhang
770bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void erase() {
771bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // erase all lines
772bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (int i = 0; i < mLines.length; i++) {
773bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLines[i] = null;
774bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
775bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mRow = MAX_ROWS;
776bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mCol = 1;
777bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
778bdfd91024767893829017918ab565f224ccd35ecChong Zhang
779bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void der() {
780bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mLines[mRow] != null) {
781bdfd91024767893829017918ab565f224ccd35ecChong Zhang                for (int i = 0; i < mCol; i++) {
782bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    if (mLines[mRow].charAt(i) != TS) {
783bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        for (int j = mCol; j < mLines[mRow].length(); j++) {
784bdfd91024767893829017918ab565f224ccd35ecChong Zhang                            mLines[j].setCharAt(j, TS);
785bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        }
786bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        return;
787bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    }
788bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
789bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLines[mRow] = null;
790bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
791bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
792bdfd91024767893829017918ab565f224ccd35ecChong Zhang
793bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void tab(int tabs) {
794bdfd91024767893829017918ab565f224ccd35ecChong Zhang            moveCursorByCol(tabs);
795bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
796bdfd91024767893829017918ab565f224ccd35ecChong Zhang
797bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void bs() {
798bdfd91024767893829017918ab565f224ccd35ecChong Zhang            moveCursorByCol(-1);
799bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mLines[mRow] != null) {
800bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLines[mRow].setCharAt(mCol, TS);
801bdfd91024767893829017918ab565f224ccd35ecChong Zhang                if (mCol == MAX_COLS - 1) {
802bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    // Spec recommendation:
803bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    // if cursor was at col 32, move cursor
804bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    // back to col 31 and erase both col 31&32
805bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    mLines[mRow].setCharAt(MAX_COLS, TS);
806bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
807bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
808bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
809bdfd91024767893829017918ab565f224ccd35ecChong Zhang
810bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void cr() {
811bdfd91024767893829017918ab565f224ccd35ecChong Zhang            moveCursorTo(mRow + 1, 1);
812bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
813bdfd91024767893829017918ab565f224ccd35ecChong Zhang
814bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void rollUp(int windowSize) {
815bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int i;
816bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (i = 0; i <= mRow - windowSize; i++) {
817bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLines[i] = null;
818bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
819bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int startRow = mRow - windowSize + 1;
820bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (startRow < 1) {
821bdfd91024767893829017918ab565f224ccd35ecChong Zhang                startRow = 1;
822bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
823bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (i = startRow; i < mRow; i++) {
824bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLines[i] = mLines[i + 1];
825bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
826bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (i = mRow; i < mLines.length; i++) {
827bdfd91024767893829017918ab565f224ccd35ecChong Zhang                // clear base row
828bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLines[i] = null;
829bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
830bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // default to col 1, in case PAC is not sent
831bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mCol = 1;
832bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
833bdfd91024767893829017918ab565f224ccd35ecChong Zhang
834bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void writeText(String text) {
835bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (int i = 0; i < text.length(); i++) {
836bdfd91024767893829017918ab565f224ccd35ecChong Zhang                getLineBuffer(mRow).setCharAt(mCol, text.charAt(i));
837bdfd91024767893829017918ab565f224ccd35ecChong Zhang                moveCursorByCol(1);
838bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
839bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
840bdfd91024767893829017918ab565f224ccd35ecChong Zhang
841bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void writeMidRowCode(StyleCode m) {
842bdfd91024767893829017918ab565f224ccd35ecChong Zhang            getLineBuffer(mRow).setMidRowAt(mCol, m);
843bdfd91024767893829017918ab565f224ccd35ecChong Zhang            moveCursorByCol(1);
844bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
845bdfd91024767893829017918ab565f224ccd35ecChong Zhang
846bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void writePAC(PAC pac) {
847bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (pac.isIndentPAC()) {
848bdfd91024767893829017918ab565f224ccd35ecChong Zhang                moveCursorTo(pac.getRow(), pac.getCol());
849bdfd91024767893829017918ab565f224ccd35ecChong Zhang            } else {
8506286f8ec05ff213f52169a6828aaae3ffd1f20caChong Zhang                moveCursorTo(pac.getRow(), 1);
851bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
852bdfd91024767893829017918ab565f224ccd35ecChong Zhang            getLineBuffer(mRow).setPACAt(mCol, pac);
853bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
854bdfd91024767893829017918ab565f224ccd35ecChong Zhang
855bdfd91024767893829017918ab565f224ccd35ecChong Zhang        SpannableStringBuilder[] getStyledText(CaptionStyle captionStyle) {
856978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            ArrayList<SpannableStringBuilder> rows = new ArrayList<>(MAX_ROWS);
857bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (int i = 1; i <= MAX_ROWS; i++) {
858bdfd91024767893829017918ab565f224ccd35ecChong Zhang                rows.add(mLines[i] != null ?
859bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        mLines[i].getStyledText(captionStyle) : null);
860bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
861bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return rows.toArray(new SpannableStringBuilder[MAX_ROWS]);
862bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
863bdfd91024767893829017918ab565f224ccd35ecChong Zhang
864bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private static int clamp(int x, int min, int max) {
865bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return x < min ? min : (x > max ? max : x);
866bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
867bdfd91024767893829017918ab565f224ccd35ecChong Zhang
868bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private void moveCursorTo(int row, int col) {
869bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mRow = clamp(row, 1, MAX_ROWS);
870bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mCol = clamp(col, 1, MAX_COLS);
871bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
872bdfd91024767893829017918ab565f224ccd35ecChong Zhang
873bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private void moveCursorToRow(int row) {
874bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mRow = clamp(row, 1, MAX_ROWS);
875bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
876bdfd91024767893829017918ab565f224ccd35ecChong Zhang
877bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private void moveCursorByCol(int col) {
878bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mCol = clamp(mCol + col, 1, MAX_COLS);
879bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
880bdfd91024767893829017918ab565f224ccd35ecChong Zhang
881bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private void moveBaselineTo(int baseRow, int windowSize) {
882bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mRow == baseRow) {
883bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return;
884bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
885bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int actualWindowSize = windowSize;
886bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (baseRow < actualWindowSize) {
887bdfd91024767893829017918ab565f224ccd35ecChong Zhang                actualWindowSize = baseRow;
888bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
889bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mRow < actualWindowSize) {
890bdfd91024767893829017918ab565f224ccd35ecChong Zhang                actualWindowSize = mRow;
891bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
892bdfd91024767893829017918ab565f224ccd35ecChong Zhang
893bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int i;
894bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (baseRow < mRow) {
895bdfd91024767893829017918ab565f224ccd35ecChong Zhang                // copy from bottom to top row
896bdfd91024767893829017918ab565f224ccd35ecChong Zhang                for (i = actualWindowSize - 1; i >= 0; i--) {
897bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    mLines[baseRow - i] = mLines[mRow - i];
898bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
899bdfd91024767893829017918ab565f224ccd35ecChong Zhang            } else {
900bdfd91024767893829017918ab565f224ccd35ecChong Zhang                // copy from top to bottom row
901bdfd91024767893829017918ab565f224ccd35ecChong Zhang                for (i = 0; i < actualWindowSize; i++) {
902bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    mLines[baseRow - i] = mLines[mRow - i];
903bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
904bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
905bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // clear rest of the rows
906bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (i = 0; i <= baseRow - windowSize; i++) {
907bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLines[i] = null;
908bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
909bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (i = baseRow + 1; i < mLines.length; i++) {
910bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLines[i] = null;
911bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
912bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
913bdfd91024767893829017918ab565f224ccd35ecChong Zhang
914bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private CCLineBuilder getLineBuffer(int row) {
915bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mLines[row] == null) {
916bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLines[row] = new CCLineBuilder(mBlankLine);
917bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
918bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mLines[row];
919bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
920bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
921bdfd91024767893829017918ab565f224ccd35ecChong Zhang
922bdfd91024767893829017918ab565f224ccd35ecChong Zhang    /*
923bdfd91024767893829017918ab565f224ccd35ecChong Zhang     * CCData parses the raw CC byte pair into displayable chars,
924bdfd91024767893829017918ab565f224ccd35ecChong Zhang     * misc control codes, Mid-Row or Preamble Address Codes.
925bdfd91024767893829017918ab565f224ccd35ecChong Zhang     */
926bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static class CCData {
927bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private final byte mType;
928bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private final byte mData1;
929bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private final byte mData2;
930bdfd91024767893829017918ab565f224ccd35ecChong Zhang
931bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private static final String[] mCtrlCodeMap = {
932bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "RCL", "BS" , "AOF", "AON",
933bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "DER", "RU2", "RU3", "RU4",
934bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "FON", "RDC", "TR" , "RTD",
935bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "EDM", "CR" , "ENM", "EOC",
936bdfd91024767893829017918ab565f224ccd35ecChong Zhang        };
937bdfd91024767893829017918ab565f224ccd35ecChong Zhang
938bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private static final String[] mSpecialCharMap = {
939bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00AE",
940bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00B0",
941bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00BD",
942bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00BF",
943bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u2122",
944bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00A2",
945bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00A3",
946bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u266A", // Eighth note
947bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00E0",
948bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00A0", // Transparent space
949bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00E8",
950bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00E2",
951bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00EA",
952bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00EE",
953bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00F4",
954bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00FB",
955bdfd91024767893829017918ab565f224ccd35ecChong Zhang        };
956bdfd91024767893829017918ab565f224ccd35ecChong Zhang
957bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private static final String[] mSpanishCharMap = {
958bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // Spanish and misc chars
959bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00C1", // A
960bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00C9", // E
961bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00D3", // I
962bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00DA", // O
963bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00DC", // U
964bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00FC", // u
965bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u2018", // opening single quote
966bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00A1", // inverted exclamation mark
967bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "*",
968bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "'",
969bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u2014", // em dash
970bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00A9", // Copyright
971bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u2120", // Servicemark
972bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u2022", // round bullet
973bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u201C", // opening double quote
974bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u201D", // closing double quote
975bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // French
976bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00C0",
977bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00C2",
978bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00C7",
979bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00C8",
980bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00CA",
981bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00CB",
982bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00EB",
983bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00CE",
984bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00CF",
985bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00EF",
986bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00D4",
987bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00D9",
988bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00F9",
989bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00DB",
990bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00AB",
991bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00BB"
992bdfd91024767893829017918ab565f224ccd35ecChong Zhang        };
993bdfd91024767893829017918ab565f224ccd35ecChong Zhang
994bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private static final String[] mProtugueseCharMap = {
995bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // Portuguese
996bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00C3",
997bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00E3",
998bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00CD",
999bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00CC",
1000bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00EC",
1001bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00D2",
1002bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00F2",
1003bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00D5",
1004bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00F5",
1005bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "{",
1006bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "}",
1007bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\\",
1008bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "^",
1009bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "_",
1010bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "|",
1011bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "~",
1012bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // German and misc chars
1013bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00C4",
1014bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00E4",
1015bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00D6",
1016bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00F6",
1017bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00DF",
1018bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00A5",
1019bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00A4",
1020bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u2502", // vertical bar
1021bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00C5",
1022bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00E5",
1023bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00D8",
1024bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u00F8",
1025bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u250C", // top-left corner
1026bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u2510", // top-right corner
1027bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u2514", // lower-left corner
1028bdfd91024767893829017918ab565f224ccd35ecChong Zhang            "\u2518", // lower-right corner
1029bdfd91024767893829017918ab565f224ccd35ecChong Zhang        };
1030bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1031bdfd91024767893829017918ab565f224ccd35ecChong Zhang        static CCData[] fromByteArray(byte[] data) {
1032bdfd91024767893829017918ab565f224ccd35ecChong Zhang            CCData[] ccData = new CCData[data.length / 3];
1033bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1034bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (int i = 0; i < ccData.length; i++) {
1035bdfd91024767893829017918ab565f224ccd35ecChong Zhang                ccData[i] = new CCData(
1036bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        data[i * 3],
1037bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        data[i * 3 + 1],
1038bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        data[i * 3 + 2]);
1039bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1040bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1041bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return ccData;
1042bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1043bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1044bdfd91024767893829017918ab565f224ccd35ecChong Zhang        CCData(byte type, byte data1, byte data2) {
1045bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mType = type;
1046bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mData1 = data1;
1047bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mData2 = data2;
1048bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1049bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1050bdfd91024767893829017918ab565f224ccd35ecChong Zhang        int getCtrlCode() {
1051bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((mData1 == 0x14 || mData1 == 0x1c)
1052bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && mData2 >= 0x20 && mData2 <= 0x2f) {
1053bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return mData2;
1054bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1055bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return INVALID;
1056bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1057bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1058bdfd91024767893829017918ab565f224ccd35ecChong Zhang        StyleCode getMidRow() {
1059bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // only support standard Mid-row codes, ignore
1060bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // optional background/foreground mid-row codes
1061bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((mData1 == 0x11 || mData1 == 0x19)
1062bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && mData2 >= 0x20 && mData2 <= 0x2f) {
1063bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return StyleCode.fromByte(mData2);
1064bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1065bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return null;
1066bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1067bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1068bdfd91024767893829017918ab565f224ccd35ecChong Zhang        PAC getPAC() {
1069bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((mData1 & 0x70) == 0x10
1070bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && (mData2 & 0x40) == 0x40
1071bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && ((mData1 & 0x07) != 0 || (mData2 & 0x20) == 0)) {
1072bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return PAC.fromBytes(mData1, mData2);
1073bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1074bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return null;
1075bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1076bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1077bdfd91024767893829017918ab565f224ccd35ecChong Zhang        int getTabOffset() {
1078bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((mData1 == 0x17 || mData1 == 0x1f)
1079bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && mData2 >= 0x21 && mData2 <= 0x23) {
1080bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return mData2 & 0x3;
1081bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1082bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return 0;
1083bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1084bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1085bdfd91024767893829017918ab565f224ccd35ecChong Zhang        boolean isDisplayableChar() {
1086bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return isBasicChar() || isSpecialChar() || isExtendedChar();
1087bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1088bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1089bdfd91024767893829017918ab565f224ccd35ecChong Zhang        String getDisplayText() {
1090bdfd91024767893829017918ab565f224ccd35ecChong Zhang            String str = getBasicChars();
1091bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1092bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (str == null) {
1093bdfd91024767893829017918ab565f224ccd35ecChong Zhang                str =  getSpecialChar();
1094bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1095bdfd91024767893829017918ab565f224ccd35ecChong Zhang                if (str == null) {
1096bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    str = getExtendedChar();
1097bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
1098bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1099bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1100bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return str;
1101bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1102bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1103bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private String ctrlCodeToString(int ctrlCode) {
1104bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mCtrlCodeMap[ctrlCode - 0x20];
1105bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1106bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1107bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private boolean isBasicChar() {
1108bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return mData1 >= 0x20 && mData1 <= 0x7f;
1109bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1110bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1111bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private boolean isSpecialChar() {
1112bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return ((mData1 == 0x11 || mData1 == 0x19)
1113bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && mData2 >= 0x30 && mData2 <= 0x3f);
1114bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1115bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1116bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private boolean isExtendedChar() {
1117bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return ((mData1 == 0x12 || mData1 == 0x1A
1118bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    || mData1 == 0x13 || mData1 == 0x1B)
1119bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && mData2 >= 0x20 && mData2 <= 0x3f);
1120bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1121bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1122bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private char getBasicChar(byte data) {
1123bdfd91024767893829017918ab565f224ccd35ecChong Zhang            char c;
1124bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // replace the non-ASCII ones
1125bdfd91024767893829017918ab565f224ccd35ecChong Zhang            switch (data) {
1126bdfd91024767893829017918ab565f224ccd35ecChong Zhang                case 0x2A: c = '\u00E1'; break;
1127bdfd91024767893829017918ab565f224ccd35ecChong Zhang                case 0x5C: c = '\u00E9'; break;
1128bdfd91024767893829017918ab565f224ccd35ecChong Zhang                case 0x5E: c = '\u00ED'; break;
1129bdfd91024767893829017918ab565f224ccd35ecChong Zhang                case 0x5F: c = '\u00F3'; break;
1130bdfd91024767893829017918ab565f224ccd35ecChong Zhang                case 0x60: c = '\u00FA'; break;
1131bdfd91024767893829017918ab565f224ccd35ecChong Zhang                case 0x7B: c = '\u00E7'; break;
1132bdfd91024767893829017918ab565f224ccd35ecChong Zhang                case 0x7C: c = '\u00F7'; break;
1133bdfd91024767893829017918ab565f224ccd35ecChong Zhang                case 0x7D: c = '\u00D1'; break;
1134bdfd91024767893829017918ab565f224ccd35ecChong Zhang                case 0x7E: c = '\u00F1'; break;
1135bdfd91024767893829017918ab565f224ccd35ecChong Zhang                case 0x7F: c = '\u2588'; break; // Full block
1136bdfd91024767893829017918ab565f224ccd35ecChong Zhang                default: c = (char) data; break;
1137bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1138bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return c;
1139bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1140bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1141bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private String getBasicChars() {
1142bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mData1 >= 0x20 && mData1 <= 0x7f) {
1143bdfd91024767893829017918ab565f224ccd35ecChong Zhang                StringBuilder builder = new StringBuilder(2);
1144bdfd91024767893829017918ab565f224ccd35ecChong Zhang                builder.append(getBasicChar(mData1));
1145bdfd91024767893829017918ab565f224ccd35ecChong Zhang                if (mData2 >= 0x20 && mData2 <= 0x7f) {
1146bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    builder.append(getBasicChar(mData2));
1147bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
1148bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return builder.toString();
1149bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1150bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1151bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return null;
1152bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1153bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1154bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private String getSpecialChar() {
1155bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((mData1 == 0x11 || mData1 == 0x19)
1156bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && mData2 >= 0x30 && mData2 <= 0x3f) {
1157bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return mSpecialCharMap[mData2 - 0x30];
1158bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1159bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1160bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return null;
1161bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1162bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1163bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private String getExtendedChar() {
1164bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if ((mData1 == 0x12 || mData1 == 0x1A)
1165bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && mData2 >= 0x20 && mData2 <= 0x3f){
1166bdfd91024767893829017918ab565f224ccd35ecChong Zhang                // 1 Spanish/French char
1167bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return mSpanishCharMap[mData2 - 0x20];
1168bdfd91024767893829017918ab565f224ccd35ecChong Zhang            } else if ((mData1 == 0x13 || mData1 == 0x1B)
1169bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    && mData2 >= 0x20 && mData2 <= 0x3f){
1170bdfd91024767893829017918ab565f224ccd35ecChong Zhang                // 1 Portuguese/German/Danish char
1171bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return mProtugueseCharMap[mData2 - 0x20];
1172bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1173bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1174bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return null;
1175bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1176bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1177bdfd91024767893829017918ab565f224ccd35ecChong Zhang        @Override
1178bdfd91024767893829017918ab565f224ccd35ecChong Zhang        public String toString() {
1179bdfd91024767893829017918ab565f224ccd35ecChong Zhang            String str;
1180bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1181bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (mData1 < 0x10 && mData2 < 0x10) {
1182bdfd91024767893829017918ab565f224ccd35ecChong Zhang                // Null Pad, ignore
1183bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return String.format("[%d]Null: %02x %02x", mType, mData1, mData2);
1184bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1185bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1186bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int ctrlCode = getCtrlCode();
1187bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (ctrlCode != INVALID) {
1188bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return String.format("[%d]%s", mType, ctrlCodeToString(ctrlCode));
1189bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1190bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1191bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int tabOffset = getTabOffset();
1192bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (tabOffset > 0) {
1193bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return String.format("[%d]Tab%d", mType, tabOffset);
1194bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1195bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1196bdfd91024767893829017918ab565f224ccd35ecChong Zhang            PAC pac = getPAC();
1197bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (pac != null) {
1198bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return String.format("[%d]PAC: %s", mType, pac.toString());
1199bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1200bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1201bdfd91024767893829017918ab565f224ccd35ecChong Zhang            StyleCode m = getMidRow();
1202bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (m != null) {
1203bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return String.format("[%d]Mid-row: %s", mType, m.toString());
1204bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1205bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1206bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (isDisplayableChar()) {
1207bdfd91024767893829017918ab565f224ccd35ecChong Zhang                return String.format("[%d]Displayable: %s (%02x %02x)",
1208bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        mType, getDisplayText(), mData1, mData2);
1209bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1210bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1211bdfd91024767893829017918ab565f224ccd35ecChong Zhang            return String.format("[%d]Invalid: %02x %02x", mType, mData1, mData2);
1212bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1213bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
1214bdfd91024767893829017918ab565f224ccd35ecChong Zhang}
1215bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1216bdfd91024767893829017918ab565f224ccd35ecChong Zhang/**
1217bdfd91024767893829017918ab565f224ccd35ecChong Zhang * Widget capable of rendering CEA-608 closed captions.
1218bdfd91024767893829017918ab565f224ccd35ecChong Zhang *
1219bdfd91024767893829017918ab565f224ccd35ecChong Zhang * @hide
1220bdfd91024767893829017918ab565f224ccd35ecChong Zhang */
1221978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chungclass Cea608CCWidget extends ClosedCaptionWidget implements Cea608CCParser.DisplayListener {
1222bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final Rect mTextBounds = new Rect();
1223bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static final String mDummyText = "1234567890123456789012345678901234";
1224bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1225978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public Cea608CCWidget(Context context) {
1226bdfd91024767893829017918ab565f224ccd35ecChong Zhang        this(context, null);
1227bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
1228bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1229978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public Cea608CCWidget(Context context, AttributeSet attrs) {
1230978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        this(context, attrs, 0);
1231bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
1232bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1233978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public Cea608CCWidget(Context context, AttributeSet attrs, int defStyle) {
1234978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        this(context, attrs, defStyle, 0);
1235bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
1236bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1237978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public Cea608CCWidget(Context context, AttributeSet attrs, int defStyleAttr,
1238978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            int defStyleRes) {
1239978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        super(context, attrs, defStyleAttr, defStyleRes);
1240bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
1241bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1242bdfd91024767893829017918ab565f224ccd35ecChong Zhang    @Override
1243978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    public ClosedCaptionLayout createCaptionLayout(Context context) {
1244978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        return new CCLayout(context);
1245bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
1246bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1247bdfd91024767893829017918ab565f224ccd35ecChong Zhang    @Override
1248bdfd91024767893829017918ab565f224ccd35ecChong Zhang    public void onDisplayChanged(SpannableStringBuilder[] styledTexts) {
1249978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        ((CCLayout) mClosedCaptionLayout).update(styledTexts);
1250bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1251bdfd91024767893829017918ab565f224ccd35ecChong Zhang        if (mListener != null) {
1252bdfd91024767893829017918ab565f224ccd35ecChong Zhang            mListener.onChanged(this);
1253bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1254bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
1255bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1256bdfd91024767893829017918ab565f224ccd35ecChong Zhang    @Override
1257bdfd91024767893829017918ab565f224ccd35ecChong Zhang    public CaptionStyle getCaptionStyle() {
1258bdfd91024767893829017918ab565f224ccd35ecChong Zhang        return mCaptionStyle;
1259bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
1260bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1261bdfd91024767893829017918ab565f224ccd35ecChong Zhang    private static class CCLineBox extends TextView {
1262bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private static final float FONT_PADDING_RATIO = 0.75f;
126319ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private static final float EDGE_OUTLINE_RATIO = 0.1f;
126419ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private static final float EDGE_SHADOW_RATIO = 0.05f;
126519ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private float mOutlineWidth;
126619ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private float mShadowRadius;
126719ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private float mShadowOffset;
126819ed4a179ccea6915f1852139d520dddba779490Chong Zhang
126919ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private int mTextColor = Color.WHITE;
127019ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private int mBgColor = Color.BLACK;
127119ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private int mEdgeType = CaptionStyle.EDGE_TYPE_NONE;
127219ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private int mEdgeColor = Color.TRANSPARENT;
1273bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1274bdfd91024767893829017918ab565f224ccd35ecChong Zhang        CCLineBox(Context context) {
1275bdfd91024767893829017918ab565f224ccd35ecChong Zhang            super(context);
1276bdfd91024767893829017918ab565f224ccd35ecChong Zhang            setGravity(Gravity.CENTER);
1277bdfd91024767893829017918ab565f224ccd35ecChong Zhang            setBackgroundColor(Color.TRANSPARENT);
1278bdfd91024767893829017918ab565f224ccd35ecChong Zhang            setTextColor(Color.WHITE);
1279bdfd91024767893829017918ab565f224ccd35ecChong Zhang            setTypeface(Typeface.MONOSPACE);
1280bdfd91024767893829017918ab565f224ccd35ecChong Zhang            setVisibility(View.INVISIBLE);
128119ed4a179ccea6915f1852139d520dddba779490Chong Zhang
128219ed4a179ccea6915f1852139d520dddba779490Chong Zhang            final Resources res = getContext().getResources();
128319ed4a179ccea6915f1852139d520dddba779490Chong Zhang
128419ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // get the default (will be updated later during measure)
128519ed4a179ccea6915f1852139d520dddba779490Chong Zhang            mOutlineWidth = res.getDimensionPixelSize(
128619ed4a179ccea6915f1852139d520dddba779490Chong Zhang                    com.android.internal.R.dimen.subtitle_outline_width);
128719ed4a179ccea6915f1852139d520dddba779490Chong Zhang            mShadowRadius = res.getDimensionPixelSize(
128819ed4a179ccea6915f1852139d520dddba779490Chong Zhang                    com.android.internal.R.dimen.subtitle_shadow_radius);
128919ed4a179ccea6915f1852139d520dddba779490Chong Zhang            mShadowOffset = res.getDimensionPixelSize(
129019ed4a179ccea6915f1852139d520dddba779490Chong Zhang                    com.android.internal.R.dimen.subtitle_shadow_offset);
1291bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1292bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1293bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void setCaptionStyle(CaptionStyle captionStyle) {
129419ed4a179ccea6915f1852139d520dddba779490Chong Zhang            mTextColor = captionStyle.foregroundColor;
129519ed4a179ccea6915f1852139d520dddba779490Chong Zhang            mBgColor = captionStyle.backgroundColor;
129619ed4a179ccea6915f1852139d520dddba779490Chong Zhang            mEdgeType = captionStyle.edgeType;
129719ed4a179ccea6915f1852139d520dddba779490Chong Zhang            mEdgeColor = captionStyle.edgeColor;
129819ed4a179ccea6915f1852139d520dddba779490Chong Zhang
129919ed4a179ccea6915f1852139d520dddba779490Chong Zhang            setTextColor(mTextColor);
130019ed4a179ccea6915f1852139d520dddba779490Chong Zhang            if (mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
130119ed4a179ccea6915f1852139d520dddba779490Chong Zhang                setShadowLayer(mShadowRadius, mShadowOffset, mShadowOffset, mEdgeColor);
130219ed4a179ccea6915f1852139d520dddba779490Chong Zhang            } else {
130319ed4a179ccea6915f1852139d520dddba779490Chong Zhang                setShadowLayer(0, 0, 0, 0);
130419ed4a179ccea6915f1852139d520dddba779490Chong Zhang            }
130519ed4a179ccea6915f1852139d520dddba779490Chong Zhang            invalidate();
1306bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1307bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1308bdfd91024767893829017918ab565f224ccd35ecChong Zhang        @Override
1309bdfd91024767893829017918ab565f224ccd35ecChong Zhang        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1310978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            float fontSize = MeasureSpec.getSize(heightMeasureSpec) * FONT_PADDING_RATIO;
1311bdfd91024767893829017918ab565f224ccd35ecChong Zhang            setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
1312bdfd91024767893829017918ab565f224ccd35ecChong Zhang
131319ed4a179ccea6915f1852139d520dddba779490Chong Zhang            mOutlineWidth = EDGE_OUTLINE_RATIO * fontSize + 1.0f;
131419ed4a179ccea6915f1852139d520dddba779490Chong Zhang            mShadowRadius = EDGE_SHADOW_RATIO * fontSize + 1.0f;;
131519ed4a179ccea6915f1852139d520dddba779490Chong Zhang            mShadowOffset = mShadowRadius;
131619ed4a179ccea6915f1852139d520dddba779490Chong Zhang
1317bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // set font scale in the X direction to match the required width
1318bdfd91024767893829017918ab565f224ccd35ecChong Zhang            setScaleX(1.0f);
1319bdfd91024767893829017918ab565f224ccd35ecChong Zhang            getPaint().getTextBounds(mDummyText, 0, mDummyText.length(), mTextBounds);
1320bdfd91024767893829017918ab565f224ccd35ecChong Zhang            float actualTextWidth = mTextBounds.width();
1321bdfd91024767893829017918ab565f224ccd35ecChong Zhang            float requiredTextWidth = MeasureSpec.getSize(widthMeasureSpec);
1322bdfd91024767893829017918ab565f224ccd35ecChong Zhang            setScaleX(requiredTextWidth / actualTextWidth);
1323bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1324bdfd91024767893829017918ab565f224ccd35ecChong Zhang            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1325bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
132619ed4a179ccea6915f1852139d520dddba779490Chong Zhang
132719ed4a179ccea6915f1852139d520dddba779490Chong Zhang        @Override
132819ed4a179ccea6915f1852139d520dddba779490Chong Zhang        protected void onDraw(Canvas c) {
132919ed4a179ccea6915f1852139d520dddba779490Chong Zhang            if (mEdgeType == CaptionStyle.EDGE_TYPE_UNSPECIFIED
133019ed4a179ccea6915f1852139d520dddba779490Chong Zhang                    || mEdgeType == CaptionStyle.EDGE_TYPE_NONE
133119ed4a179ccea6915f1852139d520dddba779490Chong Zhang                    || mEdgeType == CaptionStyle.EDGE_TYPE_DROP_SHADOW) {
133219ed4a179ccea6915f1852139d520dddba779490Chong Zhang                // these edge styles don't require a second pass
133319ed4a179ccea6915f1852139d520dddba779490Chong Zhang                super.onDraw(c);
133419ed4a179ccea6915f1852139d520dddba779490Chong Zhang                return;
133519ed4a179ccea6915f1852139d520dddba779490Chong Zhang            }
133619ed4a179ccea6915f1852139d520dddba779490Chong Zhang
133719ed4a179ccea6915f1852139d520dddba779490Chong Zhang            if (mEdgeType == CaptionStyle.EDGE_TYPE_OUTLINE) {
133819ed4a179ccea6915f1852139d520dddba779490Chong Zhang                drawEdgeOutline(c);
133919ed4a179ccea6915f1852139d520dddba779490Chong Zhang            } else {
134019ed4a179ccea6915f1852139d520dddba779490Chong Zhang                // Raised or depressed
134119ed4a179ccea6915f1852139d520dddba779490Chong Zhang                drawEdgeRaisedOrDepressed(c);
134219ed4a179ccea6915f1852139d520dddba779490Chong Zhang            }
134319ed4a179ccea6915f1852139d520dddba779490Chong Zhang        }
134419ed4a179ccea6915f1852139d520dddba779490Chong Zhang
134519ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private void drawEdgeOutline(Canvas c) {
134619ed4a179ccea6915f1852139d520dddba779490Chong Zhang            TextPaint textPaint = getPaint();
134719ed4a179ccea6915f1852139d520dddba779490Chong Zhang
134819ed4a179ccea6915f1852139d520dddba779490Chong Zhang            Paint.Style previousStyle = textPaint.getStyle();
134919ed4a179ccea6915f1852139d520dddba779490Chong Zhang            Paint.Join previousJoin = textPaint.getStrokeJoin();
135019ed4a179ccea6915f1852139d520dddba779490Chong Zhang            float previousWidth = textPaint.getStrokeWidth();
135119ed4a179ccea6915f1852139d520dddba779490Chong Zhang
135219ed4a179ccea6915f1852139d520dddba779490Chong Zhang            setTextColor(mEdgeColor);
135319ed4a179ccea6915f1852139d520dddba779490Chong Zhang            textPaint.setStyle(Paint.Style.FILL_AND_STROKE);
135419ed4a179ccea6915f1852139d520dddba779490Chong Zhang            textPaint.setStrokeJoin(Paint.Join.ROUND);
135519ed4a179ccea6915f1852139d520dddba779490Chong Zhang            textPaint.setStrokeWidth(mOutlineWidth);
135619ed4a179ccea6915f1852139d520dddba779490Chong Zhang
135719ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // Draw outline and background only.
135819ed4a179ccea6915f1852139d520dddba779490Chong Zhang            super.onDraw(c);
135919ed4a179ccea6915f1852139d520dddba779490Chong Zhang
136019ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // Restore original settings.
136119ed4a179ccea6915f1852139d520dddba779490Chong Zhang            setTextColor(mTextColor);
136219ed4a179ccea6915f1852139d520dddba779490Chong Zhang            textPaint.setStyle(previousStyle);
136319ed4a179ccea6915f1852139d520dddba779490Chong Zhang            textPaint.setStrokeJoin(previousJoin);
136419ed4a179ccea6915f1852139d520dddba779490Chong Zhang            textPaint.setStrokeWidth(previousWidth);
136519ed4a179ccea6915f1852139d520dddba779490Chong Zhang
136619ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // Remove the background.
136719ed4a179ccea6915f1852139d520dddba779490Chong Zhang            setBackgroundSpans(Color.TRANSPARENT);
136819ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // Draw foreground only.
136919ed4a179ccea6915f1852139d520dddba779490Chong Zhang            super.onDraw(c);
137019ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // Restore the background.
137119ed4a179ccea6915f1852139d520dddba779490Chong Zhang            setBackgroundSpans(mBgColor);
137219ed4a179ccea6915f1852139d520dddba779490Chong Zhang        }
137319ed4a179ccea6915f1852139d520dddba779490Chong Zhang
137419ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private void drawEdgeRaisedOrDepressed(Canvas c) {
137519ed4a179ccea6915f1852139d520dddba779490Chong Zhang            TextPaint textPaint = getPaint();
137619ed4a179ccea6915f1852139d520dddba779490Chong Zhang
137719ed4a179ccea6915f1852139d520dddba779490Chong Zhang            Paint.Style previousStyle = textPaint.getStyle();
137819ed4a179ccea6915f1852139d520dddba779490Chong Zhang            textPaint.setStyle(Paint.Style.FILL);
137919ed4a179ccea6915f1852139d520dddba779490Chong Zhang
138019ed4a179ccea6915f1852139d520dddba779490Chong Zhang            final boolean raised = mEdgeType == CaptionStyle.EDGE_TYPE_RAISED;
138119ed4a179ccea6915f1852139d520dddba779490Chong Zhang            final int colorUp = raised ? Color.WHITE : mEdgeColor;
138219ed4a179ccea6915f1852139d520dddba779490Chong Zhang            final int colorDown = raised ? mEdgeColor : Color.WHITE;
138319ed4a179ccea6915f1852139d520dddba779490Chong Zhang            final float offset = mShadowRadius / 2f;
138419ed4a179ccea6915f1852139d520dddba779490Chong Zhang
138519ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // Draw background and text with shadow up
138619ed4a179ccea6915f1852139d520dddba779490Chong Zhang            setShadowLayer(mShadowRadius, -offset, -offset, colorUp);
138719ed4a179ccea6915f1852139d520dddba779490Chong Zhang            super.onDraw(c);
138819ed4a179ccea6915f1852139d520dddba779490Chong Zhang
138919ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // Remove the background.
139019ed4a179ccea6915f1852139d520dddba779490Chong Zhang            setBackgroundSpans(Color.TRANSPARENT);
139119ed4a179ccea6915f1852139d520dddba779490Chong Zhang
139219ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // Draw text with shadow down
139319ed4a179ccea6915f1852139d520dddba779490Chong Zhang            setShadowLayer(mShadowRadius, +offset, +offset, colorDown);
139419ed4a179ccea6915f1852139d520dddba779490Chong Zhang            super.onDraw(c);
139519ed4a179ccea6915f1852139d520dddba779490Chong Zhang
139619ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // Restore settings
139719ed4a179ccea6915f1852139d520dddba779490Chong Zhang            textPaint.setStyle(previousStyle);
139819ed4a179ccea6915f1852139d520dddba779490Chong Zhang
139919ed4a179ccea6915f1852139d520dddba779490Chong Zhang            // Restore the background.
140019ed4a179ccea6915f1852139d520dddba779490Chong Zhang            setBackgroundSpans(mBgColor);
140119ed4a179ccea6915f1852139d520dddba779490Chong Zhang        }
140219ed4a179ccea6915f1852139d520dddba779490Chong Zhang
140319ed4a179ccea6915f1852139d520dddba779490Chong Zhang        private void setBackgroundSpans(int color) {
140419ed4a179ccea6915f1852139d520dddba779490Chong Zhang            CharSequence text = getText();
140519ed4a179ccea6915f1852139d520dddba779490Chong Zhang            if (text instanceof Spannable) {
140619ed4a179ccea6915f1852139d520dddba779490Chong Zhang                Spannable spannable = (Spannable) text;
1407978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung                Cea608CCParser.MutableBackgroundColorSpan[] bgSpans = spannable.getSpans(
1408978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung                        0, spannable.length(), Cea608CCParser.MutableBackgroundColorSpan.class);
140919ed4a179ccea6915f1852139d520dddba779490Chong Zhang                for (int i = 0; i < bgSpans.length; i++) {
141019ed4a179ccea6915f1852139d520dddba779490Chong Zhang                    bgSpans[i].setBackgroundColor(color);
141119ed4a179ccea6915f1852139d520dddba779490Chong Zhang                }
141219ed4a179ccea6915f1852139d520dddba779490Chong Zhang            }
141319ed4a179ccea6915f1852139d520dddba779490Chong Zhang        }
1414bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
1415bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1416978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung    private static class CCLayout extends LinearLayout implements ClosedCaptionLayout {
1417978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        private static final int MAX_ROWS = Cea608CCParser.MAX_ROWS;
1418bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private static final float SAFE_AREA_RATIO = 0.9f;
1419bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1420bdfd91024767893829017918ab565f224ccd35ecChong Zhang        private final CCLineBox[] mLineBoxes = new CCLineBox[MAX_ROWS];
1421bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1422bdfd91024767893829017918ab565f224ccd35ecChong Zhang        CCLayout(Context context) {
1423bdfd91024767893829017918ab565f224ccd35ecChong Zhang            super(context);
1424bdfd91024767893829017918ab565f224ccd35ecChong Zhang            setGravity(Gravity.START);
1425bdfd91024767893829017918ab565f224ccd35ecChong Zhang            setOrientation(LinearLayout.VERTICAL);
1426bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (int i = 0; i < MAX_ROWS; i++) {
1427bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLineBoxes[i] = new CCLineBox(getContext());
1428bdfd91024767893829017918ab565f224ccd35ecChong Zhang                addView(mLineBoxes[i], LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
1429bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1430bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1431bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1432978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        @Override
1433978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        public void setCaptionStyle(CaptionStyle captionStyle) {
1434bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (int i = 0; i < MAX_ROWS; i++) {
1435bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLineBoxes[i].setCaptionStyle(captionStyle);
1436bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1437bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1438bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1439978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        @Override
1440978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        public void setFontScale(float fontScale) {
1441978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung            // Ignores the font scale changes of the system wide CC preference.
1442978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung        }
1443978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung
1444bdfd91024767893829017918ab565f224ccd35ecChong Zhang        void update(SpannableStringBuilder[] textBuffer) {
1445bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (int i = 0; i < MAX_ROWS; i++) {
1446bdfd91024767893829017918ab565f224ccd35ecChong Zhang                if (textBuffer[i] != null) {
144719ed4a179ccea6915f1852139d520dddba779490Chong Zhang                    mLineBoxes[i].setText(textBuffer[i], TextView.BufferType.SPANNABLE);
1448bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    mLineBoxes[i].setVisibility(View.VISIBLE);
1449bdfd91024767893829017918ab565f224ccd35ecChong Zhang                } else {
1450bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    mLineBoxes[i].setVisibility(View.INVISIBLE);
1451bdfd91024767893829017918ab565f224ccd35ecChong Zhang                }
1452bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1453bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1454bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1455bdfd91024767893829017918ab565f224ccd35ecChong Zhang        @Override
1456bdfd91024767893829017918ab565f224ccd35ecChong Zhang        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1457bdfd91024767893829017918ab565f224ccd35ecChong Zhang            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1458bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1459bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int safeWidth = getMeasuredWidth();
1460bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int safeHeight = getMeasuredHeight();
1461bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1462bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // CEA-608 assumes 4:3 video
1463bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (safeWidth * 3 >= safeHeight * 4) {
1464bdfd91024767893829017918ab565f224ccd35ecChong Zhang                safeWidth = safeHeight * 4 / 3;
1465bdfd91024767893829017918ab565f224ccd35ecChong Zhang            } else {
1466bdfd91024767893829017918ab565f224ccd35ecChong Zhang                safeHeight = safeWidth * 3 / 4;
1467bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1468bdfd91024767893829017918ab565f224ccd35ecChong Zhang            safeWidth *= SAFE_AREA_RATIO;
1469bdfd91024767893829017918ab565f224ccd35ecChong Zhang            safeHeight *= SAFE_AREA_RATIO;
1470bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1471bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int lineHeight = safeHeight / MAX_ROWS;
1472bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int lineHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
1473bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    lineHeight, MeasureSpec.EXACTLY);
1474bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int lineWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
1475bdfd91024767893829017918ab565f224ccd35ecChong Zhang                    safeWidth, MeasureSpec.EXACTLY);
1476bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1477bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (int i = 0; i < MAX_ROWS; i++) {
1478bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLineBoxes[i].measure(lineWidthMeasureSpec, lineHeightMeasureSpec);
1479bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1480bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1481bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1482bdfd91024767893829017918ab565f224ccd35ecChong Zhang        @Override
1483bdfd91024767893829017918ab565f224ccd35ecChong Zhang        protected void onLayout(boolean changed, int l, int t, int r, int b) {
1484bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // safe caption area
1485bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int viewPortWidth = r - l;
1486bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int viewPortHeight = b - t;
1487bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int safeWidth, safeHeight;
1488bdfd91024767893829017918ab565f224ccd35ecChong Zhang            // CEA-608 assumes 4:3 video
1489bdfd91024767893829017918ab565f224ccd35ecChong Zhang            if (viewPortWidth * 3 >= viewPortHeight * 4) {
1490bdfd91024767893829017918ab565f224ccd35ecChong Zhang                safeWidth = viewPortHeight * 4 / 3;
1491bdfd91024767893829017918ab565f224ccd35ecChong Zhang                safeHeight = viewPortHeight;
1492bdfd91024767893829017918ab565f224ccd35ecChong Zhang            } else {
1493bdfd91024767893829017918ab565f224ccd35ecChong Zhang                safeWidth = viewPortWidth;
1494bdfd91024767893829017918ab565f224ccd35ecChong Zhang                safeHeight = viewPortWidth * 3 / 4;
1495bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1496bdfd91024767893829017918ab565f224ccd35ecChong Zhang            safeWidth *= SAFE_AREA_RATIO;
1497bdfd91024767893829017918ab565f224ccd35ecChong Zhang            safeHeight *= SAFE_AREA_RATIO;
1498bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int left = (viewPortWidth - safeWidth) / 2;
1499bdfd91024767893829017918ab565f224ccd35ecChong Zhang            int top = (viewPortHeight - safeHeight) / 2;
1500bdfd91024767893829017918ab565f224ccd35ecChong Zhang
1501bdfd91024767893829017918ab565f224ccd35ecChong Zhang            for (int i = 0; i < MAX_ROWS; i++) {
1502bdfd91024767893829017918ab565f224ccd35ecChong Zhang                mLineBoxes[i].layout(
1503bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        left,
1504bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        top + safeHeight * i / MAX_ROWS,
1505bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        left + safeWidth,
1506bdfd91024767893829017918ab565f224ccd35ecChong Zhang                        top + safeHeight * (i + 1) / MAX_ROWS);
1507bdfd91024767893829017918ab565f224ccd35ecChong Zhang            }
1508bdfd91024767893829017918ab565f224ccd35ecChong Zhang        }
1509bdfd91024767893829017918ab565f224ccd35ecChong Zhang    }
1510978bf5eff0f5bc8a8878696bc886d8b99a29ee39Jaesung Chung}
1511