Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tv.tuner.data;
     18 
     19 import android.support.annotation.NonNull;
     20 import android.text.TextUtils;
     21 import android.text.format.DateUtils;
     22 
     23 import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
     24 import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
     25 import com.android.tv.tuner.ts.SectionParser;
     26 import com.android.tv.tuner.util.ConvertUtils;
     27 import com.android.tv.util.StringUtils;
     28 
     29 import java.util.ArrayList;
     30 import java.util.HashMap;
     31 import java.util.List;
     32 import java.util.Locale;
     33 
     34 /**
     35  * Collection of ATSC PSIP table items.
     36  */
     37 public class PsipData {
     38 
     39     private PsipData() {
     40     }
     41 
     42     public static class PsipSection {
     43         private final int mTableId;
     44         private final int mTableIdExtension;
     45         private final int mSectionNumber;
     46         private final boolean mCurrentNextIndicator;
     47 
     48         public static PsipSection create(byte[] data) {
     49             if (data.length < 9) {
     50                 return null;
     51             }
     52             int tableId = data[0] & 0xff;
     53             int tableIdExtension = (data[3] & 0xff) << 8 | (data[4] & 0xff);
     54             int sectionNumber = data[6] & 0xff;
     55             boolean currentNextIndicator = (data[5] & 0x01) != 0;
     56             return new PsipSection(tableId, tableIdExtension, sectionNumber, currentNextIndicator);
     57         }
     58 
     59         private PsipSection(int tableId, int tableIdExtension, int sectionNumber,
     60                 boolean currentNextIndicator) {
     61             mTableId = tableId;
     62             mTableIdExtension = tableIdExtension;
     63             mSectionNumber = sectionNumber;
     64             mCurrentNextIndicator = currentNextIndicator;
     65         }
     66 
     67         public int getTableId() {
     68             return mTableId;
     69         }
     70 
     71         public int getTableIdExtension() {
     72             return mTableIdExtension;
     73         }
     74 
     75         public int getSectionNumber() {
     76             return mSectionNumber;
     77         }
     78 
     79         // This is for indicating that the section sent is applicable.
     80         // We only consider a situation where currentNextIndicator is expected to have a true value.
     81         // So, we are not going to compare this variable in hashCode() and equals() methods.
     82         public boolean getCurrentNextIndicator() {
     83             return mCurrentNextIndicator;
     84         }
     85 
     86         @Override
     87         public int hashCode() {
     88             int result = 17;
     89             result = 31 * result + mTableId;
     90             result = 31 * result + mTableIdExtension;
     91             result = 31 * result + mSectionNumber;
     92             return result;
     93         }
     94 
     95         @Override
     96         public boolean equals(Object obj) {
     97             if (obj instanceof PsipSection) {
     98                 PsipSection another = (PsipSection) obj;
     99                 return mTableId == another.getTableId()
    100                         && mTableIdExtension == another.getTableIdExtension()
    101                         && mSectionNumber == another.getSectionNumber();
    102             }
    103             return false;
    104         }
    105     }
    106 
    107     /**
    108      * {@link TvTracksInterface} for serving the audio and caption tracks.
    109      */
    110     public interface TvTracksInterface {
    111         /**
    112          * Set the flag that tells the caption tracks have been found in this section container.
    113          */
    114         void setHasCaptionTrack();
    115 
    116         /**
    117          * Returns whether or not the caption tracks have been found in this section container.
    118          * If true, zero caption track will be interpreted as a clearance of the caption tracks.
    119          */
    120         boolean hasCaptionTrack();
    121 
    122         /**
    123          * Returns the audio tracks received.
    124          */
    125         List<AtscAudioTrack> getAudioTracks();
    126 
    127         /**
    128          * Returns the caption tracks received.
    129          */
    130         List<AtscCaptionTrack> getCaptionTracks();
    131     }
    132 
    133     public static class MgtItem {
    134         public static final int TABLE_TYPE_EIT_RANGE_START = 0x0100;
    135         public static final int TABLE_TYPE_EIT_RANGE_END = 0x017f;
    136         public static final int TABLE_TYPE_CHANNEL_ETT = 0x0004;
    137         public static final int TABLE_TYPE_ETT_RANGE_START = 0x0200;
    138         public static final int TABLE_TYPE_ETT_RANGE_END = 0x027f;
    139 
    140         private final int mTableType;
    141         private final int mTableTypePid;
    142 
    143         public MgtItem(int tableType, int tableTypePid) {
    144             mTableType = tableType;
    145             mTableTypePid = tableTypePid;
    146         }
    147 
    148         public int getTableType() {
    149             return mTableType;
    150         }
    151 
    152         public int getTableTypePid() {
    153             return mTableTypePid;
    154         }
    155     }
    156 
    157     public static class VctItem {
    158         private final String mShortName;
    159         private final String mLongName;
    160         private final int mServiceType;
    161         private final int mChannelTsid;
    162         private final int mProgramNumber;
    163         private final int mMajorChannelNumber;
    164         private final int mMinorChannelNumber;
    165         private final int mSourceId;
    166         private String mDescription;
    167 
    168         public VctItem(String shortName, String longName, int serviceType, int channelTsid,
    169                 int programNumber, int majorChannelNumber, int minorChannelNumber, int sourceId) {
    170             mShortName = shortName;
    171             mLongName = longName;
    172             mServiceType = serviceType;
    173             mChannelTsid = channelTsid;
    174             mProgramNumber = programNumber;
    175             mMajorChannelNumber = majorChannelNumber;
    176             mMinorChannelNumber = minorChannelNumber;
    177             mSourceId = sourceId;
    178         }
    179 
    180         public String getShortName() {
    181             return mShortName;
    182         }
    183 
    184         public String getLongName() {
    185             return mLongName;
    186         }
    187 
    188         public int getServiceType() {
    189             return mServiceType;
    190         }
    191 
    192         public int getChannelTsid() {
    193             return mChannelTsid;
    194         }
    195 
    196         public int getProgramNumber() {
    197             return mProgramNumber;
    198         }
    199 
    200         public int getMajorChannelNumber() {
    201             return mMajorChannelNumber;
    202         }
    203 
    204         public int getMinorChannelNumber() {
    205             return mMinorChannelNumber;
    206         }
    207 
    208         public int getSourceId() {
    209             return mSourceId;
    210         }
    211 
    212         @Override
    213         public String toString() {
    214             return String
    215                     .format(Locale.US, "ShortName: %s LongName: %s ServiceType: %d ChannelTsid: %x "
    216                             + "ProgramNumber:%d %d-%d SourceId: %x",
    217                     mShortName, mLongName, mServiceType, mChannelTsid,
    218                     mProgramNumber, mMajorChannelNumber, mMinorChannelNumber, mSourceId);
    219         }
    220 
    221         public void setDescription(String description) {
    222             mDescription = description;
    223         }
    224 
    225         public String getDescription() {
    226             return mDescription;
    227         }
    228     }
    229 
    230     public static class SdtItem {
    231         private final String mServiceName;
    232         private final String mServiceProviderName;
    233         private final int mServiceType;
    234         private final int mServiceId;
    235         private final int mOriginalNetWorkId;
    236 
    237         public SdtItem(String serviceName, String serviceProviderName, int serviceType,
    238                        int serviceId, int originalNetWorkId) {
    239             mServiceName = serviceName;
    240             mServiceProviderName = serviceProviderName;
    241             mServiceType = serviceType;
    242             mServiceId = serviceId;
    243             mOriginalNetWorkId = originalNetWorkId;
    244         }
    245 
    246         public String getServiceName() {
    247             return mServiceName;
    248         }
    249 
    250         public String getServiceProviderName() {
    251             return mServiceProviderName;
    252         }
    253 
    254         public int getServiceType() {
    255             return mServiceType;
    256         }
    257 
    258         public int getServiceId() {
    259             return mServiceId;
    260         }
    261 
    262         public int getOriginalNetworkId() {
    263             return mOriginalNetWorkId;
    264         }
    265 
    266         @Override
    267         public String toString() {
    268             return String.format("ServiceName: %s ServiceProviderName:%s ServiceType:%d "
    269                             + "OriginalNetworkId:%d",
    270                     mServiceName, mServiceProviderName, mServiceType, mOriginalNetWorkId);
    271         }
    272     }
    273 
    274     /**
    275      * A base class for descriptors of Ts packets.
    276      */
    277     public abstract static class TsDescriptor {
    278         public abstract int getTag();
    279     }
    280 
    281     public static class ContentAdvisoryDescriptor extends TsDescriptor {
    282         private final List<RatingRegion> mRatingRegions;
    283 
    284         public ContentAdvisoryDescriptor(List<RatingRegion> ratingRegions) {
    285             mRatingRegions = ratingRegions;
    286         }
    287 
    288         @Override
    289         public int getTag() {
    290             return SectionParser.DESCRIPTOR_TAG_CONTENT_ADVISORY;
    291         }
    292 
    293         public List<RatingRegion> getRatingRegions() {
    294             return mRatingRegions;
    295         }
    296     }
    297 
    298     public static class CaptionServiceDescriptor extends TsDescriptor {
    299         private final List<AtscCaptionTrack> mCaptionTracks;
    300 
    301         public CaptionServiceDescriptor(List<AtscCaptionTrack> captionTracks) {
    302             mCaptionTracks = captionTracks;
    303         }
    304 
    305         @Override
    306         public int getTag() {
    307             return SectionParser.DESCRIPTOR_TAG_CAPTION_SERVICE;
    308         }
    309 
    310         public List<AtscCaptionTrack> getCaptionTracks() {
    311             return mCaptionTracks;
    312         }
    313     }
    314 
    315     public static class ExtendedChannelNameDescriptor extends TsDescriptor {
    316         private final String mLongChannelName;
    317 
    318         public ExtendedChannelNameDescriptor(String longChannelName) {
    319             mLongChannelName = longChannelName;
    320         }
    321 
    322         @Override
    323         public int getTag() {
    324             return SectionParser.DESCRIPTOR_TAG_EXTENDED_CHANNEL_NAME;
    325         }
    326 
    327         public String getLongChannelName() {
    328             return mLongChannelName;
    329         }
    330     }
    331 
    332     public static class GenreDescriptor extends TsDescriptor {
    333         private final String[] mBroadcastGenres;
    334         private final String[] mCanonicalGenres;
    335 
    336         public GenreDescriptor(String[] broadcastGenres, String[] canonicalGenres) {
    337             mBroadcastGenres = broadcastGenres;
    338             mCanonicalGenres = canonicalGenres;
    339         }
    340 
    341         @Override
    342         public int getTag() {
    343             return SectionParser.DESCRIPTOR_TAG_GENRE;
    344         }
    345 
    346         public String[] getBroadcastGenres() {
    347             return mBroadcastGenres;
    348         }
    349 
    350         public String[] getCanonicalGenres() {
    351             return mCanonicalGenres;
    352         }
    353     }
    354 
    355     public static class Ac3AudioDescriptor extends TsDescriptor {
    356         // See A/52 Annex A. Table A4.2
    357         private static final byte SAMPLE_RATE_CODE_48000HZ = 0;
    358         private static final byte SAMPLE_RATE_CODE_44100HZ = 1;
    359         private static final byte SAMPLE_RATE_CODE_32000HZ = 2;
    360 
    361         private final byte mSampleRateCode;
    362         private final byte mBsid;
    363         private final byte mBitRateCode;
    364         private final byte mSurroundMode;
    365         private final byte mBsmod;
    366         private final int mNumChannels;
    367         private final boolean mFullSvc;
    368         private final byte mLangCod;
    369         private final byte mLangCod2;
    370         private final byte mMainId;
    371         private final byte mPriority;
    372         private final byte mAsvcflags;
    373         private final String mText;
    374         private final String mLanguage;
    375         private final String mLanguage2;
    376 
    377         public Ac3AudioDescriptor(byte sampleRateCode, byte bsid, byte bitRateCode,
    378                 byte surroundMode, byte bsmod, int numChannels, boolean fullSvc, byte langCod,
    379                 byte langCod2, byte mainId, byte priority, byte asvcflags, String text,
    380                 String language, String language2) {
    381             mSampleRateCode = sampleRateCode;
    382             mBsid = bsid;
    383             mBitRateCode = bitRateCode;
    384             mSurroundMode = surroundMode;
    385             mBsmod = bsmod;
    386             mNumChannels = numChannels;
    387             mFullSvc = fullSvc;
    388             mLangCod = langCod;
    389             mLangCod2 = langCod2;
    390             mMainId = mainId;
    391             mPriority = priority;
    392             mAsvcflags = asvcflags;
    393             mText = text;
    394             mLanguage = language;
    395             mLanguage2 = language2;
    396         }
    397 
    398         @Override
    399         public int getTag() {
    400             return SectionParser.DESCRIPTOR_TAG_AC3_AUDIO_STREAM;
    401         }
    402 
    403         public byte getSampleRateCode() {
    404             return mSampleRateCode;
    405         }
    406 
    407         public int getSampleRate() {
    408             switch (mSampleRateCode) {
    409                 case SAMPLE_RATE_CODE_48000HZ:
    410                     return 48000;
    411                 case SAMPLE_RATE_CODE_44100HZ:
    412                     return 44100;
    413                 case SAMPLE_RATE_CODE_32000HZ:
    414                     return 32000;
    415                 default:
    416                     return 0;
    417             }
    418         }
    419 
    420         public byte getBsid() {
    421             return mBsid;
    422         }
    423 
    424         public byte getBitRateCode() {
    425             return mBitRateCode;
    426         }
    427 
    428         public byte getSurroundMode() {
    429             return mSurroundMode;
    430         }
    431 
    432         public byte getBsmod() {
    433             return mBsmod;
    434         }
    435 
    436         public int getNumChannels() {
    437             return mNumChannels;
    438         }
    439 
    440         public boolean isFullSvc() {
    441             return mFullSvc;
    442         }
    443 
    444         public byte getLangCod() {
    445             return mLangCod;
    446         }
    447 
    448         public byte getLangCod2() {
    449             return mLangCod2;
    450         }
    451 
    452         public byte getMainId() {
    453             return mMainId;
    454         }
    455 
    456         public byte getPriority() {
    457             return mPriority;
    458         }
    459 
    460         public byte getAsvcflags() {
    461             return mAsvcflags;
    462         }
    463 
    464         public String getText() {
    465             return mText;
    466         }
    467 
    468         public String getLanguage() {
    469             return mLanguage;
    470         }
    471 
    472         public String getLanguage2() {
    473             return mLanguage2;
    474         }
    475 
    476         @Override
    477         public String toString() {
    478             return String.format(Locale.US,
    479                     "AC3 audio stream sampleRateCode: %d, bsid: %d, bitRateCode: %d, "
    480                     + "surroundMode: %d, bsmod: %d, numChannels: %d, fullSvc: %s, langCod: %d, "
    481                     + "langCod2: %d, mainId: %d, priority: %d, avcflags: %d, text: %s, language: %s"
    482                     + ", language2: %s", mSampleRateCode, mBsid, mBitRateCode, mSurroundMode,
    483                     mBsmod, mNumChannels, mFullSvc, mLangCod, mLangCod2, mMainId, mPriority,
    484                     mAsvcflags, mText, mLanguage, mLanguage2);
    485         }
    486     }
    487 
    488     public static class Iso639LanguageDescriptor extends TsDescriptor {
    489         private final List<AtscAudioTrack> mAudioTracks;
    490 
    491         public Iso639LanguageDescriptor(List<AtscAudioTrack> audioTracks) {
    492             mAudioTracks = audioTracks;
    493         }
    494 
    495         @Override
    496         public int getTag() {
    497             return SectionParser.DESCRIPTOR_TAG_ISO639LANGUAGE;
    498         }
    499 
    500         public List<AtscAudioTrack> getAudioTracks() {
    501             return mAudioTracks;
    502         }
    503 
    504         @Override
    505         public String toString() {
    506             return String.format("%s %s", getClass().getName(), mAudioTracks);
    507         }
    508     }
    509 
    510     public static class ServiceDescriptor extends TsDescriptor {
    511         private final int mServiceType;
    512         private final String mServiceProviderName;
    513         private final String mServiceName;
    514 
    515         public ServiceDescriptor(int serviceType, String serviceProviderName, String serviceName) {
    516             mServiceType = serviceType;
    517             mServiceProviderName = serviceProviderName;
    518             mServiceName = serviceName;
    519         }
    520 
    521         @Override
    522         public int getTag() {
    523             return SectionParser.DVB_DESCRIPTOR_TAG_SERVICE;
    524         }
    525 
    526         public int getServiceType() {
    527             return mServiceType;
    528         }
    529 
    530         public String getServiceProviderName() {
    531             return mServiceProviderName;
    532         }
    533 
    534         public String getServiceName() {
    535             return mServiceName;
    536         }
    537 
    538         @Override
    539         public String toString() {
    540             return String.format(
    541                     "Service descriptor, service type: %d, "
    542                             + "service provider name: %s, "
    543                             + "service name: %s", mServiceType, mServiceProviderName, mServiceName);
    544         }
    545     }
    546 
    547     public static class ShortEventDescriptor extends TsDescriptor {
    548         private final String mLanguage;
    549         private final String mEventName;
    550         private final String mText;
    551 
    552         public ShortEventDescriptor(String language, String eventName, String text) {
    553             mLanguage = language;
    554             mEventName = eventName;
    555             mText = text;
    556         }
    557 
    558         public String getEventName() {
    559             return mEventName;
    560         }
    561 
    562         @Override
    563         public int getTag() {
    564             return SectionParser.DVB_DESCRIPTOR_TAG_SHORT_EVENT;
    565         }
    566 
    567         @Override
    568         public String toString() {
    569             return String.format("ShortEvent Descriptor, language:%s, event name: %s, "
    570                     + "text:%s", mLanguage, mEventName, mText);
    571         }
    572     }
    573 
    574     public static class ParentalRatingDescriptor extends TsDescriptor {
    575         private final HashMap<String, Integer> mRatings;
    576 
    577         public ParentalRatingDescriptor(HashMap<String, Integer> ratings) {
    578             mRatings = ratings;
    579         }
    580 
    581         @Override
    582         public int getTag() {
    583             return SectionParser.DVB_DESCRIPTOR_TAG_PARENTAL_RATING;
    584         }
    585 
    586         public HashMap<String, Integer> getRatings() {
    587             return mRatings;
    588         }
    589 
    590         @Override
    591         public String toString() {
    592             return String.format("Parental rating descriptor, ratings:" + mRatings);
    593         }
    594     }
    595 
    596     public static class RatingRegion {
    597         private final int mName;
    598         private final String mDescription;
    599         private final List<RegionalRating> mRegionalRatings;
    600 
    601         public RatingRegion(int name, String description, List<RegionalRating> regionalRatings) {
    602             mName = name;
    603             mDescription = description;
    604             mRegionalRatings = regionalRatings;
    605         }
    606 
    607         public int getName() {
    608             return mName;
    609         }
    610 
    611         public String getDescription() {
    612             return mDescription;
    613         }
    614 
    615         public List<RegionalRating> getRegionalRatings() {
    616             return mRegionalRatings;
    617         }
    618     }
    619 
    620     public static class RegionalRating {
    621         private final int mDimension;
    622         private final int mRating;
    623 
    624         public RegionalRating(int dimension, int rating) {
    625             mDimension = dimension;
    626             mRating = rating;
    627         }
    628 
    629         public int getDimension() {
    630             return mDimension;
    631         }
    632 
    633         public int getRating() {
    634             return mRating;
    635         }
    636     }
    637 
    638     public static class EitItem implements Comparable<EitItem>, TvTracksInterface {
    639         public static final long INVALID_PROGRAM_ID = -1;
    640 
    641         // A program id is a primary key of TvContract.Programs table. So it must be positive.
    642         private final long mProgramId;
    643         private final int mEventId;
    644         private final String mTitleText;
    645         private String mDescription;
    646         private final long mStartTime;
    647         private final int mLengthInSecond;
    648         private final String mContentRating;
    649         private final List<AtscAudioTrack> mAudioTracks;
    650         private final List<AtscCaptionTrack> mCaptionTracks;
    651         private boolean mHasCaptionTrack;
    652         private final String mBroadcastGenre;
    653         private final String mCanonicalGenre;
    654 
    655         public EitItem(long programId, int eventId, String titleText, long startTime,
    656                 int lengthInSecond, String contentRating, List<AtscAudioTrack> audioTracks,
    657                 List<AtscCaptionTrack> captionTracks, String broadcastGenre, String canonicalGenre,
    658                 String description) {
    659             mProgramId = programId;
    660             mEventId = eventId;
    661             mTitleText = titleText;
    662             mStartTime = startTime;
    663             mLengthInSecond = lengthInSecond;
    664             mContentRating = contentRating;
    665             mAudioTracks = audioTracks;
    666             mCaptionTracks = captionTracks;
    667             mBroadcastGenre = broadcastGenre;
    668             mCanonicalGenre = canonicalGenre;
    669             mDescription = description;
    670         }
    671 
    672         public long getProgramId() {
    673             return mProgramId;
    674         }
    675 
    676         public int getEventId() {
    677             return mEventId;
    678         }
    679 
    680         public String getTitleText() {
    681             return mTitleText;
    682         }
    683 
    684         public void setDescription(String description) {
    685             mDescription = description;
    686         }
    687 
    688         public String getDescription() {
    689             return mDescription;
    690         }
    691 
    692         public long getStartTime() {
    693             return mStartTime;
    694         }
    695 
    696         public int getLengthInSecond() {
    697             return mLengthInSecond;
    698         }
    699 
    700         public long getStartTimeUtcMillis() {
    701             return ConvertUtils.convertGPSTimeToUnixEpoch(mStartTime) * DateUtils.SECOND_IN_MILLIS;
    702         }
    703 
    704         public long getEndTimeUtcMillis() {
    705             return ConvertUtils.convertGPSTimeToUnixEpoch(
    706                     mStartTime + mLengthInSecond) * DateUtils.SECOND_IN_MILLIS;
    707         }
    708 
    709         public String getContentRating() {
    710             return mContentRating;
    711         }
    712 
    713         @Override
    714         public List<AtscAudioTrack> getAudioTracks() {
    715             return mAudioTracks;
    716         }
    717 
    718         @Override
    719         public List<AtscCaptionTrack> getCaptionTracks() {
    720             return mCaptionTracks;
    721         }
    722 
    723         public String getBroadcastGenre() {
    724             return mBroadcastGenre;
    725         }
    726 
    727         public String getCanonicalGenre() {
    728             return mCanonicalGenre;
    729         }
    730 
    731         @Override
    732         public void setHasCaptionTrack() {
    733             mHasCaptionTrack = true;
    734         }
    735 
    736         @Override
    737         public boolean hasCaptionTrack() {
    738             return mHasCaptionTrack;
    739         }
    740 
    741         @Override
    742         public int compareTo(@NonNull EitItem item) {
    743             // The list of caption tracks and the program ids are not compared in here because the
    744             // channels in TIF have the concept of the caption and audio tracks while the programs
    745             // do not and the programs in TIF only have a program id since they are the rows of
    746             // Content Provider.
    747             int ret = mEventId - item.getEventId();
    748             if (ret != 0) {
    749                 return ret;
    750             }
    751             ret = StringUtils.compare(mTitleText, item.getTitleText());
    752             if (ret != 0) {
    753                 return ret;
    754             }
    755             if (mStartTime > item.getStartTime()) {
    756                 return 1;
    757             } else if (mStartTime < item.getStartTime()) {
    758                 return -1;
    759             }
    760             if (mLengthInSecond > item.getLengthInSecond()) {
    761                 return 1;
    762             } else if (mLengthInSecond < item.getLengthInSecond()) {
    763                 return -1;
    764             }
    765 
    766             // Compares content ratings
    767             ret = StringUtils.compare(mContentRating, item.getContentRating());
    768             if (ret != 0) {
    769                 return ret;
    770             }
    771 
    772             // Compares broadcast genres
    773             ret = StringUtils.compare(mBroadcastGenre, item.getBroadcastGenre());
    774             if (ret != 0) {
    775                 return ret;
    776             }
    777             // Compares canonical genres
    778             ret = StringUtils.compare(mCanonicalGenre, item.getCanonicalGenre());
    779             if (ret != 0) {
    780                 return ret;
    781             }
    782 
    783             // Compares descriptions
    784             return StringUtils.compare(mDescription, item.getDescription());
    785         }
    786 
    787         public String getAudioLanguage() {
    788             if (mAudioTracks == null) {
    789                 return "";
    790             }
    791             ArrayList<String> languages = new ArrayList<>();
    792             for (AtscAudioTrack audioTrack : mAudioTracks) {
    793                 languages.add(audioTrack.language);
    794             }
    795             return TextUtils.join(",", languages);
    796         }
    797 
    798         @Override
    799         public String toString() {
    800             return String.format(Locale.US,
    801                     "EitItem programId: %d, eventId: %d, title: %s, startTime: %10d, "
    802                             + "length: %6d, rating: %s, audio tracks: %d, caption tracks: %d, "
    803                             + "genres (broadcast: %s, canonical: %s), description: %s",
    804                     mProgramId, mEventId, mTitleText, mStartTime, mLengthInSecond, mContentRating,
    805                     mAudioTracks != null ? mAudioTracks.size() : 0,
    806                     mCaptionTracks != null ? mCaptionTracks.size() : 0,
    807                     mBroadcastGenre, mCanonicalGenre, mDescription);
    808         }
    809     }
    810 
    811     public static class EttItem {
    812         public final int eventId;
    813         public final String text;
    814 
    815         public EttItem(int eventId, String text) {
    816             this.eventId = eventId;
    817             this.text = text;
    818         }
    819     }
    820 }
    821