Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 1 | /** |
| 2 | * Copyright (C) 2017 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package android.hardware.radio; |
| 18 | |
| 19 | import android.annotation.IntDef; |
Tomasz Wasilczyk | d2b5cfb | 2017-08-04 12:56:47 -0700 | [diff] [blame] | 20 | import android.annotation.IntRange; |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 21 | import android.annotation.NonNull; |
| 22 | import android.annotation.Nullable; |
| 23 | import android.annotation.SystemApi; |
| 24 | import android.os.Parcel; |
| 25 | import android.os.Parcelable; |
| 26 | |
| 27 | import java.lang.annotation.Retention; |
| 28 | import java.lang.annotation.RetentionPolicy; |
| 29 | import java.util.ArrayList; |
Tomasz Wasilczyk | eab3e55 | 2018-01-11 20:12:10 -0800 | [diff] [blame] | 30 | import java.util.Arrays; |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 31 | import java.util.List; |
| 32 | import java.util.Objects; |
| 33 | import java.util.stream.Stream; |
| 34 | |
| 35 | /** |
| 36 | * A set of identifiers necessary to tune to a given station. |
| 37 | * |
| 38 | * This can hold various identifiers, like |
| 39 | * - AM/FM frequency |
| 40 | * - HD Radio subchannel |
| 41 | * - DAB channel info |
| 42 | * |
| 43 | * The primary ID uniquely identifies a station and can be used for equality |
| 44 | * check. The secondary IDs are supplementary and can speed up tuning process, |
| 45 | * but the primary ID is sufficient (ie. after a full band scan). |
| 46 | * |
| 47 | * Two selectors with different secondary IDs, but the same primary ID are |
| 48 | * considered equal. In particular, secondary IDs vector may get updated for |
| 49 | * an entry on the program list (ie. when a better frequency for a given |
| 50 | * station is found). |
| 51 | * |
| 52 | * The primaryId of a given programType MUST be of a specific type: |
| 53 | * - AM, FM: RDS_PI if the station broadcasts RDS, AMFM_FREQUENCY otherwise; |
| 54 | * - AM_HD, FM_HD: HD_STATION_ID_EXT; |
| 55 | * - DAB: DAB_SIDECC; |
| 56 | * - DRMO: DRMO_SERVICE_ID; |
| 57 | * - SXM: SXM_SERVICE_ID; |
| 58 | * - VENDOR: VENDOR_PRIMARY. |
| 59 | * @hide |
| 60 | */ |
| 61 | @SystemApi |
| 62 | public final class ProgramSelector implements Parcelable { |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 63 | /** Invalid program type. |
| 64 | * @deprecated use {@link ProgramIdentifier} instead |
| 65 | */ |
| 66 | @Deprecated |
Tomasz Wasilczyk | 436128f | 2018-01-08 16:46:09 -0800 | [diff] [blame] | 67 | public static final int PROGRAM_TYPE_INVALID = 0; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 68 | /** Analogue AM radio (with or without RDS). |
| 69 | * @deprecated use {@link ProgramIdentifier} instead |
| 70 | */ |
| 71 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 72 | public static final int PROGRAM_TYPE_AM = 1; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 73 | /** analogue FM radio (with or without RDS). |
| 74 | * @deprecated use {@link ProgramIdentifier} instead |
| 75 | */ |
| 76 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 77 | public static final int PROGRAM_TYPE_FM = 2; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 78 | /** AM HD Radio. |
| 79 | * @deprecated use {@link ProgramIdentifier} instead |
| 80 | */ |
| 81 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 82 | public static final int PROGRAM_TYPE_AM_HD = 3; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 83 | /** FM HD Radio. |
| 84 | * @deprecated use {@link ProgramIdentifier} instead |
| 85 | */ |
| 86 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 87 | public static final int PROGRAM_TYPE_FM_HD = 4; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 88 | /** Digital audio broadcasting. |
| 89 | * @deprecated use {@link ProgramIdentifier} instead |
| 90 | */ |
| 91 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 92 | public static final int PROGRAM_TYPE_DAB = 5; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 93 | /** Digital Radio Mondiale. |
| 94 | * @deprecated use {@link ProgramIdentifier} instead |
| 95 | */ |
| 96 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 97 | public static final int PROGRAM_TYPE_DRMO = 6; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 98 | /** SiriusXM Satellite Radio. |
| 99 | * @deprecated use {@link ProgramIdentifier} instead |
| 100 | */ |
| 101 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 102 | public static final int PROGRAM_TYPE_SXM = 7; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 103 | /** Vendor-specific, not synced across devices. |
| 104 | * @deprecated use {@link ProgramIdentifier} instead |
| 105 | */ |
| 106 | @Deprecated |
Tomasz Wasilczyk | d2b5cfb | 2017-08-04 12:56:47 -0700 | [diff] [blame] | 107 | public static final int PROGRAM_TYPE_VENDOR_START = 1000; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 108 | /** @deprecated use {@link ProgramIdentifier} instead */ |
| 109 | @Deprecated |
Tomasz Wasilczyk | d2b5cfb | 2017-08-04 12:56:47 -0700 | [diff] [blame] | 110 | public static final int PROGRAM_TYPE_VENDOR_END = 1999; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 111 | /** @deprecated use {@link ProgramIdentifier} instead */ |
| 112 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 113 | @IntDef(prefix = { "PROGRAM_TYPE_" }, value = { |
Tomasz Wasilczyk | 436128f | 2018-01-08 16:46:09 -0800 | [diff] [blame] | 114 | PROGRAM_TYPE_INVALID, |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 115 | PROGRAM_TYPE_AM, |
| 116 | PROGRAM_TYPE_FM, |
| 117 | PROGRAM_TYPE_AM_HD, |
| 118 | PROGRAM_TYPE_FM_HD, |
| 119 | PROGRAM_TYPE_DAB, |
| 120 | PROGRAM_TYPE_DRMO, |
| 121 | PROGRAM_TYPE_SXM, |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 122 | }) |
Tomasz Wasilczyk | d2b5cfb | 2017-08-04 12:56:47 -0700 | [diff] [blame] | 123 | @IntRange(from = PROGRAM_TYPE_VENDOR_START, to = PROGRAM_TYPE_VENDOR_END) |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 124 | @Retention(RetentionPolicy.SOURCE) |
| 125 | public @interface ProgramType {} |
| 126 | |
Tomasz Wasilczyk | 436128f | 2018-01-08 16:46:09 -0800 | [diff] [blame] | 127 | public static final int IDENTIFIER_TYPE_INVALID = 0; |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 128 | /** 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 Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 148 | * |
| 149 | * @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 150 | */ |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 151 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 152 | public static final int IDENTIFIER_TYPE_HD_SUBCHANNEL = 4; |
| 153 | /** |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 154 | * 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 Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 174 | * |
| 175 | * Consists of (from the LSB): |
| 176 | * - 16bit: SId; |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 177 | * - 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 Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 184 | * The remaining bits should be set to zeros when writing on the chip side |
| 185 | * and ignored when read. |
| 186 | */ |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 187 | public static final int IDENTIFIER_TYPE_DAB_SID_EXT = IDENTIFIER_TYPE_DAB_SIDECC; |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 188 | /** 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 Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 198 | /** |
| 199 | * 1: AM, 2:FM |
| 200 | * @deprecated use {@link IDENTIFIER_TYPE_DRMO_FREQUENCY} instead |
| 201 | */ |
| 202 | @Deprecated |
Tomasz Wasilczyk | ea0302a | 2017-07-17 15:40:33 -0700 | [diff] [blame] | 203 | public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11; |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 204 | /** 32bit */ |
Tomasz Wasilczyk | ea0302a | 2017-07-17 15:40:33 -0700 | [diff] [blame] | 205 | public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12; |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 206 | /** 0-999 range */ |
Tomasz Wasilczyk | ea0302a | 2017-07-17 15:40:33 -0700 | [diff] [blame] | 207 | public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13; |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 208 | /** |
| 209 | * Primary identifier for vendor-specific radio technology. |
| 210 | * The value format is determined by a vendor. |
| 211 | * |
Tomasz Wasilczyk | d2b5cfb | 2017-08-04 12:56:47 -0700 | [diff] [blame] | 212 | * 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 Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 215 | */ |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 216 | 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 Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 231 | @IntDef(prefix = { "IDENTIFIER_TYPE_" }, value = { |
Tomasz Wasilczyk | 436128f | 2018-01-08 16:46:09 -0800 | [diff] [blame] | 232 | IDENTIFIER_TYPE_INVALID, |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 233 | IDENTIFIER_TYPE_AMFM_FREQUENCY, |
| 234 | IDENTIFIER_TYPE_RDS_PI, |
| 235 | IDENTIFIER_TYPE_HD_STATION_ID_EXT, |
| 236 | IDENTIFIER_TYPE_HD_SUBCHANNEL, |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 237 | IDENTIFIER_TYPE_HD_STATION_NAME, |
| 238 | IDENTIFIER_TYPE_DAB_SID_EXT, |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 239 | 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 Wasilczyk | ea0302a | 2017-07-17 15:40:33 -0700 | [diff] [blame] | 245 | IDENTIFIER_TYPE_DRMO_MODULATION, |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 246 | IDENTIFIER_TYPE_SXM_SERVICE_ID, |
| 247 | IDENTIFIER_TYPE_SXM_CHANNEL, |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 248 | }) |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 249 | @IntRange(from = IDENTIFIER_TYPE_VENDOR_START, to = IDENTIFIER_TYPE_VENDOR_END) |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 250 | @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 Sharkey | 67f9d50 | 2017-08-05 13:49:13 -0600 | [diff] [blame] | 287 | * @return program type. |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 288 | * @deprecated use {@link getPrimaryId} instead |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 289 | */ |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 290 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 291 | 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 Sharkey | 67f9d50 | 2017-08-05 13:49:13 -0600 | [diff] [blame] | 299 | * @return primary identifier. |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 300 | */ |
| 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 Sharkey | 67f9d50 | 2017-08-05 13:49:13 -0600 | [diff] [blame] | 309 | * @return secondary identifier list, must not be modified. |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 310 | */ |
| 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 Wasilczyk | 436128f | 2018-01-08 16:46:09 -0800 | [diff] [blame] | 357 | * @return an array of vendor identifiers, must not be modified. |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 358 | * @deprecated for HAL 1.x compatibility; |
| 359 | * HAL 2.x uses standard primary/secondary lists for vendor IDs |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 360 | */ |
Tomasz Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 361 | @Deprecated |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 362 | public @NonNull long[] getVendorIds() { |
| 363 | return mVendorIds; |
| 364 | } |
| 365 | |
| 366 | /** |
Tomasz Wasilczyk | eab3e55 | 2018-01-11 20:12:10 -0800 | [diff] [blame] | 367 | * 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 Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 399 | * 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 Wasilczyk | dc7687a | 2018-04-11 08:40:31 -0700 | [diff] [blame] | 414 | * 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 Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 416 | * 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 Wasilczyk | dc7687a | 2018-04-11 08:40:31 -0700 | [diff] [blame] | 424 | return frequencyKhz > 150 && frequencyKhz <= 30000; |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 425 | } 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 Wasilczyk | 940c691 | 2018-03-07 14:46:34 -0800 | [diff] [blame] | 445 | 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 Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 454 | 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 Wasilczyk | dc7687a | 2018-04-11 08:40:31 -0700 | [diff] [blame] | 466 | throw new IllegalArgumentException("Provided value is not a valid AM/FM frequency: " |
| 467 | + frequencyKhz); |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 468 | } |
| 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 Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 558 | if (type == IDENTIFIER_TYPE_HD_STATION_NAME) { |
| 559 | // see getType |
| 560 | type = IDENTIFIER_TYPE_HD_SUBCHANNEL; |
| 561 | } |
Tomasz Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 562 | 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 Wasilczyk | fae3d0d | 2018-01-13 11:24:16 -0800 | [diff] [blame] | 572 | 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 Wasilczyk | 8cfb0e8 | 2017-07-12 13:59:20 -0700 | [diff] [blame] | 579 | 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 | } |