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