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