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.database.Cursor;
20 import android.support.annotation.NonNull;
21 import android.util.Log;
22 import com.android.tv.common.util.StringUtils;
23 import com.android.tv.tuner.data.Channel.DeliverySystemType;
24 import com.android.tv.tuner.data.Channel.TunerChannelProto;
25 import com.android.tv.tuner.data.Track.AtscAudioTrack;
26 import com.android.tv.tuner.data.Track.AtscCaptionTrack;
27 import java.io.IOException;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.List;
32 import java.util.Objects;
33 
34 /** A class that represents a single channel accessible through a tuner. */
35 public class TunerChannel implements Comparable<TunerChannel>, PsipData.TvTracksInterface {
36     private static final String TAG = "TunerChannel";
37 
38     /** Channel number separator between major number and minor number. */
39     public static final char CHANNEL_NUMBER_SEPARATOR = '-';
40 
41     // See ATSC Code Points Registry.
42     private static final String[] ATSC_SERVICE_TYPE_NAMES =
43             new String[] {
44                 "ATSC Reserved",
45                 "Analog television channels",
46                 "ATSC_digital_television",
47                 "ATSC_audio",
48                 "ATSC_data_only_service",
49                 "Software Download",
50                 "Unassociated/Small Screen Service",
51                 "Parameterized Service",
52                 "ATSC NRT Service",
53                 "Extended Parameterized Service"
54             };
55     private static final String ATSC_SERVICE_TYPE_NAME_RESERVED =
56             ATSC_SERVICE_TYPE_NAMES[Channel.AtscServiceType.SERVICE_TYPE_ATSC_RESERVED_VALUE];
57 
58     public static final int INVALID_FREQUENCY = -1;
59 
60     // According to RFC4259, The number of available PIDs ranges from 0 to 8191.
61     public static final int INVALID_PID = -1;
62 
63     // According to ISO13818-1, Mpeg2 StreamType has a range from 0x00 to 0xff.
64     public static final int INVALID_STREAMTYPE = -1;
65 
66     // @GuardedBy(this) Writing operations and toByteArray will be guarded. b/34197766
67     private TunerChannelProto mProto;
68 
TunerChannel( PsipData.VctItem channel, int programNumber, List<PsiData.PmtItem> pmtItems, Channel.TunerType type)69     private TunerChannel(
70             PsipData.VctItem channel,
71             int programNumber,
72             List<PsiData.PmtItem> pmtItems,
73             Channel.TunerType type) {
74         String shortName = "";
75         String longName = "";
76         String description = "";
77         int tsid = 0;
78         int virtualMajor = 0;
79         int virtualMinor = 0;
80         Channel.AtscServiceType serviceType =
81                 Channel.AtscServiceType.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION;
82         if (channel != null) {
83             shortName = channel.getShortName();
84             tsid = channel.getChannelTsid();
85             programNumber = channel.getProgramNumber();
86             virtualMajor = channel.getMajorChannelNumber();
87             virtualMinor = channel.getMinorChannelNumber();
88             Channel.AtscServiceType chanServiceType =
89                     Channel.AtscServiceType.forNumber(channel.getServiceType());
90             if (chanServiceType != null) {
91                 serviceType = chanServiceType;
92             }
93             longName = (channel.getLongName() != null ? channel.getLongName() : longName);
94             description =
95                     (channel.getDescription() != null ? channel.getDescription() : description);
96         }
97         TunerChannelProto tunerChannelProto =
98                 TunerChannelProto.newBuilder()
99                         .setShortName(shortName)
100                         .setTsid(tsid)
101                         .setProgramNumber(programNumber)
102                         .setVirtualMajor(virtualMajor)
103                         .setVirtualMinor(virtualMinor)
104                         .setServiceType(serviceType)
105                         .setLongName(longName)
106                         .setDescription(description)
107                         .build();
108         initProto(pmtItems, type, tunerChannelProto);
109     }
110 
initProto( List<PsiData.PmtItem> pmtItems, Channel.TunerType type, TunerChannelProto tunerChannelProto)111     private void initProto(
112             List<PsiData.PmtItem> pmtItems,
113             Channel.TunerType type,
114             TunerChannelProto tunerChannelProto) {
115         int videoPid = INVALID_PID;
116         int pcrPid = 0;
117         Channel.VideoStreamType videoStreamType = Channel.VideoStreamType.UNSET;
118         List<Integer> audioPids = new ArrayList<>();
119         List<Channel.AudioStreamType> audioStreamTypes = new ArrayList<>();
120         for (PsiData.PmtItem pmt : pmtItems) {
121             switch (pmt.getStreamType()) {
122                     // MPEG ES stream video types
123                 case Channel.VideoStreamType.MPEG1_VALUE:
124                 case Channel.VideoStreamType.MPEG2_VALUE:
125                 case Channel.VideoStreamType.H263_VALUE:
126                 case Channel.VideoStreamType.H264_VALUE:
127                 case Channel.VideoStreamType.H265_VALUE:
128                     videoPid = pmt.getEsPid();
129                     videoStreamType = Channel.VideoStreamType.forNumber(pmt.getStreamType());
130                     break;
131 
132                     // MPEG ES stream audio types
133                 case Channel.AudioStreamType.MPEG1AUDIO_VALUE:
134                 case Channel.AudioStreamType.MPEG2AUDIO_VALUE:
135                 case Channel.AudioStreamType.MPEG2AACAUDIO_VALUE:
136                 case Channel.AudioStreamType.MPEG4LATMAACAUDIO_VALUE:
137                 case Channel.AudioStreamType.A52AC3AUDIO_VALUE:
138                 case Channel.AudioStreamType.EAC3AUDIO_VALUE:
139                     audioPids.add(pmt.getEsPid());
140                     audioStreamTypes.add(Channel.AudioStreamType.forNumber(pmt.getStreamType()));
141                     break;
142 
143                     // Non MPEG ES stream types
144                 case 0x100: // PmtItem.ES_PID_PCR:
145                     pcrPid = pmt.getEsPid();
146                     break;
147                 default:
148                     // fall out
149             }
150         }
151         mProto =
152                 TunerChannelProto.newBuilder(tunerChannelProto)
153                         .setType(type)
154                         .setChannelId(-1L)
155                         .setFrequency(INVALID_FREQUENCY)
156                         .setVideoPid(videoPid)
157                         .setVideoStreamType(videoStreamType)
158                         .addAllAudioPids(audioPids)
159                         .setAudioTrackIndex(audioPids.isEmpty() ? -1 : 0)
160                         .addAllAudioStreamTypes(audioStreamTypes)
161                         .setPcrPid(pcrPid)
162                         .build();
163     }
164 
165     // b/141952456 may cause hasAudioTrackIndex and hasVideo to return false
initChannelInfoIfNeeded()166     private void initChannelInfoIfNeeded() {
167         if (!mProto.hasAudioTrackIndex()) {
168             // Force select audio track 0
169             selectAudioTrack(0);
170         }
171         if (!mProto.hasVideoPid() && mProto.getVideoPid() != INVALID_PID) {
172             // Force set Video PID
173             setVideoPid(mProto.getVideoPid());
174         }
175     }
176 
TunerChannel( int programNumber, Channel.TunerType type, PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems)177     private TunerChannel(
178             int programNumber,
179             Channel.TunerType type,
180             PsipData.SdtItem channel,
181             List<PsiData.PmtItem> pmtItems) {
182         String shortName = "";
183         Channel.AtscServiceType serviceType =
184                 Channel.AtscServiceType.SERVICE_TYPE_ATSC_DIGITAL_TELEVISION;
185         if (channel != null) {
186             shortName = channel.getServiceName();
187             programNumber = channel.getServiceId();
188             Channel.AtscServiceType chanServiceType =
189                     Channel.AtscServiceType.forNumber(channel.getServiceType());
190             if (chanServiceType != null) {
191                 serviceType = chanServiceType;
192             }
193         }
194         TunerChannelProto tunerChannelProto =
195                 TunerChannelProto.newBuilder()
196                         .setShortName(shortName)
197                         .setProgramNumber(programNumber)
198                         .setServiceType(serviceType)
199                         .build();
200         initProto(pmtItems, type, tunerChannelProto);
201     }
202 
203     /** Initialize tuner channel with VCT items and PMT items. */
TunerChannel(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems)204     public TunerChannel(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
205         this(channel, 0, pmtItems, Channel.TunerType.TYPE_TUNER);
206     }
207 
208     /** Initialize tuner channel with program number and PMT items. */
TunerChannel(int programNumber, List<PsiData.PmtItem> pmtItems)209     public TunerChannel(int programNumber, List<PsiData.PmtItem> pmtItems) {
210         this(null, programNumber, pmtItems, Channel.TunerType.TYPE_TUNER);
211     }
212 
213     /** Initialize tuner channel with SDT items and PMT items. */
TunerChannel(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems)214     public TunerChannel(PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
215         this(0, Channel.TunerType.TYPE_TUNER, channel, pmtItems);
216     }
217 
TunerChannel(TunerChannelProto tunerChannelProto)218     private TunerChannel(TunerChannelProto tunerChannelProto) {
219         mProto = tunerChannelProto;
220         initChannelInfoIfNeeded();
221     }
222 
forFile(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems)223     public static TunerChannel forFile(PsipData.VctItem channel, List<PsiData.PmtItem> pmtItems) {
224         return new TunerChannel(channel, 0, pmtItems, Channel.TunerType.TYPE_FILE);
225     }
226 
forDvbFile( PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems)227     public static TunerChannel forDvbFile(
228             PsipData.SdtItem channel, List<PsiData.PmtItem> pmtItems) {
229         return new TunerChannel(0, Channel.TunerType.TYPE_FILE, channel, pmtItems);
230     }
231 
232     /**
233      * Create a TunerChannel object suitable for network tuners
234      *
235      * @param major Channel number major
236      * @param minor Channel number minor
237      * @param programNumber Program number
238      * @param shortName Short name
239      * @param recordingProhibited Recording prohibition info
240      * @param videoFormat Video format. Should be {@code null} or one of the followings: {@link
241      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_240P}, {@link
242      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_360P}, {@link
243      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_480I}, {@link
244      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_480P}, {@link
245      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_576I}, {@link
246      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_576P}, {@link
247      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_720P}, {@link
248      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080I}, {@link
249      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_1080P}, {@link
250      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_2160P}, {@link
251      *     android.media.tv.TvContract.Channels#VIDEO_FORMAT_4320P}
252      * @return a TunerChannel object
253      */
forNetwork( int major, int minor, int programNumber, String shortName, boolean recordingProhibited, String videoFormat)254     public static TunerChannel forNetwork(
255             int major,
256             int minor,
257             int programNumber,
258             String shortName,
259             boolean recordingProhibited,
260             String videoFormat) {
261         TunerChannel tunerChannel =
262                 new TunerChannel(
263                         null,
264                         programNumber,
265                         Collections.EMPTY_LIST,
266                         Channel.TunerType.TYPE_NETWORK);
267         tunerChannel.setVirtualMajor(major);
268         tunerChannel.setVirtualMinor(minor);
269         tunerChannel.setShortName(shortName);
270         // Set audio and video pids in order to work around the audio-only channel check.
271         tunerChannel.setAudioPids(new ArrayList<>(Arrays.asList(0)));
272         tunerChannel.selectAudioTrack(0);
273         tunerChannel.setVideoPid(0);
274         tunerChannel.setRecordingProhibited(recordingProhibited);
275         if (videoFormat != null) {
276             tunerChannel.setVideoFormat(videoFormat);
277         }
278         return tunerChannel;
279     }
280 
getName()281     public String getName() {
282         return !mProto.getShortName().isEmpty() ? mProto.getShortName() : mProto.getLongName();
283     }
284 
getShortName()285     public String getShortName() {
286         return mProto.getShortName();
287     }
288 
getProgramNumber()289     public int getProgramNumber() {
290         return mProto.getProgramNumber();
291     }
292 
getServiceType()293     public int getServiceType() {
294         return mProto.getServiceType().getNumber();
295     }
296 
getServiceTypeName()297     public String getServiceTypeName() {
298         int serviceType = getServiceType();
299         if (serviceType >= 0 && serviceType < ATSC_SERVICE_TYPE_NAMES.length) {
300             return ATSC_SERVICE_TYPE_NAMES[serviceType];
301         }
302         return ATSC_SERVICE_TYPE_NAME_RESERVED;
303     }
304 
getVirtualMajor()305     public int getVirtualMajor() {
306         return mProto.getVirtualMajor();
307     }
308 
getVirtualMinor()309     public int getVirtualMinor() {
310         return mProto.getVirtualMinor();
311     }
312 
getDeliverySystemType()313     public DeliverySystemType getDeliverySystemType() {
314         return mProto.getDeliverySystemType();
315     }
316 
getFrequency()317     public int getFrequency() {
318         return mProto.getFrequency();
319     }
320 
getModulation()321     public String getModulation() {
322         return mProto.getModulation();
323     }
324 
getTsid()325     public int getTsid() {
326         return mProto.getTsid();
327     }
328 
getVideoPid()329     public int getVideoPid() {
330         return mProto.getVideoPid();
331     }
332 
setVideoPid(int videoPid)333     public synchronized void setVideoPid(int videoPid) {
334         mProto = mProto.toBuilder().setVideoPid(videoPid).build();
335     }
336 
getVideoStreamType()337     public int getVideoStreamType() {
338         return mProto.getVideoStreamType().getNumber();
339     }
340 
getAudioPid()341     public int getAudioPid() {
342         if (!mProto.hasAudioTrackIndex() || mProto.getAudioTrackIndex() == -1) {
343             return INVALID_PID;
344         }
345         return mProto.getAudioPids(mProto.getAudioTrackIndex());
346     }
347 
getAudioStreamType()348     public int getAudioStreamType() {
349         if (!mProto.hasAudioTrackIndex() || mProto.getAudioTrackIndex() == -1) {
350             return INVALID_STREAMTYPE;
351         }
352         return mProto.getAudioStreamTypes(mProto.getAudioTrackIndex()).getNumber();
353     }
354 
getAudioPids()355     public List<Integer> getAudioPids() {
356         return mProto.getAudioPidsList();
357     }
358 
setAudioPids(List<Integer> audioPids)359     public synchronized void setAudioPids(List<Integer> audioPids) {
360         mProto = mProto.toBuilder().clearAudioPids().addAllAudioPids(audioPids).build();
361     }
362 
getAudioStreamTypes()363     public List<Integer> getAudioStreamTypes() {
364         List<Channel.AudioStreamType> audioStreamTypes = mProto.getAudioStreamTypesList();
365         List<Integer> audioStreamTypesValues = new ArrayList<>(audioStreamTypes.size());
366 
367         for (Channel.AudioStreamType audioStreamType : audioStreamTypes) {
368             audioStreamTypesValues.add(audioStreamType.getNumber());
369         }
370         return audioStreamTypesValues;
371     }
372 
setAudioStreamTypes(List<Integer> audioStreamTypesValues)373     public synchronized void setAudioStreamTypes(List<Integer> audioStreamTypesValues) {
374         List<Channel.AudioStreamType> audioStreamTypes =
375                 new ArrayList<>(audioStreamTypesValues.size());
376 
377         for (Integer audioStreamTypesValue : audioStreamTypesValues) {
378             audioStreamTypes.add(Channel.AudioStreamType.forNumber(audioStreamTypesValue));
379         }
380         mProto =
381                 mProto.toBuilder()
382                         .clearAudioStreamTypes()
383                         .addAllAudioStreamTypes(audioStreamTypes)
384                         .build();
385     }
386 
getPcrPid()387     public int getPcrPid() {
388         return mProto.getPcrPid();
389     }
390 
getType()391     public Channel.TunerType getType() {
392         return mProto.getType();
393     }
394 
setFilepath(String filepath)395     public synchronized void setFilepath(String filepath) {
396         mProto = mProto.toBuilder().setFilepath(filepath == null ? "" : filepath).build();
397     }
398 
getFilepath()399     public String getFilepath() {
400         return mProto.getFilepath();
401     }
402 
setVirtualMajor(int virtualMajor)403     public synchronized void setVirtualMajor(int virtualMajor) {
404         mProto = mProto.toBuilder().setVirtualMajor(virtualMajor).build();
405     }
406 
setVirtualMinor(int virtualMinor)407     public synchronized void setVirtualMinor(int virtualMinor) {
408         mProto = mProto.toBuilder().setVirtualMinor(virtualMinor).build();
409     }
410 
setShortName(String shortName)411     public synchronized void setShortName(String shortName) {
412         mProto = mProto.toBuilder().setShortName(shortName == null ? "" : shortName).build();
413     }
414 
setDeliverySystemType(DeliverySystemType deliverySystemType)415     public synchronized void setDeliverySystemType(DeliverySystemType deliverySystemType) {
416         mProto = mProto.toBuilder().setDeliverySystemType(deliverySystemType).build();
417     }
418 
setFrequency(int frequency)419     public synchronized void setFrequency(int frequency) {
420         mProto = mProto.toBuilder().setFrequency(frequency).build();
421     }
422 
setModulation(String modulation)423     public synchronized void setModulation(String modulation) {
424         mProto = mProto.toBuilder().setModulation(modulation == null ? "" : modulation).build();
425     }
426 
hasVideo()427     public boolean hasVideo() {
428         return mProto.hasVideoPid() && mProto.getVideoPid() != INVALID_PID;
429     }
430 
hasAudio()431     public boolean hasAudio() {
432         return getAudioPid() != INVALID_PID;
433     }
434 
getChannelId()435     public long getChannelId() {
436         return mProto.getChannelId();
437     }
438 
setChannelId(long channelId)439     public synchronized void setChannelId(long channelId) {
440         mProto = mProto.toBuilder().setChannelId(channelId).build();
441     }
442 
443     /**
444      * The flag indicating whether this TV channel is locked or not. This is primarily used for
445      * alternative parental control to prevent unauthorized users from watching the current channel
446      * regardless of the content rating
447      *
448      * @see <a
449      *     href="https://developer.android.com/reference/android/media/tv/TvContract.Channels.html#COLUMN_LOCKED">link</a>
450      */
isLocked()451     public boolean isLocked() {
452         return mProto.getLocked();
453     }
454 
setLocked(boolean locked)455     public synchronized void setLocked(boolean locked) {
456         mProto = mProto.toBuilder().setLocked(locked).build();
457     }
458 
getDisplayNumber()459     public String getDisplayNumber() {
460         return getDisplayNumber(true);
461     }
462 
getDisplayNumber(boolean ignoreZeroMinorNumber)463     public String getDisplayNumber(boolean ignoreZeroMinorNumber) {
464         if (getVirtualMajor() != 0 && (getVirtualMinor() != 0 || !ignoreZeroMinorNumber)) {
465             return String.format(
466                     "%d%c%d", getVirtualMajor(), CHANNEL_NUMBER_SEPARATOR, getVirtualMinor());
467         } else if (getVirtualMajor() != 0) {
468             return Integer.toString(getVirtualMajor());
469         } else {
470             return Integer.toString(getProgramNumber());
471         }
472     }
473 
getDescription()474     public String getDescription() {
475         return mProto.getDescription();
476     }
477 
478     @Override
setHasCaptionTrack()479     public synchronized void setHasCaptionTrack() {
480         mProto = mProto.toBuilder().setHasCaptionTrack(true).build();
481     }
482 
483     @Override
hasCaptionTrack()484     public boolean hasCaptionTrack() {
485         return mProto.getHasCaptionTrack();
486     }
487 
488     @Override
getAudioTracks()489     public List<AtscAudioTrack> getAudioTracks() {
490         return mProto.getAudioTracksList();
491     }
492 
setAudioTracks(List<AtscAudioTrack> audioTracks)493     public synchronized void setAudioTracks(List<AtscAudioTrack> audioTracks) {
494         mProto = mProto.toBuilder().clearAudioTracks().addAllAudioTracks(audioTracks).build();
495     }
496 
497     @Override
getCaptionTracks()498     public List<AtscCaptionTrack> getCaptionTracks() {
499         return mProto.getCaptionTracksList();
500     }
501 
setCaptionTracks(List<AtscCaptionTrack> captionTracks)502     public synchronized void setCaptionTracks(List<AtscCaptionTrack> captionTracks) {
503         mProto = mProto.toBuilder().clearCaptionTracks().addAllCaptionTracks(captionTracks).build();
504     }
505 
selectAudioTrack(int index)506     public synchronized void selectAudioTrack(int index) {
507         if (index < 0 || index >= mProto.getAudioPidsCount()) {
508             index = -1;
509         }
510         mProto = mProto.toBuilder().setAudioTrackIndex(index).build();
511     }
512 
setRecordingProhibited(boolean recordingProhibited)513     public synchronized void setRecordingProhibited(boolean recordingProhibited) {
514         mProto = mProto.toBuilder().setRecordingProhibited(recordingProhibited).build();
515     }
516 
isRecordingProhibited()517     public boolean isRecordingProhibited() {
518         return mProto.getRecordingProhibited();
519     }
520 
setVideoFormat(String videoFormat)521     public synchronized void setVideoFormat(String videoFormat) {
522         mProto = mProto.toBuilder().setVideoFormat(videoFormat == null ? "" : videoFormat).build();
523     }
524 
getVideoFormat()525     public String getVideoFormat() {
526         return mProto.getVideoFormat();
527     }
528 
529     @Override
toString()530     public String toString() {
531         switch (getType()) {
532             case TYPE_FILE:
533                 return String.format(
534                         "{%d-%d %s} Filepath: %s, ProgramNumber %d",
535                         getVirtualMajor(),
536                         getVirtualMinor(),
537                         getShortName(),
538                         getFilepath(),
539                         getProgramNumber());
540                 // case Channel.TunerType.TYPE_TUNER:
541             default:
542                 return String.format(
543                         "{%d-%d %s} Frequency: %d, ProgramNumber %d",
544                         getVirtualMajor(),
545                         getVirtualMinor(),
546                         getShortName(),
547                         getFrequency(),
548                         getProgramNumber());
549         }
550     }
551 
552     @Override
compareTo(@onNull TunerChannel channel)553     public int compareTo(@NonNull TunerChannel channel) {
554         // In the same frequency, the program number acts as the sub-channel number.
555         int ret = getFrequency() - channel.getFrequency();
556         if (ret != 0) {
557             return ret;
558         }
559         ret = getProgramNumber() - channel.getProgramNumber();
560         if (ret != 0) {
561             return ret;
562         }
563         ret = StringUtils.compare(getName(), channel.getName());
564         if (ret != 0) {
565             return ret;
566         }
567         if (getDeliverySystemType() != channel.getDeliverySystemType()) {
568             return 1;
569         }
570         // For FileTsStreamer, file paths should be compared.
571         return StringUtils.compare(getFilepath(), channel.getFilepath());
572     }
573 
574     @Override
equals(Object o)575     public boolean equals(Object o) {
576         if (!(o instanceof TunerChannel)) {
577             return false;
578         }
579         return compareTo((TunerChannel) o) == 0;
580     }
581 
582     @Override
hashCode()583     public int hashCode() {
584         return Objects.hash(getDeliverySystemType(), getFrequency(), getProgramNumber(), getName(),
585                 getFilepath());
586     }
587 
588     // Serialization
toByteArray()589     public synchronized byte[] toByteArray() {
590         try {
591             return mProto.toByteArray();
592         } catch (Exception e) {
593             // Retry toByteArray. b/34197766
594             Log.w(
595                     TAG,
596                     "TunerChannel or its variables are modified in multiple thread without lock",
597                     e);
598             return mProto.toByteArray();
599         }
600     }
601 
parseFrom(byte[] data)602     public static TunerChannel parseFrom(byte[] data) {
603         if (data == null) {
604             return null;
605         }
606         try {
607             return new TunerChannel(TunerChannelProto.parseFrom(data));
608         } catch (IOException e) {
609             Log.e(TAG, "Could not parse from byte array", e);
610             return null;
611         }
612     }
613 
fromCursor(Cursor cursor)614     public static TunerChannel fromCursor(Cursor cursor) {
615         long channelId = cursor.getLong(0);
616         boolean locked = cursor.getInt(1) > 0;
617         byte[] data = cursor.getBlob(2);
618         TunerChannel channel = TunerChannel.parseFrom(data);
619         if (channel != null) {
620             channel.setChannelId(channelId);
621             channel.setLocked(locked);
622         }
623         return channel;
624     }
625 }
626