1 /**
2  * Copyright (C) 2017 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 android.hardware.radio;
18 
19 import android.annotation.IntDef;
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SystemApi;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.RetentionPolicy;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.Objects;
33 import java.util.stream.Stream;
34 
35 /**
36  * A set of identifiers necessary to tune to a given station.
37  *
38  * This can hold various identifiers, like
39  * - AM/FM frequency
40  * - HD Radio subchannel
41  * - DAB channel info
42  *
43  * The primary ID uniquely identifies a station and can be used for equality
44  * check. The secondary IDs are supplementary and can speed up tuning process,
45  * but the primary ID is sufficient (ie. after a full band scan).
46  *
47  * Two selectors with different secondary IDs, but the same primary ID are
48  * considered equal. In particular, secondary IDs vector may get updated for
49  * an entry on the program list (ie. when a better frequency for a given
50  * station is found).
51  *
52  * The primaryId of a given programType MUST be of a specific type:
53  * - AM, FM: RDS_PI if the station broadcasts RDS, AMFM_FREQUENCY otherwise;
54  * - AM_HD, FM_HD: HD_STATION_ID_EXT;
55  * - DAB: DAB_SIDECC;
56  * - DRMO: DRMO_SERVICE_ID;
57  * - SXM: SXM_SERVICE_ID;
58  * - VENDOR: VENDOR_PRIMARY.
59  * @hide
60  */
61 @SystemApi
62 public final class ProgramSelector implements Parcelable {
63     /** Invalid program type.
64      * @deprecated use {@link ProgramIdentifier} instead
65      */
66     @Deprecated
67     public static final int PROGRAM_TYPE_INVALID = 0;
68     /** Analog AM radio (with or without RDS).
69      * @deprecated use {@link ProgramIdentifier} instead
70      */
71     @Deprecated
72     public static final int PROGRAM_TYPE_AM = 1;
73     /** analog FM radio (with or without RDS).
74      * @deprecated use {@link ProgramIdentifier} instead
75      */
76     @Deprecated
77     public static final int PROGRAM_TYPE_FM = 2;
78     /** AM HD Radio.
79      * @deprecated use {@link ProgramIdentifier} instead
80      */
81     @Deprecated
82     public static final int PROGRAM_TYPE_AM_HD = 3;
83     /** FM HD Radio.
84      * @deprecated use {@link ProgramIdentifier} instead
85      */
86     @Deprecated
87     public static final int PROGRAM_TYPE_FM_HD = 4;
88     /** Digital audio broadcasting.
89      * @deprecated use {@link ProgramIdentifier} instead
90      */
91     @Deprecated
92     public static final int PROGRAM_TYPE_DAB = 5;
93     /** Digital Radio Mondiale.
94      * @deprecated use {@link ProgramIdentifier} instead
95      */
96     @Deprecated
97     public static final int PROGRAM_TYPE_DRMO = 6;
98     /** SiriusXM Satellite Radio.
99      * @deprecated use {@link ProgramIdentifier} instead
100      */
101     @Deprecated
102     public static final int PROGRAM_TYPE_SXM = 7;
103     /** Vendor-specific, not synced across devices.
104      * @deprecated use {@link ProgramIdentifier} instead
105      */
106     @Deprecated
107     public static final int PROGRAM_TYPE_VENDOR_START = 1000;
108     /** @deprecated use {@link ProgramIdentifier} instead */
109     @Deprecated
110     public static final int PROGRAM_TYPE_VENDOR_END = 1999;
111     /** @deprecated use {@link ProgramIdentifier} instead */
112     @Deprecated
113     @IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
114         PROGRAM_TYPE_INVALID,
115         PROGRAM_TYPE_AM,
116         PROGRAM_TYPE_FM,
117         PROGRAM_TYPE_AM_HD,
118         PROGRAM_TYPE_FM_HD,
119         PROGRAM_TYPE_DAB,
120         PROGRAM_TYPE_DRMO,
121         PROGRAM_TYPE_SXM,
122     })
123     @IntRange(from = PROGRAM_TYPE_VENDOR_START, to = PROGRAM_TYPE_VENDOR_END)
124     @Retention(RetentionPolicy.SOURCE)
125     public @interface ProgramType {}
126 
127     public static final int IDENTIFIER_TYPE_INVALID = 0;
128     /**
129      * Primary identifier for analog (without RDS) AM/FM stations:
130      * frequency in kHz.
131      *
132      * <p>This identifier also contains band information:
133      * <li>
134      *     <ul><500kHz: AM LW.
135      *     <ul>500kHz - 1705kHz: AM MW.
136      *     <ul>1.71MHz - 30MHz: AM SW.
137      *     <ul>>60MHz: FM.
138      * </li>
139      */
140     public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
141     /**
142      * 16bit primary identifier for FM RDS station.
143      */
144     public static final int IDENTIFIER_TYPE_RDS_PI = 2;
145     /**
146      * 64bit compound primary identifier for HD Radio.
147      *
148      * <p>Consists of (from the LSB):
149      * <li>
150      *     <ul>32bit: Station ID number.
151      *     <ul>4bit: HD_SUBCHANNEL.
152      *     <ul>18bit: AMFM_FREQUENCY.
153      * </li>
154      *
155      * <p>While station ID number should be unique globally, it sometimes gets
156      * abused by broadcasters (i.e. not being set at all). To ensure local
157      * uniqueness, AMFM_FREQUENCY_KHZ was added here. Global uniqueness is
158      * a best-effort - see {@link IDENTIFIER_TYPE_HD_STATION_NAME}.
159      *
160      * <p>HD Radio subchannel is a value in range of 0-7.
161      * This index is 0-based (where 0 is MPS and 1..7 are SPS),
162      * as opposed to HD Radio standard (where it's 1-based).
163      *
164      * <p>The remaining bits should be set to zeros when writing on the chip side
165      * and ignored when read.
166      */
167     public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3;
168     /**
169      * HD Radio subchannel - a value in range of 0-7.
170      *
171      * <p>The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
172      * as opposed to HD Radio standard (where it's 1-based).
173      *
174      * @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead
175      */
176     @Deprecated
177     public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4;
178     /**
179      * 64bit additional identifier for HD Radio.
180      *
181      * <p>Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
182      * globally unique. To provide a best-effort solution, a short version of
183      * station name may be carried as additional identifier and may be used
184      * by the tuner hardware to double-check tuning.
185      *
186      * <p>The name is limited to the first 8 A-Z0-9 characters (lowercase
187      * letters must be converted to uppercase). Encoded in little-endian
188      * ASCII: the first character of the name is the LSB.
189      *
190      * <p>For example: "Abc" is encoded as 0x434241.
191      */
192     public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
193     /**
194      * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
195      *
196      * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
197      */
198     @Deprecated
199     public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5;
200     /**
201      * 28bit compound primary identifier for Digital Audio Broadcasting.
202      *
203      * <p>Consists of (from the LSB):
204      * <li>
205      *     <ul>16bit: SId.
206      *     <ul>8bit: ECC code.
207      *     <ul>4bit: SCIdS.
208      * </li>
209      *
210      * <p>SCIdS (Service Component Identifier within the Service) value
211      * of 0 represents the main service, while 1 and above represents
212      * secondary services.
213      *
214      * <p>The remaining bits should be set to zeros when writing on the chip
215      * side and ignored when read.
216      *
217      * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
218      */
219     @Deprecated
220     public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC;
221     /** 16bit */
222     public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6;
223     /** 12bit */
224     public static final int IDENTIFIER_TYPE_DAB_SCID = 7;
225     /** kHz */
226     public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8;
227     /**
228      * 24bit primary identifier for Digital Radio Mondiale.
229      */
230     public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9;
231     /** kHz */
232     public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
233     /**
234      * 1: AM, 2:FM
235      * @deprecated use {@link #IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
236      */
237     @Deprecated
238     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
239     /**
240      * 32bit primary identifier for SiriusXM Satellite Radio.
241      */
242     public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
243     /** 0-999 range */
244     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
245     /**
246      * 44bit compound primary identifier for Digital Audio Broadcasting and
247      * Digital Multimedia Broadcasting.
248      *
249      * <p>Consists of (from the LSB):
250      * - 32bit: SId;
251      * - 8bit: ECC code;
252      * - 4bit: SCIdS.
253      *
254      * <p>SCIdS (Service Component Identifier within the Service) value
255      * of 0 represents the main service, while 1 and above represents
256      * secondary services.
257      *
258      * <p>The remaining bits should be set to zeros when writing on the chip
259      * side and ignored when read.
260      */
261     public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14;
262     /**
263      * Primary identifier for vendor-specific radio technology.
264      * The value format is determined by a vendor.
265      *
266      * <p>It must not be used in any other programType than corresponding VENDOR
267      * type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must
268      * not be used in any program type other than 1015).
269      */
270     public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START;
271     /**
272      * @see {@link IDENTIFIER_TYPE_VENDOR_START}
273      */
274     public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
275     /**
276      * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_START} instead
277      */
278     @Deprecated
279     public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START;
280     /**
281      * @deprecated use {@link #IDENTIFIER_TYPE_VENDOR_END} instead
282      */
283     @Deprecated
284     public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END;
285     @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
286         IDENTIFIER_TYPE_INVALID,
287         IDENTIFIER_TYPE_AMFM_FREQUENCY,
288         IDENTIFIER_TYPE_RDS_PI,
289         IDENTIFIER_TYPE_HD_STATION_ID_EXT,
290         IDENTIFIER_TYPE_HD_SUBCHANNEL,
291         IDENTIFIER_TYPE_HD_STATION_NAME,
292         IDENTIFIER_TYPE_DAB_SID_EXT,
293         IDENTIFIER_TYPE_DAB_SIDECC,
294         IDENTIFIER_TYPE_DAB_ENSEMBLE,
295         IDENTIFIER_TYPE_DAB_SCID,
296         IDENTIFIER_TYPE_DAB_FREQUENCY,
297         IDENTIFIER_TYPE_DRMO_SERVICE_ID,
298         IDENTIFIER_TYPE_DRMO_FREQUENCY,
299         IDENTIFIER_TYPE_DRMO_MODULATION,
300         IDENTIFIER_TYPE_SXM_SERVICE_ID,
301         IDENTIFIER_TYPE_SXM_CHANNEL,
302         IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
303     })
304     @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END)
305     @Retention(RetentionPolicy.SOURCE)
306     public @interface IdentifierType {}
307 
308     private final @ProgramType int mProgramType;
309     private final @NonNull Identifier mPrimaryId;
310     private final @NonNull Identifier[] mSecondaryIds;
311     private final @NonNull long[] mVendorIds;
312 
313     /**
314      * Constructor for ProgramSelector.
315      *
316      * It's not desired to modify selector objects, so all its fields are initialized at creation.
317      *
318      * Identifier lists must not contain any nulls, but can itself be null to be interpreted
319      * as empty list at object creation.
320      *
321      * @param programType type of a radio technology.
322      * @param primaryId primary program identifier.
323      * @param secondaryIds list of secondary program identifiers.
324      * @param vendorIds list of vendor-specific program identifiers.
325      */
ProgramSelector(@rogramType int programType, @NonNull Identifier primaryId, @Nullable Identifier[] secondaryIds, @Nullable long[] vendorIds)326     public ProgramSelector(@ProgramType int programType, @NonNull Identifier primaryId,
327             @Nullable Identifier[] secondaryIds, @Nullable long[] vendorIds) {
328         if (secondaryIds == null) secondaryIds = new Identifier[0];
329         if (vendorIds == null) vendorIds = new long[0];
330         if (Stream.of(secondaryIds).anyMatch(id -> id == null)) {
331             throw new IllegalArgumentException("secondaryIds list must not contain nulls");
332         }
333         mProgramType = programType;
334         mPrimaryId = Objects.requireNonNull(primaryId);
335         mSecondaryIds = secondaryIds;
336         mVendorIds = vendorIds;
337     }
338 
339     /**
340      * Type of a radio technology.
341      *
342      * @return program type.
343      * @deprecated use {@link #getPrimaryId} instead
344      */
345     @Deprecated
getProgramType()346     public @ProgramType int getProgramType() {
347         return mProgramType;
348     }
349 
350     /**
351      * Primary program identifier uniquely identifies a station and is used to
352      * determine equality between two ProgramSelectors.
353      *
354      * @return primary identifier.
355      */
getPrimaryId()356     public @NonNull Identifier getPrimaryId() {
357         return mPrimaryId;
358     }
359 
360     /**
361      * Secondary program identifier is not required for tuning, but may make it
362      * faster or more reliable.
363      *
364      * @return secondary identifier list, must not be modified.
365      */
getSecondaryIds()366     public @NonNull Identifier[] getSecondaryIds() {
367         return mSecondaryIds;
368     }
369 
370     /**
371      * Looks up an identifier of a given type (either primary or secondary).
372      *
373      * If there are multiple identifiers if a given type, then first in order (where primary id is
374      * before any secondary) is selected.
375      *
376      * @param type type of identifier.
377      * @return identifier value, if found.
378      * @throws IllegalArgumentException, if not found.
379      */
getFirstId(@dentifierType int type)380     public long getFirstId(@IdentifierType int type) {
381         if (mPrimaryId.getType() == type) return mPrimaryId.getValue();
382         for (Identifier id : mSecondaryIds) {
383             if (id.getType() == type) return id.getValue();
384         }
385         throw new IllegalArgumentException("Identifier " + type + " not found");
386     }
387 
388     /**
389      * Looks up all identifier of a given type (either primary or secondary).
390      *
391      * Some identifiers may be provided multiple times, for example
392      * IDENTIFIER_TYPE_AMFM_FREQUENCY for FM Alternate Frequencies.
393      *
394      * @param type type of identifier.
395      * @return a list of identifiers, generated on each call. May be modified.
396      */
getAllIds(@dentifierType int type)397     public @NonNull Identifier[] getAllIds(@IdentifierType int type) {
398         List<Identifier> out = new ArrayList<>();
399 
400         if (mPrimaryId.getType() == type) out.add(mPrimaryId);
401         for (Identifier id : mSecondaryIds) {
402             if (id.getType() == type) out.add(id);
403         }
404 
405         return out.toArray(new Identifier[out.size()]);
406     }
407 
408     /**
409      * Vendor identifiers are passed as-is to the HAL implementation,
410      * preserving elements order.
411      *
412      * @return an array of vendor identifiers, must not be modified.
413      * @deprecated for HAL 1.x compatibility;
414      *             HAL 2.x uses standard primary/secondary lists for vendor IDs
415      */
416     @Deprecated
getVendorIds()417     public @NonNull long[] getVendorIds() {
418         return mVendorIds;
419     }
420 
421     /**
422      * Creates an equivalent ProgramSelector with a given secondary identifier preferred.
423      *
424      * Used to point to a specific physical identifier for technologies that may broadcast the same
425      * program on different channels. For example, with a DAB program broadcasted over multiple
426      * ensembles, the radio hardware may select the one with the strongest signal. The UI may select
427      * preferred ensemble though, so the radio hardware may try to use it in the first place.
428      *
429      * This is a best-effort hint for the tuner, not a guaranteed behavior.
430      *
431      * Setting the given secondary identifier as preferred means filtering out other secondary
432      * identifiers of its type and adding it to the list.
433      *
434      * @param preferred preferred secondary identifier
435      * @return a new ProgramSelector with a given secondary identifier preferred
436      */
withSecondaryPreferred(@onNull Identifier preferred)437     public @NonNull ProgramSelector withSecondaryPreferred(@NonNull Identifier preferred) {
438         int preferredType = preferred.getType();
439         Identifier[] secondaryIds = Stream.concat(
440             // remove other identifiers of that type
441             Arrays.stream(mSecondaryIds).filter(id -> id.getType() != preferredType),
442             // add preferred identifier instead
443             Stream.of(preferred)).toArray(Identifier[]::new);
444 
445         return new ProgramSelector(
446             mProgramType,
447             mPrimaryId,
448             secondaryIds,
449             mVendorIds
450         );
451     }
452 
453     /**
454      * Builds new ProgramSelector for AM/FM frequency.
455      *
456      * @param band the band.
457      * @param frequencyKhz the frequency in kHz.
458      * @return new ProgramSelector object representing given frequency.
459      * @throws IllegalArgumentException if provided frequency is out of bounds.
460      */
createAmFmSelector( @adioManager.Band int band, int frequencyKhz)461     public static @NonNull ProgramSelector createAmFmSelector(
462             @RadioManager.Band int band, int frequencyKhz) {
463         return createAmFmSelector(band, frequencyKhz, 0);
464     }
465 
466     /**
467      * Checks, if a given AM/FM frequency is roughly valid and in correct unit.
468      *
469      * It does not check the range precisely: it may provide false positives, but not false
470      * negatives. In particular, it may be way off for certain regions.
471      * The main purpose is to avoid passing inproper units, ie. MHz instead of kHz.
472      *
473      * @param isAm true, if AM, false if FM.
474      * @param frequencyKhz the frequency in kHz.
475      * @return true, if the frequency is rougly valid.
476      */
isValidAmFmFrequency(boolean isAm, int frequencyKhz)477     private static boolean isValidAmFmFrequency(boolean isAm, int frequencyKhz) {
478         if (isAm) {
479             return frequencyKhz > 150 && frequencyKhz <= 30000;
480         } else {
481             return frequencyKhz > 60000 && frequencyKhz < 110000;
482         }
483     }
484 
485     /**
486      * Builds new ProgramSelector for AM/FM frequency.
487      *
488      * This method variant supports HD Radio subchannels, but it's undesirable to
489      * select them manually. Instead, the value should be retrieved from program list.
490      *
491      * @param band the band.
492      * @param frequencyKhz the frequency in kHz.
493      * @param subChannel 1-based HD Radio subchannel.
494      * @return new ProgramSelector object representing given frequency.
495      * @throws IllegalArgumentException if provided frequency is out of bounds,
496      *         or tried setting a subchannel for analog AM/FM.
497      */
createAmFmSelector( @adioManager.Band int band, int frequencyKhz, int subChannel)498     public static @NonNull ProgramSelector createAmFmSelector(
499             @RadioManager.Band int band, int frequencyKhz, int subChannel) {
500         if (band == RadioManager.BAND_INVALID) {
501             // 50MHz is a rough boundary between AM (<30MHz) and FM (>60MHz).
502             if (frequencyKhz < 50000) {
503                 band = (subChannel <= 0) ? RadioManager.BAND_AM : RadioManager.BAND_AM_HD;
504             } else {
505                 band = (subChannel <= 0) ? RadioManager.BAND_FM : RadioManager.BAND_FM_HD;
506             }
507         }
508 
509         boolean isAm = (band == RadioManager.BAND_AM || band == RadioManager.BAND_AM_HD);
510         boolean isDigital = (band == RadioManager.BAND_AM_HD || band == RadioManager.BAND_FM_HD);
511         if (!isAm && !isDigital && band != RadioManager.BAND_FM) {
512             throw new IllegalArgumentException("Unknown band: " + band);
513         }
514         if (subChannel < 0 || subChannel > 8) {
515             throw new IllegalArgumentException("Invalid subchannel: " + subChannel);
516         }
517         if (subChannel > 0 && !isDigital) {
518             throw new IllegalArgumentException("Subchannels are not supported for non-HD radio");
519         }
520         if (!isValidAmFmFrequency(isAm, frequencyKhz)) {
521             throw new IllegalArgumentException("Provided value is not a valid AM/FM frequency: "
522                     + frequencyKhz);
523         }
524 
525         // We can't use AM_HD or FM_HD, because we don't know HD station ID.
526         @ProgramType int programType = isAm ? PROGRAM_TYPE_AM : PROGRAM_TYPE_FM;
527         Identifier primary = new Identifier(IDENTIFIER_TYPE_AMFM_FREQUENCY, frequencyKhz);
528 
529         Identifier[] secondary = null;
530         if (subChannel > 0) {
531             /* Stating sub channel for non-HD AM/FM does not give any guarantees,
532              * but we can't do much more without HD station ID.
533              *
534              * The legacy APIs had 1-based subChannels, while ProgramSelector is 0-based.
535              */
536             secondary = new Identifier[]{
537                     new Identifier(IDENTIFIER_TYPE_HD_SUBCHANNEL, subChannel - 1)};
538         }
539 
540         return new ProgramSelector(programType, primary, secondary, null);
541     }
542 
543     @NonNull
544     @Override
toString()545     public String toString() {
546         StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType)
547                 .append(", primary=").append(mPrimaryId);
548         if (mSecondaryIds.length > 0) {
549             sb.append(", secondary=").append(Arrays.toString(mSecondaryIds));
550         }
551         if (mVendorIds.length > 0) {
552             sb.append(", vendor=").append(Arrays.toString(mVendorIds));
553         }
554         sb.append(")");
555         return sb.toString();
556     }
557 
558     @Override
hashCode()559     public int hashCode() {
560         // secondaryIds and vendorIds are ignored for equality/hashing
561         return mPrimaryId.hashCode();
562     }
563 
564     @Override
equals(@ullable Object obj)565     public boolean equals(@Nullable Object obj) {
566         if (this == obj) return true;
567         if (!(obj instanceof ProgramSelector)) return false;
568         ProgramSelector other = (ProgramSelector) obj;
569         // secondaryIds and vendorIds are ignored for equality/hashing
570         // programType can be inferred from primaryId, thus not checked
571         return mPrimaryId.equals(other.getPrimaryId());
572     }
573 
574     /** @hide */
strictEquals(@ullable Object obj)575     public boolean strictEquals(@Nullable Object obj) {
576         if (this == obj) return true;
577         if (!(obj instanceof ProgramSelector)) return false;
578         ProgramSelector other = (ProgramSelector) obj;
579         // vendorIds are ignored for equality
580         // programType can be inferred from primaryId, thus not checked
581         return mPrimaryId.equals(other.getPrimaryId())
582                 && mSecondaryIds.length == other.mSecondaryIds.length
583                 && Arrays.asList(mSecondaryIds).containsAll(
584                         Arrays.asList(other.mSecondaryIds));
585     }
586 
ProgramSelector(Parcel in)587     private ProgramSelector(Parcel in) {
588         mProgramType = in.readInt();
589         mPrimaryId = in.readTypedObject(Identifier.CREATOR);
590         mSecondaryIds = in.createTypedArray(Identifier.CREATOR);
591         if (Stream.of(mSecondaryIds).anyMatch(id -> id == null)) {
592             throw new IllegalArgumentException("secondaryIds list must not contain nulls");
593         }
594         mVendorIds = in.createLongArray();
595     }
596 
597     @Override
writeToParcel(Parcel dest, int flags)598     public void writeToParcel(Parcel dest, int flags) {
599         dest.writeInt(mProgramType);
600         dest.writeTypedObject(mPrimaryId, 0);
601         dest.writeTypedArray(mSecondaryIds, 0);
602         dest.writeLongArray(mVendorIds);
603     }
604 
605     @Override
describeContents()606     public int describeContents() {
607         return 0;
608     }
609 
610     public static final @android.annotation.NonNull Parcelable.Creator<ProgramSelector> CREATOR =
611             new Parcelable.Creator<ProgramSelector>() {
612         public ProgramSelector createFromParcel(Parcel in) {
613             return new ProgramSelector(in);
614         }
615 
616         public ProgramSelector[] newArray(int size) {
617             return new ProgramSelector[size];
618         }
619     };
620 
621     /**
622      * A single program identifier component, eg. frequency or channel ID.
623      *
624      * The long value field holds the value in format described in comments for
625      * IdentifierType constants.
626      */
627     public static final class Identifier implements Parcelable {
628         private final @IdentifierType int mType;
629         private final long mValue;
630 
Identifier(@dentifierType int type, long value)631         public Identifier(@IdentifierType int type, long value) {
632             if (type == IDENTIFIER_TYPE_HD_STATION_NAME) {
633                 // see getType
634                 type = IDENTIFIER_TYPE_HD_SUBCHANNEL;
635             }
636             mType = type;
637             mValue = value;
638         }
639 
640         /**
641          * Type of an identifier.
642          *
643          * @return type of an identifier.
644          */
getType()645         public @IdentifierType int getType() {
646             if (mType == IDENTIFIER_TYPE_HD_SUBCHANNEL && mValue > 10) {
647                 /* HD_SUBCHANNEL and HD_STATION_NAME use the same identifier type, but they differ
648                  * in possible values: sub channel is 0-7, station name is greater than ASCII space
649                  * code (32).
650                  */
651                 return IDENTIFIER_TYPE_HD_STATION_NAME;
652             }
653             return mType;
654         }
655 
656         /**
657          * Returns whether this Identifier's type is considered a category when filtering
658          * ProgramLists for category entries.
659          *
660          * @see {@link ProgramList.Filter#areCategoriesIncluded()}
661          * @return False if this identifier's type is not tuneable (e.g. DAB ensemble or
662          *         vendor-specified type). True otherwise.
663          */
isCategoryType()664         public boolean isCategoryType() {
665             return (mType >= IDENTIFIER_TYPE_VENDOR_START && mType <= IDENTIFIER_TYPE_VENDOR_END)
666                     || mType == IDENTIFIER_TYPE_DAB_ENSEMBLE;
667         }
668 
669         /**
670          * Value of an identifier.
671          *
672          * Its meaning depends on identifier type, ie. for IDENTIFIER_TYPE_AMFM_FREQUENCY type,
673          * the value is a frequency in kHz.
674          *
675          * The range of a value depends on its type; it does not always require the whole long
676          * range. Casting to necessary type (ie. int) without range checking is correct in front-end
677          * code - any range violations are either errors in the framework or in the
678          * HAL implementation. For example, IDENTIFIER_TYPE_AMFM_FREQUENCY always fits in int,
679          * as Integer.MAX_VALUE would mean 2.1THz.
680          *
681          * @return value of an identifier.
682          */
getValue()683         public long getValue() {
684             return mValue;
685         }
686 
687         @NonNull
688         @Override
toString()689         public String toString() {
690             return "Identifier(" + mType + ", " + mValue + ")";
691         }
692 
693         @Override
hashCode()694         public int hashCode() {
695             return Objects.hash(mType, mValue);
696         }
697 
698         @Override
equals(@ullable Object obj)699         public boolean equals(@Nullable Object obj) {
700             if (this == obj) return true;
701             if (!(obj instanceof Identifier)) return false;
702             Identifier other = (Identifier) obj;
703             return other.getType() == mType && other.getValue() == mValue;
704         }
705 
Identifier(Parcel in)706         private Identifier(Parcel in) {
707             mType = in.readInt();
708             mValue = in.readLong();
709         }
710 
711         @Override
writeToParcel(Parcel dest, int flags)712         public void writeToParcel(Parcel dest, int flags) {
713             dest.writeInt(mType);
714             dest.writeLong(mValue);
715         }
716 
717         @Override
describeContents()718         public int describeContents() {
719             return 0;
720         }
721 
722         public static final @android.annotation.NonNull Parcelable.Creator<Identifier> CREATOR =
723                 new Parcelable.Creator<Identifier>() {
724             public Identifier createFromParcel(Parcel in) {
725                 return new Identifier(in);
726             }
727 
728             public Identifier[] newArray(int size) {
729                 return new Identifier[size];
730             }
731         };
732     }
733 }
734