blob: 435bcb020a910c2f00deac42122720b085e44095 [file] [log] [blame]
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -07001/**
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
17package android.hardware.radio;
18
19import android.annotation.IntDef;
Tomasz Wasilczykd2b5cfb2017-08-04 12:56:47 -070020import android.annotation.IntRange;
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -070021import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.SystemApi;
24import android.os.Parcel;
25import android.os.Parcelable;
26
27import java.lang.annotation.Retention;
28import java.lang.annotation.RetentionPolicy;
29import java.util.ArrayList;
Tomasz Wasilczykeab3e552018-01-11 20:12:10 -080030import java.util.Arrays;
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -070031import java.util.List;
32import java.util.Objects;
33import 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
62public final class ProgramSelector implements Parcelable {
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -080063 /** Invalid program type.
64 * @deprecated use {@link ProgramIdentifier} instead
65 */
66 @Deprecated
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -080067 public static final int PROGRAM_TYPE_INVALID = 0;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -080068 /** Analogue AM radio (with or without RDS).
69 * @deprecated use {@link ProgramIdentifier} instead
70 */
71 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -070072 public static final int PROGRAM_TYPE_AM = 1;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -080073 /** analogue FM radio (with or without RDS).
74 * @deprecated use {@link ProgramIdentifier} instead
75 */
76 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -070077 public static final int PROGRAM_TYPE_FM = 2;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -080078 /** AM HD Radio.
79 * @deprecated use {@link ProgramIdentifier} instead
80 */
81 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -070082 public static final int PROGRAM_TYPE_AM_HD = 3;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -080083 /** FM HD Radio.
84 * @deprecated use {@link ProgramIdentifier} instead
85 */
86 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -070087 public static final int PROGRAM_TYPE_FM_HD = 4;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -080088 /** Digital audio broadcasting.
89 * @deprecated use {@link ProgramIdentifier} instead
90 */
91 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -070092 public static final int PROGRAM_TYPE_DAB = 5;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -080093 /** Digital Radio Mondiale.
94 * @deprecated use {@link ProgramIdentifier} instead
95 */
96 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -070097 public static final int PROGRAM_TYPE_DRMO = 6;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -080098 /** SiriusXM Satellite Radio.
99 * @deprecated use {@link ProgramIdentifier} instead
100 */
101 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700102 public static final int PROGRAM_TYPE_SXM = 7;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800103 /** Vendor-specific, not synced across devices.
104 * @deprecated use {@link ProgramIdentifier} instead
105 */
106 @Deprecated
Tomasz Wasilczykd2b5cfb2017-08-04 12:56:47 -0700107 public static final int PROGRAM_TYPE_VENDOR_START = 1000;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800108 /** @deprecated use {@link ProgramIdentifier} instead */
109 @Deprecated
Tomasz Wasilczykd2b5cfb2017-08-04 12:56:47 -0700110 public static final int PROGRAM_TYPE_VENDOR_END = 1999;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800111 /** @deprecated use {@link ProgramIdentifier} instead */
112 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700113 @IntDef(prefix = { "PROGRAM_TYPE_" }, value = {
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800114 PROGRAM_TYPE_INVALID,
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700115 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,
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700122 })
Tomasz Wasilczykd2b5cfb2017-08-04 12:56:47 -0700123 @IntRange(from = PROGRAM_TYPE_VENDOR_START, to = PROGRAM_TYPE_VENDOR_END)
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700124 @Retention(RetentionPolicy.SOURCE)
125 public @interface ProgramType {}
126
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800127 public static final int IDENTIFIER_TYPE_INVALID = 0;
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700128 /** kHz */
129 public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
130 /** 16bit */
131 public static final int IDENTIFIER_TYPE_RDS_PI = 2;
132 /**
133 * 64bit compound primary identifier for HD Radio.
134 *
135 * Consists of (from the LSB):
136 * - 32bit: Station ID number;
137 * - 4bit: HD_SUBCHANNEL;
138 * - 18bit: AMFM_FREQUENCY.
139 * The remaining bits should be set to zeros when writing on the chip side
140 * and ignored when read.
141 */
142 public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3;
143 /**
144 * HD Radio subchannel - a value of range 0-7.
145 *
146 * The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
147 * as opposed to HD Radio standard (where it's 1-based).
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800148 *
149 * @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700150 */
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800151 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700152 public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4;
153 /**
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800154 * 64bit additional identifier for HD Radio.
155 *
156 * Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
157 * globally unique. To provide a best-effort solution, a short version of
158 * station name may be carried as additional identifier and may be used
159 * by the tuner hardware to double-check tuning.
160 *
161 * The name is limited to the first 8 A-Z0-9 characters (lowercase letters
162 * must be converted to uppercase). Encoded in little-endian ASCII:
163 * the first character of the name is the LSB.
164 *
165 * For example: "Abc" is encoded as 0x434241.
166 */
167 public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
168 /**
169 * @see {@link IDENTIFIER_TYPE_DAB_SID_EXT}
170 */
171 public static final int IDENTIFIER_TYPE_DAB_SIDECC = 5;
172 /**
173 * 28bit compound primary identifier for Digital Audio Broadcasting.
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700174 *
175 * Consists of (from the LSB):
176 * - 16bit: SId;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800177 * - 8bit: ECC code;
178 * - 4bit: SCIdS.
179 *
180 * SCIdS (Service Component Identifier within the Service) value
181 * of 0 represents the main service, while 1 and above represents
182 * secondary services.
183 *
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700184 * The remaining bits should be set to zeros when writing on the chip side
185 * and ignored when read.
186 */
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800187 public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC;
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700188 /** 16bit */
189 public static final int IDENTIFIER_TYPE_DAB_ENSEMBLE = 6;
190 /** 12bit */
191 public static final int IDENTIFIER_TYPE_DAB_SCID = 7;
192 /** kHz */
193 public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8;
194 /** 24bit */
195 public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9;
196 /** kHz */
197 public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800198 /**
199 * 1: AM, 2:FM
200 * @deprecated use {@link IDENTIFIER_TYPE_DRMO_FREQUENCY} instead
201 */
202 @Deprecated
Tomasz Wasilczykea0302a2017-07-17 15:40:33 -0700203 public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700204 /** 32bit */
Tomasz Wasilczykea0302a2017-07-17 15:40:33 -0700205 public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700206 /** 0-999 range */
Tomasz Wasilczykea0302a2017-07-17 15:40:33 -0700207 public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700208 /**
209 * Primary identifier for vendor-specific radio technology.
210 * The value format is determined by a vendor.
211 *
Tomasz Wasilczykd2b5cfb2017-08-04 12:56:47 -0700212 * It must not be used in any other programType than corresponding VENDOR
213 * type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must
214 * not be used in any program type other than 1015).
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700215 */
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800216 public static final int IDENTIFIER_TYPE_VENDOR_START = PROGRAM_TYPE_VENDOR_START;
217 /**
218 * @see {@link IDENTIFIER_TYPE_VENDOR_START}
219 */
220 public static final int IDENTIFIER_TYPE_VENDOR_END = PROGRAM_TYPE_VENDOR_END;
221 /**
222 * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_START} instead
223 */
224 @Deprecated
225 public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_START = IDENTIFIER_TYPE_VENDOR_START;
226 /**
227 * @deprecated use {@link IDENTIFIER_TYPE_VENDOR_END} instead
228 */
229 @Deprecated
230 public static final int IDENTIFIER_TYPE_VENDOR_PRIMARY_END = IDENTIFIER_TYPE_VENDOR_END;
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700231 @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = {
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800232 IDENTIFIER_TYPE_INVALID,
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700233 IDENTIFIER_TYPE_AMFM_FREQUENCY,
234 IDENTIFIER_TYPE_RDS_PI,
235 IDENTIFIER_TYPE_HD_STATION_ID_EXT,
236 IDENTIFIER_TYPE_HD_SUBCHANNEL,
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800237 IDENTIFIER_TYPE_HD_STATION_NAME,
238 IDENTIFIER_TYPE_DAB_SID_EXT,
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700239 IDENTIFIER_TYPE_DAB_SIDECC,
240 IDENTIFIER_TYPE_DAB_ENSEMBLE,
241 IDENTIFIER_TYPE_DAB_SCID,
242 IDENTIFIER_TYPE_DAB_FREQUENCY,
243 IDENTIFIER_TYPE_DRMO_SERVICE_ID,
244 IDENTIFIER_TYPE_DRMO_FREQUENCY,
Tomasz Wasilczykea0302a2017-07-17 15:40:33 -0700245 IDENTIFIER_TYPE_DRMO_MODULATION,
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700246 IDENTIFIER_TYPE_SXM_SERVICE_ID,
247 IDENTIFIER_TYPE_SXM_CHANNEL,
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700248 })
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800249 @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END)
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700250 @Retention(RetentionPolicy.SOURCE)
251 public @interface IdentifierType {}
252
253 private final @ProgramType int mProgramType;
254 private final @NonNull Identifier mPrimaryId;
255 private final @NonNull Identifier[] mSecondaryIds;
256 private final @NonNull long[] mVendorIds;
257
258 /**
259 * Constructor for ProgramSelector.
260 *
261 * It's not desired to modify selector objects, so all its fields are initialized at creation.
262 *
263 * Identifier lists must not contain any nulls, but can itself be null to be interpreted
264 * as empty list at object creation.
265 *
266 * @param programType type of a radio technology.
267 * @param primaryId primary program identifier.
268 * @param secondaryIds list of secondary program identifiers.
269 * @param vendorIds list of vendor-specific program identifiers.
270 */
271 public ProgramSelector(@ProgramType int programType, @NonNull Identifier primaryId,
272 @Nullable Identifier[] secondaryIds, @Nullable long[] vendorIds) {
273 if (secondaryIds == null) secondaryIds = new Identifier[0];
274 if (vendorIds == null) vendorIds = new long[0];
275 if (Stream.of(secondaryIds).anyMatch(id -> id == null)) {
276 throw new IllegalArgumentException("secondaryIds list must not contain nulls");
277 }
278 mProgramType = programType;
279 mPrimaryId = Objects.requireNonNull(primaryId);
280 mSecondaryIds = secondaryIds;
281 mVendorIds = vendorIds;
282 }
283
284 /**
285 * Type of a radio technology.
286 *
Jeff Sharkey67f9d502017-08-05 13:49:13 -0600287 * @return program type.
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800288 * @deprecated use {@link getPrimaryId} instead
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700289 */
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800290 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700291 public @ProgramType int getProgramType() {
292 return mProgramType;
293 }
294
295 /**
296 * Primary program identifier uniquely identifies a station and is used to
297 * determine equality between two ProgramSelectors.
298 *
Jeff Sharkey67f9d502017-08-05 13:49:13 -0600299 * @return primary identifier.
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700300 */
301 public @NonNull Identifier getPrimaryId() {
302 return mPrimaryId;
303 }
304
305 /**
306 * Secondary program identifier is not required for tuning, but may make it
307 * faster or more reliable.
308 *
Jeff Sharkey67f9d502017-08-05 13:49:13 -0600309 * @return secondary identifier list, must not be modified.
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700310 */
311 public @NonNull Identifier[] getSecondaryIds() {
312 return mSecondaryIds;
313 }
314
315 /**
316 * Looks up an identifier of a given type (either primary or secondary).
317 *
318 * If there are multiple identifiers if a given type, then first in order (where primary id is
319 * before any secondary) is selected.
320 *
321 * @param type type of identifier.
322 * @return identifier value, if found.
323 * @throws IllegalArgumentException, if not found.
324 */
325 public long getFirstId(@IdentifierType int type) {
326 if (mPrimaryId.getType() == type) return mPrimaryId.getValue();
327 for (Identifier id : mSecondaryIds) {
328 if (id.getType() == type) return id.getValue();
329 }
330 throw new IllegalArgumentException("Identifier " + type + " not found");
331 }
332
333 /**
334 * Looks up all identifier of a given type (either primary or secondary).
335 *
336 * Some identifiers may be provided multiple times, for example
337 * IDENTIFIER_TYPE_AMFM_FREQUENCY for FM Alternate Frequencies.
338 *
339 * @param type type of identifier.
340 * @return a list of identifiers, generated on each call. May be modified.
341 */
342 public @NonNull Identifier[] getAllIds(@IdentifierType int type) {
343 List<Identifier> out = new ArrayList<>();
344
345 if (mPrimaryId.getType() == type) out.add(mPrimaryId);
346 for (Identifier id : mSecondaryIds) {
347 if (id.getType() == type) out.add(id);
348 }
349
350 return out.toArray(new Identifier[out.size()]);
351 }
352
353 /**
354 * Vendor identifiers are passed as-is to the HAL implementation,
355 * preserving elements order.
356 *
Tomasz Wasilczyk436128f2018-01-08 16:46:09 -0800357 * @return an array of vendor identifiers, must not be modified.
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800358 * @deprecated for HAL 1.x compatibility;
359 * HAL 2.x uses standard primary/secondary lists for vendor IDs
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700360 */
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800361 @Deprecated
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700362 public @NonNull long[] getVendorIds() {
363 return mVendorIds;
364 }
365
366 /**
Tomasz Wasilczykeab3e552018-01-11 20:12:10 -0800367 * Creates an equivalent ProgramSelector with a given secondary identifier preferred.
368 *
369 * Used to point to a specific physical identifier for technologies that may broadcast the same
370 * program on different channels. For example, with a DAB program broadcasted over multiple
371 * ensembles, the radio hardware may select the one with the strongest signal. The UI may select
372 * preferred ensemble though, so the radio hardware may try to use it in the first place.
373 *
374 * This is a best-effort hint for the tuner, not a guaranteed behavior.
375 *
376 * Setting the given secondary identifier as preferred means filtering out other secondary
377 * identifiers of its type and adding it to the list.
378 *
379 * @param preferred preferred secondary identifier
380 * @return a new ProgramSelector with a given secondary identifier preferred
381 */
382 public @NonNull ProgramSelector withSecondaryPreferred(@NonNull Identifier preferred) {
383 int preferredType = preferred.getType();
384 Identifier[] secondaryIds = Stream.concat(
385 // remove other identifiers of that type
386 Arrays.stream(mSecondaryIds).filter(id -> id.getType() != preferredType),
387 // add preferred identifier instead
388 Stream.of(preferred)).toArray(Identifier[]::new);
389
390 return new ProgramSelector(
391 mProgramType,
392 mPrimaryId,
393 secondaryIds,
394 mVendorIds
395 );
396 }
397
398 /**
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700399 * Builds new ProgramSelector for AM/FM frequency.
400 *
401 * @param band the band.
402 * @param frequencyKhz the frequency in kHz.
403 * @return new ProgramSelector object representing given frequency.
404 * @throws IllegalArgumentException if provided frequency is out of bounds.
405 */
406 public static @NonNull ProgramSelector createAmFmSelector(
407 @RadioManager.Band int band, int frequencyKhz) {
408 return createAmFmSelector(band, frequencyKhz, 0);
409 }
410
411 /**
412 * Checks, if a given AM/FM frequency is roughly valid and in correct unit.
413 *
Tomasz Wasilczykdc7687a2018-04-11 08:40:31 -0700414 * It does not check the range precisely: it may provide false positives, but not false
415 * negatives. In particular, it may be way off for certain regions.
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700416 * The main purpose is to avoid passing inproper units, ie. MHz instead of kHz.
417 *
418 * @param isAm true, if AM, false if FM.
419 * @param frequencyKhz the frequency in kHz.
420 * @return true, if the frequency is rougly valid.
421 */
422 private static boolean isValidAmFmFrequency(boolean isAm, int frequencyKhz) {
423 if (isAm) {
Tomasz Wasilczykdc7687a2018-04-11 08:40:31 -0700424 return frequencyKhz > 150 && frequencyKhz <= 30000;
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700425 } else {
426 return frequencyKhz > 60000 && frequencyKhz < 110000;
427 }
428 }
429
430 /**
431 * Builds new ProgramSelector for AM/FM frequency.
432 *
433 * This method variant supports HD Radio subchannels, but it's undesirable to
434 * select them manually. Instead, the value should be retrieved from program list.
435 *
436 * @param band the band.
437 * @param frequencyKhz the frequency in kHz.
438 * @param subChannel 1-based HD Radio subchannel.
439 * @return new ProgramSelector object representing given frequency.
440 * @throws IllegalArgumentException if provided frequency is out of bounds,
441 * or tried setting a subchannel for analog AM/FM.
442 */
443 public static @NonNull ProgramSelector createAmFmSelector(
444 @RadioManager.Band int band, int frequencyKhz, int subChannel) {
Tomasz Wasilczyk940c6912018-03-07 14:46:34 -0800445 if (band == RadioManager.BAND_INVALID) {
446 // 50MHz is a rough boundary between AM (<30MHz) and FM (>60MHz).
447 if (frequencyKhz < 50000) {
448 band = (subChannel <= 0) ? RadioManager.BAND_AM : RadioManager.BAND_AM_HD;
449 } else {
450 band = (subChannel <= 0) ? RadioManager.BAND_FM : RadioManager.BAND_FM_HD;
451 }
452 }
453
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700454 boolean isAm = (band == RadioManager.BAND_AM || band == RadioManager.BAND_AM_HD);
455 boolean isDigital = (band == RadioManager.BAND_AM_HD || band == RadioManager.BAND_FM_HD);
456 if (!isAm && !isDigital && band != RadioManager.BAND_FM) {
457 throw new IllegalArgumentException("Unknown band: " + band);
458 }
459 if (subChannel < 0 || subChannel > 8) {
460 throw new IllegalArgumentException("Invalid subchannel: " + subChannel);
461 }
462 if (subChannel > 0 && !isDigital) {
463 throw new IllegalArgumentException("Subchannels are not supported for non-HD radio");
464 }
465 if (!isValidAmFmFrequency(isAm, frequencyKhz)) {
Tomasz Wasilczykdc7687a2018-04-11 08:40:31 -0700466 throw new IllegalArgumentException("Provided value is not a valid AM/FM frequency: "
467 + frequencyKhz);
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700468 }
469
470 // We can't use AM_HD or FM_HD, because we don't know HD station ID.
471 @ProgramType int programType = isAm ? PROGRAM_TYPE_AM : PROGRAM_TYPE_FM;
472 Identifier primary = new Identifier(IDENTIFIER_TYPE_AMFM_FREQUENCY, frequencyKhz);
473
474 Identifier[] secondary = null;
475 if (subChannel > 0) {
476 /* Stating sub channel for non-HD AM/FM does not give any guarantees,
477 * but we can't do much more without HD station ID.
478 *
479 * The legacy APIs had 1-based subChannels, while ProgramSelector is 0-based.
480 */
481 secondary = new Identifier[]{
482 new Identifier(IDENTIFIER_TYPE_HD_SUBCHANNEL, subChannel - 1)};
483 }
484
485 return new ProgramSelector(programType, primary, secondary, null);
486 }
487
488 @Override
489 public String toString() {
490 StringBuilder sb = new StringBuilder("ProgramSelector(type=").append(mProgramType)
491 .append(", primary=").append(mPrimaryId);
492 if (mSecondaryIds.length > 0) sb.append(", secondary=").append(mSecondaryIds);
493 if (mVendorIds.length > 0) sb.append(", vendor=").append(mVendorIds);
494 sb.append(")");
495 return sb.toString();
496 }
497
498 @Override
499 public int hashCode() {
500 // secondaryIds and vendorIds are ignored for equality/hashing
501 return Objects.hash(mProgramType, mPrimaryId);
502 }
503
504 @Override
505 public boolean equals(Object obj) {
506 if (this == obj) return true;
507 if (!(obj instanceof ProgramSelector)) return false;
508 ProgramSelector other = (ProgramSelector) obj;
509 // secondaryIds and vendorIds are ignored for equality/hashing
510 return other.getProgramType() == mProgramType && mPrimaryId.equals(other.getPrimaryId());
511 }
512
513 private ProgramSelector(Parcel in) {
514 mProgramType = in.readInt();
515 mPrimaryId = in.readTypedObject(Identifier.CREATOR);
516 mSecondaryIds = in.createTypedArray(Identifier.CREATOR);
517 if (Stream.of(mSecondaryIds).anyMatch(id -> id == null)) {
518 throw new IllegalArgumentException("secondaryIds list must not contain nulls");
519 }
520 mVendorIds = in.createLongArray();
521 }
522
523 @Override
524 public void writeToParcel(Parcel dest, int flags) {
525 dest.writeInt(mProgramType);
526 dest.writeTypedObject(mPrimaryId, 0);
527 dest.writeTypedArray(mSecondaryIds, 0);
528 dest.writeLongArray(mVendorIds);
529 }
530
531 @Override
532 public int describeContents() {
533 return 0;
534 }
535
536 public static final Parcelable.Creator<ProgramSelector> CREATOR =
537 new Parcelable.Creator<ProgramSelector>() {
538 public ProgramSelector createFromParcel(Parcel in) {
539 return new ProgramSelector(in);
540 }
541
542 public ProgramSelector[] newArray(int size) {
543 return new ProgramSelector[size];
544 }
545 };
546
547 /**
548 * A single program identifier component, eg. frequency or channel ID.
549 *
550 * The long value field holds the value in format described in comments for
551 * IdentifierType constants.
552 */
553 public static final class Identifier implements Parcelable {
554 private final @IdentifierType int mType;
555 private final long mValue;
556
557 public Identifier(@IdentifierType int type, long value) {
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800558 if (type == IDENTIFIER_TYPE_HD_STATION_NAME) {
559 // see getType
560 type = IDENTIFIER_TYPE_HD_SUBCHANNEL;
561 }
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700562 mType = type;
563 mValue = value;
564 }
565
566 /**
567 * Type of an identifier.
568 *
569 * @return type of an identifier.
570 */
571 public @IdentifierType int getType() {
Tomasz Wasilczykfae3d0d2018-01-13 11:24:16 -0800572 if (mType == IDENTIFIER_TYPE_HD_SUBCHANNEL && mValue > 10) {
573 /* HD_SUBCHANNEL and HD_STATION_NAME use the same identifier type, but they differ
574 * in possible values: sub channel is 0-7, station name is greater than ASCII space
575 * code (32).
576 */
577 return IDENTIFIER_TYPE_HD_STATION_NAME;
578 }
Tomasz Wasilczyk8cfb0e82017-07-12 13:59:20 -0700579 return mType;
580 }
581
582 /**
583 * Value of an identifier.
584 *
585 * Its meaning depends on identifier type, ie. for IDENTIFIER_TYPE_AMFM_FREQUENCY type,
586 * the value is a frequency in kHz.
587 *
588 * The range of a value depends on its type; it does not always require the whole long
589 * range. Casting to necessary type (ie. int) without range checking is correct in front-end
590 * code - any range violations are either errors in the framework or in the
591 * HAL implementation. For example, IDENTIFIER_TYPE_AMFM_FREQUENCY always fits in int,
592 * as Integer.MAX_VALUE would mean 2.1THz.
593 *
594 * @return value of an identifier.
595 */
596 public long getValue() {
597 return mValue;
598 }
599
600 @Override
601 public String toString() {
602 return "Identifier(" + mType + ", " + mValue + ")";
603 }
604
605 @Override
606 public int hashCode() {
607 return Objects.hash(mType, mValue);
608 }
609
610 @Override
611 public boolean equals(Object obj) {
612 if (this == obj) return true;
613 if (!(obj instanceof Identifier)) return false;
614 Identifier other = (Identifier) obj;
615 return other.getType() == mType && other.getValue() == mValue;
616 }
617
618 private Identifier(Parcel in) {
619 mType = in.readInt();
620 mValue = in.readLong();
621 }
622
623 @Override
624 public void writeToParcel(Parcel dest, int flags) {
625 dest.writeInt(mType);
626 dest.writeLong(mValue);
627 }
628
629 @Override
630 public int describeContents() {
631 return 0;
632 }
633
634 public static final Parcelable.Creator<Identifier> CREATOR =
635 new Parcelable.Creator<Identifier>() {
636 public Identifier createFromParcel(Parcel in) {
637 return new Identifier(in);
638 }
639
640 public Identifier[] newArray(int size) {
641 return new Identifier[size];
642 }
643 };
644 }
645}