11abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/*
21abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Copyright (C) 2015 The Android Open Source Project
31abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
41abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
51abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * you may not use this file except in compliance with the License.
61abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * You may obtain a copy of the License at
71abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
81abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
91abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Unless required by applicable law or agreed to in writing, software
111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * See the License for the specific language governing permissions and
141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * limitations under the License.
151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */
161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopackage com.android.tv.tuner.cc;
181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
191abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.os.SystemClock;
201abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.support.annotation.IntDef;
211abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.util.Log;
221abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport android.util.SparseIntArray;
231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.Cea708Data;
2565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.Cea708Data.CaptionColor;
2665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.Cea708Data.CaptionEvent;
2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.Cea708Data.CaptionPenAttr;
2865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.Cea708Data.CaptionPenColor;
2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.Cea708Data.CaptionPenLocation;
3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.Cea708Data.CaptionWindow;
3165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.Cea708Data.CaptionWindowAttr;
3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.Cea708Data.CcPacket;
3365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.util.ByteArrayBuffer;
341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
351abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.io.UnsupportedEncodingException;
361abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.lang.annotation.Retention;
371abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.lang.annotation.RetentionPolicy;
381abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.nio.ByteBuffer;
391abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.nio.charset.StandardCharsets;
401abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.Arrays;
411abddd9f6225298066094e20a6c29061b6af4590Nick Chalkoimport java.util.TreeSet;
421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko/**
441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * A class for parsing CEA-708, which is the standard for closed captioning for ATSC DTV.
451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>ATSC DTV closed caption data are carried on picture user data of video streams.
471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * This class starts to parse from picture user data payload, so extraction process of user_data
481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * from video streams is up to outside of this code.
491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>There are 4 steps to decode user_data to provide closed caption services.
511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <h3>Step 1. user_data -&gt; CcPacket ({@link #parseClosedCaption} method)</h3>
531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>First, user_data consists of cc_data packets, which are 3-byte segments. Here, CcPacket is a
551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * collection of cc_data packets in a frame along with same presentation timestamp. Because cc_data
561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * packets must be reassembled in the frame display order, CcPackets are reordered.
571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <h3>Step 2. CcPacket -&gt; DTVCC packet ({@link #parseCcPacket} method)</h3>
591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>Each cc_data packet has a one byte for declaring a type of itself and data validity, and the
611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * subsequent two bytes for input data of a DTVCC packet. There are 4 types for cc_data packet.
621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * We're interested in DTVCC_PACKET_START(type 3) and DTVCC_PACKET_DATA(type 2). Each DTVCC packet
631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * begins with DTVCC_PACKET_START(type 3) and the following cc_data packets which has
641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * DTVCC_PACKET_DATA(type 2) are appended into the DTVCC packet being assembled.
651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <h3>Step 3. DTVCC packet -&gt; Service Blocks ({@link #parseDtvCcPacket} method)</h3>
671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>A DTVCC packet consists of multiple service blocks. Each service block represents a caption
691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * track and has a service number, which ranges from 1 to 63, that denotes caption track identity.
701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * In here, we listen at most one chosen caption track by {@link #mListenServiceNumber}.
711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * Otherwise, just skip the other service blocks.
721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <h3>Step 4. Interpreting Service Block Data ({@link #parseServiceBlockData}, {@code parseXX},
741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * and {@link #parseExt1} methods)</h3>
751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>Service block data is actual caption stream. it looks similar to telnet. It uses most parts of
771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * ASCII table and consists of specially defined commands and some ASCII control codes which work
781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * in a behavior slightly different from their original purpose. ASCII control codes and caption
791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * commands are explicit instructions that control the state of a closed caption service and the
801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * other ASCII and text codes are implicit instructions that send their characters to buffer.
811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>There are 4 main code groups and 4 extended code groups. Both the range of code groups are the
831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * same as the range of a byte.
841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>4 main code groups: C0, C1, G0, G1
861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <br>4 extended code groups: C2, C3, G2, G3
871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>Each code group has its own handle method. For example, {@link #parseC0} handles C0 code group
891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * and so on. And {@link #parseServiceBlockData} method maps a stream on the main code groups while
901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * {@link #parseExt1} method maps on the extended code groups.
911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>The main code groups:
931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <ul>
941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <li>C0 - contains modified ASCII control codes. It is not intended by CEA-708 but Korea TTA
951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *      standard for ATSC CC uses P16 character heavily, which is unclear entity in CEA-708 doc,
961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *      even for the alphanumeric characters instead of ASCII characters.</li>
971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <li>C1 - contains the caption commands. There are 3 categories of a caption command.</li>
981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <ul>
991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <li>Window commands: The window commands control a caption window which is addressable area being
1001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *                  with in the Safe title area. (CWX, CLW, DSW, HDW, TGW, DLW, SWA, DFX)</li>
1011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <li>Pen commands: Th pen commands control text style and location. (SPA, SPC, SPL)</li>
1021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <li>Job commands: The job commands make a delay and recover from the delay. (DLY, DLC, RST)</li>
1031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * </ul>
1041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <li>G0 - same as printable ASCII character set except music note character.</li>
1051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <li>G1 - same as ISO 8859-1 Latin 1 character set.</li>
1061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * </ul>
1071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko * <p>Most of the extended code groups are being skipped.
1081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko *
1091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko */
1101abddd9f6225298066094e20a6c29061b6af4590Nick Chalkopublic class Cea708Parser {
1111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final String TAG = "Cea708Parser";
1121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final boolean DEBUG = false;
1131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // According to CEA-708B, the maximum value of closed caption bandwidth is 9600bps.
1151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int MAX_ALLOCATED_SIZE = 9600 / 8;
1161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final String MUSIC_NOTE_CHAR = new String(
1171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            "\u266B".getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
1181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // The following values are denoting the type of closed caption data.
1201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // See CEA-708B section 4.4.1.
1211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int CC_TYPE_DTVCC_PACKET_START = 3;
1221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int CC_TYPE_DTVCC_PACKET_DATA = 2;
1231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // The following values are defined in CEA-708B Figure 4 and 6.
1251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int DTVCC_MAX_PACKET_SIZE = 64;
1261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int DTVCC_PACKET_SIZE_SCALE_FACTOR = 2;
1271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int DTVCC_EXTENDED_SERVICE_NUMBER_POINT = 7;
1281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // The following values are for seeking closed caption tracks.
1301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int DISCOVERY_PERIOD_MS = 10000; // 10 sec
1311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int DISCOVERY_NUM_BYTES_THRESHOLD = 10; // 10 bytes
1321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int DISCOVERY_CC_SERVICE_NUMBER_START = 1; // CC1
1331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private static final int DISCOVERY_CC_SERVICE_NUMBER_END = 4; // CC4
1341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private final ByteArrayBuffer mDtvCcPacket = new ByteArrayBuffer(MAX_ALLOCATED_SIZE);
1361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private final TreeSet<CcPacket> mCcPackets = new TreeSet<>();
1371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private final StringBuffer mBuffer = new StringBuffer();
1381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private final SparseIntArray mDiscoveredNumBytes = new SparseIntArray(); // per service number
1391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private long mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime();
1401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int mCommand = 0;
1411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int mListenServiceNumber = 0;
1421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private boolean mDtvCcPacking = false;
1431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // Assign a dummy listener in order to avoid null checks.
1451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private OnCea708ParserListener mListener = new OnCea708ParserListener() {
1461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        @Override
1471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        public void emitEvent(CaptionEvent event) {
1481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // do nothing
1491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
1501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        @Override
1521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        public void discoverServiceNumber(int serviceNumber) {
1531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // do nothing
1541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
1551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    };
1561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
1571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    /**
1581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * {@link Cea708Parser} emits caption event of three different types.
1591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * {@link OnCea708ParserListener#emitEvent} is invoked with the parameter
1601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * {@link CaptionEvent} to pass all the results to an observer of the decoding process.
1611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     *
1621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * <p>{@link CaptionEvent#type} determines the type of the result and
1631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * {@link CaptionEvent#obj} contains the output value of a caption event.
1641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * The observer must do the casting to the corresponding type.
1651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     *
1661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * <ul><li>{@code CAPTION_EMIT_TYPE_BUFFER}: Passes a caption text buffer to a observer.
1671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * {@code obj} must be of {@link String}.</li>
1681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     *
1691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * <li>{@code CAPTION_EMIT_TYPE_CONTROL}: Passes a caption character control code to a observer.
1701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * {@code obj} must be of {@link Character}.</li>
1711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     *
1721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * <li>{@code CAPTION_EMIT_TYPE_CLEAR_COMMAND}: Passes a clear command to a observer.
1731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     * {@code obj} must be {@code NULL}.</li></ul>
1741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko     */
1751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @IntDef({CAPTION_EMIT_TYPE_BUFFER, CAPTION_EMIT_TYPE_CONTROL, CAPTION_EMIT_TYPE_COMMAND_CWX,
1761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        CAPTION_EMIT_TYPE_COMMAND_CLW, CAPTION_EMIT_TYPE_COMMAND_DSW, CAPTION_EMIT_TYPE_COMMAND_HDW,
1771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        CAPTION_EMIT_TYPE_COMMAND_TGW, CAPTION_EMIT_TYPE_COMMAND_DLW, CAPTION_EMIT_TYPE_COMMAND_DLY,
1781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        CAPTION_EMIT_TYPE_COMMAND_DLC, CAPTION_EMIT_TYPE_COMMAND_RST, CAPTION_EMIT_TYPE_COMMAND_SPA,
1791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        CAPTION_EMIT_TYPE_COMMAND_SPC, CAPTION_EMIT_TYPE_COMMAND_SPL, CAPTION_EMIT_TYPE_COMMAND_SWA,
1801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        CAPTION_EMIT_TYPE_COMMAND_DFX})
1811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    @Retention(RetentionPolicy.SOURCE)
1821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public @interface CaptionEmitType {}
1831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_BUFFER = 1;
1841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_CONTROL = 2;
1851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_CWX = 3;
1861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_CLW = 4;
1871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_DSW = 5;
1881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_HDW = 6;
1891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_TGW = 7;
1901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_DLW = 8;
1911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_DLY = 9;
1921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_DLC = 10;
1931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_RST = 11;
1941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_SPA = 12;
1951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_SPC = 13;
1961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_SPL = 14;
1971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_SWA = 15;
1981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public static final int CAPTION_EMIT_TYPE_COMMAND_DFX = 16;
1991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public interface OnCea708ParserListener {
2011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        void emitEvent(CaptionEvent event);
2021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        void discoverServiceNumber(int serviceNumber);
2031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public void setListener(OnCea708ParserListener listener) {
2061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (listener != null) {
2071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mListener = listener;
2081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
2091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public void setListenServiceNumber(int serviceNumber) {
2121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mListenServiceNumber = serviceNumber;
2131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private void emitCaptionEvent(CaptionEvent captionEvent) {
2161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Emit the existing string buffer before a new event is arrived.
2171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        emitCaptionBuffer();
2181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mListener.emitEvent(captionEvent);
2191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private void emitCaptionBuffer() {
2221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mBuffer.length() > 0) {
2231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mListener.emitEvent(new CaptionEvent(CAPTION_EMIT_TYPE_BUFFER, mBuffer.toString()));
2241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mBuffer.setLength(0);
2251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
2261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // Step 1. user_data -> CcPacket ({@link #parseClosedCaption} method)
2291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public void parseClosedCaption(ByteBuffer data, long framePtsUs) {
2301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int ccCount = data.limit() / 3;
2311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte[] ccBytes = new byte[3 * ccCount];
2321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < 3 * ccCount; i++) {
2331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ccBytes[i] = data.get(i);
2341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
2351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        CcPacket ccPacket = new CcPacket(ccBytes, ccCount, framePtsUs);
2361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mCcPackets.add(ccPacket);
2371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    public boolean processClosedCaptions(long framePtsUs) {
2401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // To get the sorted cc packets that have lower frame pts than current frame pts,
2411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // the following offset divides off the lower side of the packets.
2421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        CcPacket offsetPacket = new CcPacket(new byte[0], 0, framePtsUs);
2431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        offsetPacket = mCcPackets.lower(offsetPacket);
2441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        boolean processed = false;
2451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (offsetPacket != null) {
2461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            while (!mCcPackets.isEmpty() && offsetPacket.compareTo(mCcPackets.first()) >= 0) {
2471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                CcPacket packet = mCcPackets.pollFirst();
2481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                parseCcPacket(packet);
2491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                processed = true;
2501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
2511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
2521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return processed;
2531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // Step 2. CcPacket -> DTVCC packet ({@link #parseCcPacket} method)
2561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private void parseCcPacket(CcPacket ccPacket) {
2571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of cc packet, see ATSC TSG-676 - Table A8.
2581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        byte[] bytes = ccPacket.bytes;
2591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int pos = 0;
2601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        for (int i = 0; i < ccPacket.ccCount; ++i) {
2611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            boolean ccValid = (bytes[pos] & 0x04) != 0;
2621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int ccType = bytes[pos] & 0x03;
2631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // The dtvcc should be considered complete:
2651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // - if either ccValid is set and ccType is 3
2661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // - or ccValid is clear and ccType is 2 or 3.
2671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (ccValid) {
2681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (ccType == CC_TYPE_DTVCC_PACKET_START) {
2691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    if (mDtvCcPacking) {
2701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length());
2711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        mDtvCcPacket.clear();
2721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    }
2731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    mDtvCcPacking = true;
2741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    mDtvCcPacket.append(bytes[pos + 1]);
2751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    mDtvCcPacket.append(bytes[pos + 2]);
2761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                } else if (mDtvCcPacking && ccType == CC_TYPE_DTVCC_PACKET_DATA) {
2771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    mDtvCcPacket.append(bytes[pos + 1]);
2781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    mDtvCcPacket.append(bytes[pos + 2]);
2791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
2801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            } else {
2811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if ((ccType == CC_TYPE_DTVCC_PACKET_START || ccType == CC_TYPE_DTVCC_PACKET_DATA)
2821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        && mDtvCcPacking) {
2831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    mDtvCcPacking = false;
2841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    parseDtvCcPacket(mDtvCcPacket.buffer(), mDtvCcPacket.length());
2851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    mDtvCcPacket.clear();
2861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
2871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
2881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 3;
2891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
2901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
2911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
2921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // Step 3. DTVCC packet -> Service Blocks ({@link #parseDtvCcPacket} method)
2931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private void parseDtvCcPacket(byte[] data, int limit) {
2941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of DTVCC packet, see CEA-708B Figure 4.
2951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int pos = 0;
2961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int packetSize = data[pos] & 0x3f;
2971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (packetSize == 0) {
2981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            packetSize = DTVCC_MAX_PACKET_SIZE;
2991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
3001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int calculatedPacketSize = packetSize * DTVCC_PACKET_SIZE_SCALE_FACTOR;
3011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (limit != calculatedPacketSize) {
3021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            return;
3031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
3041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ++pos;
3051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        int len = pos + calculatedPacketSize;
3061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        while (pos < len) {
3071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // For the details of Service Block, see CEA-708B Figure 5 and 6.
3081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int serviceNumber = (data[pos] & 0xe0) >> 5;
3091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int blockSize = data[pos] & 0x1f;
3101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ++pos;
3111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (serviceNumber == DTVCC_EXTENDED_SERVICE_NUMBER_POINT) {
3121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                serviceNumber = (data[pos] & 0x3f);
3131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ++pos;
3141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // Return if invalid service number
3161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (serviceNumber < DTVCC_EXTENDED_SERVICE_NUMBER_POINT) {
3171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    return;
3181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
3191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
3201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (pos + blockSize > limit) {
3211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                return;
3221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
3231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // Send parsed service number in order to find unveiled closed caption tracks which
3251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // are not specified in any ATSC PSIP sections. Since some broadcasts send empty closed
3261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // caption tracks, it detects the proper closed caption tracks by counting the number of
3271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // bytes sent with the same service number during a discovery period.
3281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // The viewer in most TV sets chooses between CC1, CC2, CC3, CC4 to view different
3291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // language captions. Therefore, only CC1, CC2, CC3, CC4 are allowed to be reported.
3301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (blockSize > 0 && serviceNumber >= DISCOVERY_CC_SERVICE_NUMBER_START
3311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    && serviceNumber <= DISCOVERY_CC_SERVICE_NUMBER_END) {
3321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                mDiscoveredNumBytes.put(
3331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        serviceNumber, blockSize + mDiscoveredNumBytes.get(serviceNumber, 0));
3341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
3351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (mLastDiscoveryLaunchedMs + DISCOVERY_PERIOD_MS < SystemClock.elapsedRealtime()) {
3361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                for (int i = 0; i < mDiscoveredNumBytes.size(); ++i) {
3371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    int discoveredNumBytes = mDiscoveredNumBytes.valueAt(i);
3381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    if (discoveredNumBytes >= DISCOVERY_NUM_BYTES_THRESHOLD) {
3391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        int discoveredServiceNumber = mDiscoveredNumBytes.keyAt(i);
3401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        mListener.discoverServiceNumber(discoveredServiceNumber);
3411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    }
3421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
3431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                mDiscoveredNumBytes.clear();
3441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                mLastDiscoveryLaunchedMs = SystemClock.elapsedRealtime();
3451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
3461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // Skip current service block if either there is no block data or the service number
3481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // is not same as listening service number.
3491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (blockSize == 0 || serviceNumber != mListenServiceNumber) {
3501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                pos += blockSize;
3511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                continue;
3521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
3531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // From this point, starts to read DTVCC coding layer.
3551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // First, identify code groups, which is defined in CEA-708B Section 7.1.
3561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            int blockLimit = pos + blockSize;
3571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            while (pos < blockLimit) {
3581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                pos = parseServiceBlockData(data, pos);
3591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
3601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // Emit the buffer after reading codes.
3621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            emitCaptionBuffer();
3631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = blockLimit;
3641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
3651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
3661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // Step 4. Main code groups
3681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int parseServiceBlockData(byte[] data, int pos) {
3691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of the ranges of DTVCC code groups, see CEA-708B Table 6.
3701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mCommand = data[pos] & 0xff;
3711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ++pos;
3721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mCommand == Cea708Data.CODE_C0_EXT1) {
3731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = parseExt1(data, pos);
3741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_C0_RANGE_START
3751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C0_RANGE_END) {
3761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = parseC0(data, pos);
3771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_C1_RANGE_START
3781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C1_RANGE_END) {
3791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = parseC1(data, pos);
3801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_G0_RANGE_START
3811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_G0_RANGE_END) {
3821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = parseG0(data, pos);
3831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_G1_RANGE_START
3841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_G1_RANGE_END) {
3851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = parseG1(data, pos);
3861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
3871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return pos;
3881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
3891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
3901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int parseC0(byte[] data, int pos) {
3911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of C0 code group, see CEA-708B Section 7.4.1.
3921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // CL Group: C0 Subset of ASCII Control codes
3931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mCommand >= Cea708Data.CODE_C0_SKIP2_RANGE_START
3941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C0_SKIP2_RANGE_END) {
3951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            if (mCommand == Cea708Data.CODE_C0_P16) {
3961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // TODO : P16 escapes next two bytes for the large character maps.(no standard rule)
3971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // TODO : For korea broadcasting, express whole letters by using this.
3981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                try {
3991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    if (data[pos] == 0) {
4001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        mBuffer.append((char) data[pos + 1]);
4011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    } else {
4021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        String value = new String(
4031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                Arrays.copyOfRange(data, pos, pos + 2),
4041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                "EUC-KR");
4051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        mBuffer.append(value);
4061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    }
4071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                } catch (UnsupportedEncodingException e) {
4081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.e(TAG, "P16 Code - Could not find supported encoding", e);
4091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
4101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
4111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 2;
4121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_C0_SKIP1_RANGE_START
4131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C0_SKIP1_RANGE_END) {
4141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ++pos;
4151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else {
4161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // NUL, BS, FF, CR interpreted as they are in ASCII control codes.
4171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // HCR moves the pen location to th beginning of the current line and deletes contents.
4181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // FF clears the screen and moves the pen location to (0,0).
4191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // ETX is the NULL command which is used to flush text to the current window when no
4201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // other command is pending.
4211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            switch (mCommand) {
4221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case Cea708Data.CODE_C0_NUL:
4231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
4241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case Cea708Data.CODE_C0_ETX:
4251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
4261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
4271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case Cea708Data.CODE_C0_BS:
4281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
4291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
4301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case Cea708Data.CODE_C0_FF:
4311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
4321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
4331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case Cea708Data.CODE_C0_CR:
4341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    mBuffer.append('\n');
4351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
4361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                case Cea708Data.CODE_C0_HCR:
4371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_CONTROL, (char) mCommand));
4381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
4391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                default:
4401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    break;
4411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
4421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
4431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return pos;
4441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
4451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int parseC1(byte[] data, int pos) {
4471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of C1 code group, see CEA-708B Section 8.10.
4481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // CR Group: C1 Caption Control Codes
4491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        switch (mCommand) {
4501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_CW0:
4511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_CW1:
4521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_CW2:
4531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_CW3:
4541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_CW4:
4551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_CW5:
4561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_CW6:
4571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_CW7: {
4581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // SetCurrentWindow0-7
4591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int windowId = mCommand - Cea708Data.CODE_C1_CW0;
4601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CWX, windowId));
4611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
4621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format("CaptionCommand CWX windowId: %d", windowId));
4631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
4641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
4661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_CLW: {
4681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // ClearWindows
4691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int windowBitmap = data[pos] & 0xff;
4701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ++pos;
4711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_CLW, windowBitmap));
4721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
4731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format("CaptionCommand CLW windowBitmap: %d", windowBitmap));
4741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
4751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
4771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DSW: {
4791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // DisplayWindows
4801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int windowBitmap = data[pos] & 0xff;
4811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ++pos;
4821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DSW, windowBitmap));
4831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
4841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format("CaptionCommand DSW windowBitmap: %d", windowBitmap));
4851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
4861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
4881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
4891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_HDW: {
4901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // HideWindows
4911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int windowBitmap = data[pos] & 0xff;
4921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ++pos;
4931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_HDW, windowBitmap));
4941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
4951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format("CaptionCommand HDW windowBitmap: %d", windowBitmap));
4961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
4971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
4981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
4991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_TGW: {
5011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // ToggleWindows
5021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int windowBitmap = data[pos] & 0xff;
5031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ++pos;
5041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_TGW, windowBitmap));
5051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
5061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format("CaptionCommand TGW windowBitmap: %d", windowBitmap));
5071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
5081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
5091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
5101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DLW: {
5121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // DeleteWindows
5131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int windowBitmap = data[pos] & 0xff;
5141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ++pos;
5151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLW, windowBitmap));
5161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
5171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format("CaptionCommand DLW windowBitmap: %d", windowBitmap));
5181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
5191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
5201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
5211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DLY: {
5231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // Delay
5241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int tenthsOfSeconds = data[pos] & 0xff;
5251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ++pos;
5261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLY, tenthsOfSeconds));
5271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
5281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format("CaptionCommand DLY %d tenths of seconds",
5291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            tenthsOfSeconds));
5301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
5311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
5321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
5331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DLC: {
5341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // DelayCancel
5351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DLC, null));
5361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
5371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, "CaptionCommand DLC");
5381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
5391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
5401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
5411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_RST: {
5431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // Reset
5441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_RST, null));
5451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
5461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, "CaptionCommand RST");
5471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
5481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
5491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
5501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_SPA: {
5521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // SetPenAttributes
5531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int textTag = (data[pos] & 0xf0) >> 4;
5541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int penSize = data[pos] & 0x03;
5551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int penOffset = (data[pos] & 0x0c) >> 2;
5561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                boolean italic = (data[pos + 1] & 0x80) != 0;
5571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                boolean underline = (data[pos + 1] & 0x40) != 0;
5581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int edgeType = (data[pos + 1] & 0x38) >> 3;
5591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int fontTag = data[pos + 1] & 0x7;
5601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                pos += 2;
5611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPA,
5621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        new CaptionPenAttr(penSize, penOffset, textTag, fontTag, edgeType,
5631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                underline, italic)));
5641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
5651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format(
5661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            "CaptionCommand SPA penSize: %d, penOffset: %d, textTag: %d, "
5671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                    + "fontTag: %d, edgeType: %d, underline: %s, italic: %s",
5681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            penSize, penOffset, textTag, fontTag, edgeType, underline, italic));
5691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
5701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
5711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
5721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
5731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_SPC: {
5741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // SetPenColor
5751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int opacity = (data[pos] & 0xc0) >> 6;
5761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int red = (data[pos] & 0x30) >> 4;
5771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int green = (data[pos] & 0x0c) >> 2;
5781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int blue = data[pos] & 0x03;
5791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                CaptionColor foregroundColor = new CaptionColor(opacity, red, green, blue);
5801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ++pos;
5811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                opacity = (data[pos] & 0xc0) >> 6;
5821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                red = (data[pos] & 0x30) >> 4;
5831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                green = (data[pos] & 0x0c) >> 2;
5841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                blue = data[pos] & 0x03;
5851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                CaptionColor backgroundColor = new CaptionColor(opacity, red, green, blue);
5861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ++pos;
5871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                red = (data[pos] & 0x30) >> 4;
5881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                green = (data[pos] & 0x0c) >> 2;
5891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                blue = data[pos] & 0x03;
5901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                CaptionColor edgeColor = new CaptionColor(
5911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        CaptionColor.OPACITY_SOLID, red, green, blue);
5921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                ++pos;
5931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPC,
5941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        new CaptionPenColor(foregroundColor, backgroundColor, edgeColor)));
5951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
5961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format(
5971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            "CaptionCommand SPC foregroundColor %s backgroundColor %s edgeColor %s",
5981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            foregroundColor, backgroundColor, edgeColor));
5991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
6001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
6011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
6031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_SPL: {
6041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // SetPenLocation
6051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // column is normally 0-31 for 4:3 formats, and 0-41 for 16:9 formats
6061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int row = data[pos] & 0x0f;
6071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int column = data[pos + 1] & 0x3f;
6081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                pos += 2;
6091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SPL,
6101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        new CaptionPenLocation(row, column)));
6111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
6121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format("CaptionCommand SPL row: %d, column: %d",
6131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            row, column));
6141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
6151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
6161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
6181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_SWA: {
6191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // SetWindowAttributes
6201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int opacity = (data[pos] & 0xc0) >> 6;
6211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int red = (data[pos] & 0x30) >> 4;
6221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int green = (data[pos] & 0x0c) >> 2;
6231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int blue = data[pos] & 0x03;
6241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                CaptionColor fillColor = new CaptionColor(opacity, red, green, blue);
6251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int borderType = (data[pos + 1] & 0xc0) >> 6 | (data[pos + 2] & 0x80) >> 5;
6261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                red = (data[pos + 1] & 0x30) >> 4;
6271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                green = (data[pos + 1] & 0x0c) >> 2;
6281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                blue = data[pos + 1] & 0x03;
6291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                CaptionColor borderColor = new CaptionColor(
6301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        CaptionColor.OPACITY_SOLID, red, green, blue);
6311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                boolean wordWrap = (data[pos + 2] & 0x40) != 0;
6321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int printDirection = (data[pos + 2] & 0x30) >> 4;
6331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int scrollDirection = (data[pos + 2] & 0x0c) >> 2;
6341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int justify = (data[pos + 2] & 0x03);
6351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int effectSpeed = (data[pos + 3] & 0xf0) >> 4;
6361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int effectDirection = (data[pos + 3] & 0x0c) >> 2;
6371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int displayEffect = data[pos + 3] & 0x3;
6381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                pos += 4;
6391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_SWA,
6401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        new CaptionWindowAttr(fillColor, borderColor, borderType, wordWrap,
6411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                printDirection, scrollDirection, justify,
6421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                effectDirection, effectSpeed, displayEffect)));
6431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
6441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format(
6451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            "CaptionCommand SWA fillColor: %s, borderColor: %s, borderType: %d"
6461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                    + "wordWrap: %s, printDirection: %d, scrollDirection: %d, "
6471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                    + "justify: %s, effectDirection: %d, effectSpeed: %d, "
6481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                    + "displayEffect: %d",
6491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            fillColor, borderColor, borderType, wordWrap, printDirection,
6501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            scrollDirection, justify, effectDirection, effectSpeed, displayEffect));
6511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
6521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
6531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
6551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DF0:
6561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DF1:
6571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DF2:
6581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DF3:
6591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DF4:
6601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DF5:
6611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DF6:
6621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_C1_DF7: {
6631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // DefineWindow0-7
6641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int windowId = mCommand - Cea708Data.CODE_C1_DF0;
6651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                boolean visible = (data[pos] & 0x20) != 0;
6661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                boolean rowLock = (data[pos] & 0x10) != 0;
6671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                boolean columnLock = (data[pos] & 0x08) != 0;
6681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int priority = data[pos] & 0x07;
6691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                boolean relativePositioning = (data[pos + 1] & 0x80) != 0;
6701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int anchorVertical = data[pos + 1] & 0x7f;
6711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int anchorHorizontal = data[pos + 2] & 0xff;
6721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int anchorId = (data[pos + 3] & 0xf0) >> 4;
6731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int rowCount = data[pos + 3] & 0x0f;
6741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int columnCount = data[pos + 4] & 0x3f;
6751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int windowStyle = (data[pos + 5] & 0x38) >> 3;
6761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                int penStyle = data[pos + 5] & 0x07;
6771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                pos += 6;
6781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                emitCaptionEvent(new CaptionEvent(CAPTION_EMIT_TYPE_COMMAND_DFX,
6791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                        new CaptionWindow(windowId, visible, rowLock, columnLock, priority,
6801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                relativePositioning, anchorVertical, anchorHorizontal, anchorId,
6811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                rowCount, columnCount, penStyle, windowStyle)));
6821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                if (DEBUG) {
6831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                    Log.d(TAG, String.format(
6841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            "CaptionCommand DFx windowId: %d, priority: %d, columnLock: %s, "
6851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                    + "rowLock: %s, visible: %s, anchorVertical: %d, "
6861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                    + "relativePositioning: %s, anchorHorizontal: %d, "
6871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                    + "rowCount: %d, anchorId: %d, columnCount: %d, penStyle: %d, "
6881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                                    + "windowStyle: %d",
6891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            windowId, priority, columnLock, rowLock, visible, anchorVertical,
6901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            relativePositioning, anchorHorizontal, rowCount, anchorId, columnCount,
6911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                            penStyle, windowStyle));
6921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                }
6931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
6941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            }
6951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
6961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            default:
6971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
6981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
6991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return pos;
7001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
7011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
7021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int parseG0(byte[] data, int pos) {
7031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of G0 code group, see CEA-708B Section 7.4.3.
7041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // GL Group: G0 Modified version of ANSI X3.4 Printable Character Set (ASCII)
7051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mCommand == Cea708Data.CODE_G0_MUSICNOTE) {
7061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // Music note.
7071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mBuffer.append(MUSIC_NOTE_CHAR);
7081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else {
7091abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // Put ASCII code into buffer.
7101abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            mBuffer.append((char) mCommand);
7111abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
7121abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return pos;
7131abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
7141abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
7151abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int parseG1(byte[] data, int pos) {
7161abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of G0 code group, see CEA-708B Section 7.4.4.
7171abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // GR Group: G1 ISO 8859-1 Latin 1 Characters
7181abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Put ASCII Extended character set into buffer.
7191abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mBuffer.append((char) mCommand);
7201abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return pos;
7211abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
7221abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
7231abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    // Step 4. Extended code groups
7241abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int parseExt1(byte[] data, int pos) {
7251abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of EXT1 code group, see CEA-708B Section 7.2.
7261abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        mCommand = data[pos] & 0xff;
7271abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        ++pos;
7281abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mCommand >= Cea708Data.CODE_C2_RANGE_START
7291abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C2_RANGE_END) {
7301abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = parseC2(data, pos);
7311abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_C3_RANGE_START
7321abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C3_RANGE_END) {
7331abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = parseC3(data, pos);
7341abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_G2_RANGE_START
7351abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_G2_RANGE_END) {
7361abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = parseG2(data, pos);
7371abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_G3_RANGE_START
7381abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_G3_RANGE_END) {
7391abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos = parseG3(data ,pos);
7401abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
7411abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return pos;
7421abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
7431abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
7441abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int parseC2(byte[] data, int pos) {
7451abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of C2 code group, see CEA-708B Section 7.4.7.
7461abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Extended Miscellaneous Control Codes
7471abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // C2 Table : No commands as of CEA-708B. A decoder must skip.
7481abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mCommand >= Cea708Data.CODE_C2_SKIP0_RANGE_START
7491abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C2_SKIP0_RANGE_END) {
7501abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // Do nothing.
7511abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_C2_SKIP1_RANGE_START
7521abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C2_SKIP1_RANGE_END) {
7531abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            ++pos;
7541abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_C2_SKIP2_RANGE_START
7551abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C2_SKIP2_RANGE_END) {
7561abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 2;
7571abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_C2_SKIP3_RANGE_START
7581abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C2_SKIP3_RANGE_END) {
7591abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 3;
7601abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
7611abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return pos;
7621abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
7631abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
7641abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int parseC3(byte[] data, int pos) {
7651abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of C3 code group, see CEA-708B Section 7.4.8.
7661abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Extended Control Code Set 2
7671abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // C3 Table : No commands as of CEA-708B. A decoder must skip.
7681abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mCommand >= Cea708Data.CODE_C3_SKIP4_RANGE_START
7691abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C3_SKIP4_RANGE_END) {
7701abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 4;
7711abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        } else if (mCommand >= Cea708Data.CODE_C3_SKIP5_RANGE_START
7721abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                && mCommand <= Cea708Data.CODE_C3_SKIP5_RANGE_END) {
7731abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            pos += 5;
7741abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
7751abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return pos;
7761abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
7771abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
7781abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int parseG2(byte[] data, int pos) {
7791abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of C3 code group, see CEA-708B Section 7.4.5.
7801abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Extended Control Code Set 1(G2 Table)
7811abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        switch (mCommand) {
7821abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_G2_TSP:
7831abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // TODO : TSP is the Transparent space
7841abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
7851abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_G2_NBTSP:
7861abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // TODO : NBTSP is Non-Breaking Transparent Space.
7871abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
7881abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            case Cea708Data.CODE_G2_BLK:
7891abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // TODO : BLK indicates a solid block which fills the entire character block
7901abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                // TODO : with a solid foreground color.
7911abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
7921abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            default:
7931abddd9f6225298066094e20a6c29061b6af4590Nick Chalko                break;
7941abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
7951abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return pos;
7961abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
7971abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
7981abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    private int parseG3(byte[] data, int pos) {
7991abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // For the details of C3 code group, see CEA-708B Section 7.4.6.
8001abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Future characters and icons(G3 Table)
8011abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        if (mCommand == Cea708Data.CODE_G3_CC) {
8021abddd9f6225298066094e20a6c29061b6af4590Nick Chalko            // TODO : [CC] icon with square corners
8031abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        }
8041abddd9f6225298066094e20a6c29061b6af4590Nick Chalko
8051abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        // Do nothing
8061abddd9f6225298066094e20a6c29061b6af4590Nick Chalko        return pos;
8071abddd9f6225298066094e20a6c29061b6af4590Nick Chalko    }
8081abddd9f6225298066094e20a6c29061b6af4590Nick Chalko}
809