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
17package com.android.tv.tuner.data;
18
19import android.support.annotation.NonNull;
20import android.text.TextUtils;
21import android.text.format.DateUtils;
22
23import com.android.tv.tuner.data.nano.Track.AtscAudioTrack;
24import com.android.tv.tuner.data.nano.Track.AtscCaptionTrack;
25import com.android.tv.tuner.ts.SectionParser;
26import com.android.tv.tuner.util.ConvertUtils;
27import com.android.tv.util.StringUtils;
28
29import java.util.ArrayList;
30import java.util.HashMap;
31import java.util.List;
32import java.util.Locale;
33
34/**
35 * Collection of ATSC PSIP table items.
36 */
37public 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