1 /** 2 * Copyright (C) 2015 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.Manifest; 20 import android.annotation.CallbackExecutor; 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresFeature; 25 import android.annotation.RequiresPermission; 26 import android.annotation.SystemApi; 27 import android.annotation.SystemService; 28 import android.content.Context; 29 import android.content.pm.PackageManager; 30 import android.os.Handler; 31 import android.os.Parcel; 32 import android.os.Parcelable; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.os.ServiceManager.ServiceNotFoundException; 36 import android.text.TextUtils; 37 import android.util.Log; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.util.Preconditions; 41 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 import java.util.Arrays; 45 import java.util.Collection; 46 import java.util.Collections; 47 import java.util.HashMap; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.Set; 52 import java.util.concurrent.Executor; 53 import java.util.stream.Collectors; 54 55 /** 56 * The RadioManager class allows to control a broadcast radio tuner present on the device. 57 * It provides data structures and methods to query for available radio modules, list their 58 * properties and open an interface to control tuning operations and receive callbacks when 59 * asynchronous operations complete or events occur. 60 * @hide 61 */ 62 @SystemApi 63 @SystemService(Context.RADIO_SERVICE) 64 @RequiresFeature(PackageManager.FEATURE_BROADCAST_RADIO) 65 public class RadioManager { 66 private static final String TAG = "BroadcastRadio.manager"; 67 68 /** Method return status: successful operation */ 69 public static final int STATUS_OK = 0; 70 /** Method return status: unspecified error */ 71 public static final int STATUS_ERROR = Integer.MIN_VALUE; 72 /** Method return status: permission denied */ 73 public static final int STATUS_PERMISSION_DENIED = -1; 74 /** Method return status: initialization failure */ 75 public static final int STATUS_NO_INIT = -19; 76 /** Method return status: invalid argument provided */ 77 public static final int STATUS_BAD_VALUE = -22; 78 /** Method return status: cannot reach service */ 79 public static final int STATUS_DEAD_OBJECT = -32; 80 /** Method return status: invalid or out of sequence operation */ 81 public static final int STATUS_INVALID_OPERATION = -38; 82 /** Method return status: time out before operation completion */ 83 public static final int STATUS_TIMED_OUT = -110; 84 85 /** 86 * Radio operation status types 87 * 88 * @hide 89 */ 90 @IntDef(prefix = { "STATUS_" }, value = { 91 STATUS_OK, 92 STATUS_ERROR, 93 STATUS_PERMISSION_DENIED, 94 STATUS_NO_INIT, 95 STATUS_BAD_VALUE, 96 STATUS_DEAD_OBJECT, 97 STATUS_INVALID_OPERATION, 98 STATUS_TIMED_OUT, 99 }) 100 @Retention(RetentionPolicy.SOURCE) 101 public @interface RadioStatusType{} 102 103 104 // keep in sync with radio_class_t in /system/core/incluse/system/radio.h 105 /** Radio module class supporting FM (including HD radio) and AM */ 106 public static final int CLASS_AM_FM = 0; 107 /** Radio module class supporting satellite radio */ 108 public static final int CLASS_SAT = 1; 109 /** Radio module class supporting Digital terrestrial radio */ 110 public static final int CLASS_DT = 2; 111 112 public static final int BAND_INVALID = -1; 113 /** AM radio band (LW/MW/SW). 114 * @see BandDescriptor */ 115 public static final int BAND_AM = 0; 116 /** FM radio band. 117 * @see BandDescriptor */ 118 public static final int BAND_FM = 1; 119 /** FM HD radio or DRM band. 120 * @see BandDescriptor */ 121 public static final int BAND_FM_HD = 2; 122 /** AM HD radio or DRM band. 123 * @see BandDescriptor */ 124 public static final int BAND_AM_HD = 3; 125 @IntDef(prefix = { "BAND_" }, value = { 126 BAND_INVALID, 127 BAND_AM, 128 BAND_FM, 129 BAND_AM_HD, 130 BAND_FM_HD, 131 }) 132 @Retention(RetentionPolicy.SOURCE) 133 public @interface Band {} 134 135 // keep in sync with radio_region_t in /system/core/incluse/system/radio.h 136 /** Africa, Europe. 137 * @see BandDescriptor */ 138 public static final int REGION_ITU_1 = 0; 139 /** Americas. 140 * @see BandDescriptor */ 141 public static final int REGION_ITU_2 = 1; 142 /** Russia. 143 * @see BandDescriptor */ 144 public static final int REGION_OIRT = 2; 145 /** Japan. 146 * @see BandDescriptor */ 147 public static final int REGION_JAPAN = 3; 148 /** Korea. 149 * @see BandDescriptor */ 150 public static final int REGION_KOREA = 4; 151 152 /** 153 * Forces mono audio stream reception. 154 * 155 * Analog broadcasts can recover poor reception conditions by jointing 156 * stereo channels into one. Mainly for, but not limited to AM/FM. 157 */ 158 public static final int CONFIG_FORCE_MONO = 1; 159 /** 160 * Forces the analog playback for the supporting radio technology. 161 * 162 * User may disable digital playback for FM HD Radio or hybrid FM/DAB with 163 * this option. This is purely user choice, ie. does not reflect digital- 164 * analog handover state managed from the HAL implementation side. 165 * 166 * Some radio technologies may not support this, ie. DAB. 167 */ 168 public static final int CONFIG_FORCE_ANALOG = 2; 169 /** 170 * Forces the digital playback for the supporting radio technology. 171 * 172 * User may disable digital-analog handover that happens with poor 173 * reception conditions. With digital forced, the radio will remain silent 174 * instead of switching to analog channel if it's available. This is purely 175 * user choice, it does not reflect the actual state of handover. 176 */ 177 public static final int CONFIG_FORCE_DIGITAL = 3; 178 /** 179 * RDS Alternative Frequencies. 180 * 181 * If set and the currently tuned RDS station broadcasts on multiple 182 * channels, radio tuner automatically switches to the best available 183 * alternative. 184 */ 185 public static final int CONFIG_RDS_AF = 4; 186 /** 187 * RDS region-specific program lock-down. 188 * 189 * Allows user to lock to the current region as they move into the 190 * other region. 191 */ 192 public static final int CONFIG_RDS_REG = 5; 193 /** Enables DAB-DAB hard- and implicit-linking (the same content). */ 194 public static final int CONFIG_DAB_DAB_LINKING = 6; 195 /** Enables DAB-FM hard- and implicit-linking (the same content). */ 196 public static final int CONFIG_DAB_FM_LINKING = 7; 197 /** Enables DAB-DAB soft-linking (related content). */ 198 public static final int CONFIG_DAB_DAB_SOFT_LINKING = 8; 199 /** Enables DAB-FM soft-linking (related content). */ 200 public static final int CONFIG_DAB_FM_SOFT_LINKING = 9; 201 202 /** @hide */ 203 @IntDef(prefix = { "CONFIG_" }, value = { 204 CONFIG_FORCE_MONO, 205 CONFIG_FORCE_ANALOG, 206 CONFIG_FORCE_DIGITAL, 207 CONFIG_RDS_AF, 208 CONFIG_RDS_REG, 209 CONFIG_DAB_DAB_LINKING, 210 CONFIG_DAB_FM_LINKING, 211 CONFIG_DAB_DAB_SOFT_LINKING, 212 CONFIG_DAB_FM_SOFT_LINKING, 213 }) 214 @Retention(RetentionPolicy.SOURCE) 215 public @interface ConfigFlag {} 216 217 /***************************************************************************** 218 * Lists properties, options and radio bands supported by a given broadcast radio module. 219 * Each module has a unique ID used to address it when calling RadioManager APIs. 220 * Module properties are returned by {@link #listModules(List <ModuleProperties>)} method. 221 ****************************************************************************/ 222 public static class ModuleProperties implements Parcelable { 223 224 private final int mId; 225 @NonNull private final String mServiceName; 226 private final int mClassId; 227 private final String mImplementor; 228 private final String mProduct; 229 private final String mVersion; 230 private final String mSerial; 231 private final int mNumTuners; 232 private final int mNumAudioSources; 233 private final boolean mIsInitializationRequired; 234 private final boolean mIsCaptureSupported; 235 private final BandDescriptor[] mBands; 236 private final boolean mIsBgScanSupported; 237 private final Set<Integer> mSupportedProgramTypes; 238 private final Set<Integer> mSupportedIdentifierTypes; 239 @Nullable private final Map<String, Integer> mDabFrequencyTable; 240 @NonNull private final Map<String, String> mVendorInfo; 241 242 /** @hide */ ModuleProperties(int id, String serviceName, int classId, String implementor, String product, String version, String serial, int numTuners, int numAudioSources, boolean isInitializationRequired, boolean isCaptureSupported, BandDescriptor[] bands, boolean isBgScanSupported, @ProgramSelector.ProgramType int[] supportedProgramTypes, @ProgramSelector.IdentifierType int[] supportedIdentifierTypes, @Nullable Map<String, Integer> dabFrequencyTable, Map<String, String> vendorInfo)243 public ModuleProperties(int id, String serviceName, int classId, String implementor, 244 String product, String version, String serial, int numTuners, int numAudioSources, 245 boolean isInitializationRequired, boolean isCaptureSupported, 246 BandDescriptor[] bands, boolean isBgScanSupported, 247 @ProgramSelector.ProgramType int[] supportedProgramTypes, 248 @ProgramSelector.IdentifierType int[] supportedIdentifierTypes, 249 @Nullable Map<String, Integer> dabFrequencyTable, 250 Map<String, String> vendorInfo) { 251 mId = id; 252 mServiceName = TextUtils.isEmpty(serviceName) ? "default" : serviceName; 253 mClassId = classId; 254 mImplementor = implementor; 255 mProduct = product; 256 mVersion = version; 257 mSerial = serial; 258 mNumTuners = numTuners; 259 mNumAudioSources = numAudioSources; 260 mIsInitializationRequired = isInitializationRequired; 261 mIsCaptureSupported = isCaptureSupported; 262 mBands = bands; 263 mIsBgScanSupported = isBgScanSupported; 264 mSupportedProgramTypes = arrayToSet(supportedProgramTypes); 265 mSupportedIdentifierTypes = arrayToSet(supportedIdentifierTypes); 266 if (dabFrequencyTable != null) { 267 for (Map.Entry<String, Integer> entry : dabFrequencyTable.entrySet()) { 268 Objects.requireNonNull(entry.getKey()); 269 Objects.requireNonNull(entry.getValue()); 270 } 271 } 272 mDabFrequencyTable = (dabFrequencyTable == null || dabFrequencyTable.isEmpty()) 273 ? null : dabFrequencyTable; 274 mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo; 275 } 276 arrayToSet(int[] arr)277 private static Set<Integer> arrayToSet(int[] arr) { 278 return Arrays.stream(arr).boxed().collect(Collectors.toSet()); 279 } 280 setToArray(Set<Integer> set)281 private static int[] setToArray(Set<Integer> set) { 282 return set.stream().mapToInt(Integer::intValue).toArray(); 283 } 284 285 /** Unique module identifier provided by the native service. 286 * For use with {@link #openTuner(int, BandConfig, boolean, Callback, Handler)}. 287 * @return the radio module unique identifier. 288 */ getId()289 public int getId() { 290 return mId; 291 } 292 293 /** 294 * Module service (driver) name as registered with HIDL. 295 * @return the module service name. 296 */ getServiceName()297 public @NonNull String getServiceName() { 298 return mServiceName; 299 } 300 301 /** Module class identifier: {@link #CLASS_AM_FM}, {@link #CLASS_SAT}, {@link #CLASS_DT} 302 * @return the radio module class identifier. 303 */ getClassId()304 public int getClassId() { 305 return mClassId; 306 } 307 308 /** Human readable broadcast radio module implementor 309 * @return the name of the radio module implementator. 310 */ getImplementor()311 public String getImplementor() { 312 return mImplementor; 313 } 314 315 /** Human readable broadcast radio module product name 316 * @return the radio module product name. 317 */ getProduct()318 public String getProduct() { 319 return mProduct; 320 } 321 322 /** Human readable broadcast radio module version number 323 * @return the radio module version. 324 */ getVersion()325 public String getVersion() { 326 return mVersion; 327 } 328 329 /** Radio module serial number. 330 * Can be used for subscription services. 331 * @return the radio module serial number. 332 */ getSerial()333 public String getSerial() { 334 return mSerial; 335 } 336 337 /** Number of tuners available. 338 * This is the number of tuners that can be open simultaneously. 339 * @return the number of tuners supported. 340 */ getNumTuners()341 public int getNumTuners() { 342 return mNumTuners; 343 } 344 345 /** Number tuner audio sources available. Must be less or equal to getNumTuners(). 346 * When more than one tuner is supported, one is usually for playback and has one 347 * associated audio source and the other is for pre scanning and building a 348 * program list. 349 * @return the number of audio sources available. 350 */ 351 @RadioStatusType getNumAudioSources()352 public int getNumAudioSources() { 353 return mNumAudioSources; 354 } 355 356 /** 357 * Checks, if BandConfig initialization (after {@link RadioManager#openTuner}) 358 * is required to be done before other operations or not. 359 * 360 * If it is, the client has to wait for {@link RadioTuner.Callback#onConfigurationChanged} 361 * callback before executing any other operations. Otherwise, such operation will fail 362 * returning {@link RadioManager#STATUS_INVALID_OPERATION} error code. 363 */ isInitializationRequired()364 public boolean isInitializationRequired() { 365 return mIsInitializationRequired; 366 } 367 368 /** {@code true} if audio capture is possible from radio tuner output. 369 * This indicates if routing to audio devices not connected to the same HAL as the FM radio 370 * is possible (e.g. to USB) or DAR (Digital Audio Recorder) feature can be implemented. 371 * @return {@code true} if audio capture is possible, {@code false} otherwise. 372 */ isCaptureSupported()373 public boolean isCaptureSupported() { 374 return mIsCaptureSupported; 375 } 376 377 /** 378 * {@code true} if the module supports background scanning. At the given time it may not 379 * be available though, see {@link RadioTuner#startBackgroundScan()}. 380 * 381 * @return {@code true} if background scanning is supported (not necessary available 382 * at a given time), {@code false} otherwise. 383 */ isBackgroundScanningSupported()384 public boolean isBackgroundScanningSupported() { 385 return mIsBgScanSupported; 386 } 387 388 /** 389 * Checks, if a given program type is supported by this tuner. 390 * 391 * If a program type is supported by radio module, it means it can tune 392 * to ProgramSelector of a given type. 393 * 394 * @return {@code true} if a given program type is supported. 395 */ isProgramTypeSupported(@rogramSelector.ProgramType int type)396 public boolean isProgramTypeSupported(@ProgramSelector.ProgramType int type) { 397 return mSupportedProgramTypes.contains(type); 398 } 399 400 /** 401 * Checks, if a given program identifier is supported by this tuner. 402 * 403 * If an identifier is supported by radio module, it means it can use it for 404 * tuning to ProgramSelector with either primary or secondary Identifier of 405 * a given type. 406 * 407 * @return {@code true} if a given program type is supported. 408 */ isProgramIdentifierSupported(@rogramSelector.IdentifierType int type)409 public boolean isProgramIdentifierSupported(@ProgramSelector.IdentifierType int type) { 410 return mSupportedIdentifierTypes.contains(type); 411 } 412 413 /** 414 * A frequency table for Digital Audio Broadcasting (DAB). 415 * 416 * The key is a channel name, i.e. 5A, 7B. 417 * 418 * The value is a frequency, in kHz. 419 * 420 * @return a frequency table, or {@code null} if the module doesn't support DAB 421 */ getDabFrequencyTable()422 public @Nullable Map<String, Integer> getDabFrequencyTable() { 423 return mDabFrequencyTable; 424 } 425 426 /** 427 * A map of vendor-specific opaque strings, passed from HAL without changes. 428 * Format of these strings can vary across vendors. 429 * 430 * It may be used for extra features, that's not supported by a platform, 431 * for example: preset-slots=6; ultra-hd-capable=false. 432 * 433 * Keys must be prefixed with unique vendor Java-style namespace, 434 * eg. 'com.somecompany.parameter1'. 435 */ getVendorInfo()436 public @NonNull Map<String, String> getVendorInfo() { 437 return mVendorInfo; 438 } 439 440 /** List of descriptors for all bands supported by this module. 441 * @return an array of {@link BandDescriptor}. 442 */ getBands()443 public BandDescriptor[] getBands() { 444 return mBands; 445 } 446 ModuleProperties(Parcel in)447 private ModuleProperties(Parcel in) { 448 mId = in.readInt(); 449 String serviceName = in.readString(); 450 mServiceName = TextUtils.isEmpty(serviceName) ? "default" : serviceName; 451 mClassId = in.readInt(); 452 mImplementor = in.readString(); 453 mProduct = in.readString(); 454 mVersion = in.readString(); 455 mSerial = in.readString(); 456 mNumTuners = in.readInt(); 457 mNumAudioSources = in.readInt(); 458 mIsInitializationRequired = in.readInt() == 1; 459 mIsCaptureSupported = in.readInt() == 1; 460 Parcelable[] tmp = in.readParcelableArray(BandDescriptor.class.getClassLoader(), 461 BandDescriptor.class); 462 mBands = new BandDescriptor[tmp.length]; 463 for (int i = 0; i < tmp.length; i++) { 464 mBands[i] = (BandDescriptor) tmp[i]; 465 } 466 mIsBgScanSupported = in.readInt() == 1; 467 mSupportedProgramTypes = arrayToSet(in.createIntArray()); 468 mSupportedIdentifierTypes = arrayToSet(in.createIntArray()); 469 Map<String, Integer> dabFrequencyTableIn = Utils.readStringIntMap(in); 470 mDabFrequencyTable = (dabFrequencyTableIn.isEmpty()) ? null : dabFrequencyTableIn; 471 mVendorInfo = Utils.readStringMap(in); 472 } 473 474 public static final @android.annotation.NonNull Parcelable.Creator<ModuleProperties> CREATOR 475 = new Parcelable.Creator<ModuleProperties>() { 476 public ModuleProperties createFromParcel(Parcel in) { 477 return new ModuleProperties(in); 478 } 479 480 public ModuleProperties[] newArray(int size) { 481 return new ModuleProperties[size]; 482 } 483 }; 484 485 @Override writeToParcel(Parcel dest, int flags)486 public void writeToParcel(Parcel dest, int flags) { 487 dest.writeInt(mId); 488 dest.writeString(mServiceName); 489 dest.writeInt(mClassId); 490 dest.writeString(mImplementor); 491 dest.writeString(mProduct); 492 dest.writeString(mVersion); 493 dest.writeString(mSerial); 494 dest.writeInt(mNumTuners); 495 dest.writeInt(mNumAudioSources); 496 dest.writeInt(mIsInitializationRequired ? 1 : 0); 497 dest.writeInt(mIsCaptureSupported ? 1 : 0); 498 dest.writeParcelableArray(mBands, flags); 499 dest.writeInt(mIsBgScanSupported ? 1 : 0); 500 dest.writeIntArray(setToArray(mSupportedProgramTypes)); 501 dest.writeIntArray(setToArray(mSupportedIdentifierTypes)); 502 Utils.writeStringIntMap(dest, mDabFrequencyTable); 503 Utils.writeStringMap(dest, mVendorInfo); 504 } 505 506 @Override describeContents()507 public int describeContents() { 508 return 0; 509 } 510 511 @NonNull 512 @Override toString()513 public String toString() { 514 return "ModuleProperties [mId=" + mId 515 + ", mServiceName=" + mServiceName + ", mClassId=" + mClassId 516 + ", mImplementor=" + mImplementor + ", mProduct=" + mProduct 517 + ", mVersion=" + mVersion + ", mSerial=" + mSerial 518 + ", mNumTuners=" + mNumTuners 519 + ", mNumAudioSources=" + mNumAudioSources 520 + ", mIsInitializationRequired=" + mIsInitializationRequired 521 + ", mIsCaptureSupported=" + mIsCaptureSupported 522 + ", mIsBgScanSupported=" + mIsBgScanSupported 523 + ", mBands=" + Arrays.toString(mBands) + "]"; 524 } 525 526 @Override hashCode()527 public int hashCode() { 528 return Objects.hash(mId, mServiceName, mClassId, mImplementor, mProduct, mVersion, 529 mSerial, mNumTuners, mNumAudioSources, mIsInitializationRequired, 530 mIsCaptureSupported, Arrays.hashCode(mBands), mIsBgScanSupported, 531 mDabFrequencyTable, mVendorInfo); 532 } 533 534 @Override equals(@ullable Object obj)535 public boolean equals(@Nullable Object obj) { 536 if (this == obj) return true; 537 if (!(obj instanceof ModuleProperties)) return false; 538 ModuleProperties other = (ModuleProperties) obj; 539 540 if (mId != other.getId()) return false; 541 if (!TextUtils.equals(mServiceName, other.mServiceName)) return false; 542 if (mClassId != other.mClassId) return false; 543 if (!Objects.equals(mImplementor, other.mImplementor)) return false; 544 if (!Objects.equals(mProduct, other.mProduct)) return false; 545 if (!Objects.equals(mVersion, other.mVersion)) return false; 546 if (!Objects.equals(mSerial, other.mSerial)) return false; 547 if (mNumTuners != other.mNumTuners) return false; 548 if (mNumAudioSources != other.mNumAudioSources) return false; 549 if (mIsInitializationRequired != other.mIsInitializationRequired) return false; 550 if (mIsCaptureSupported != other.mIsCaptureSupported) return false; 551 if (!Arrays.equals(mBands, other.mBands)) return false; 552 if (mIsBgScanSupported != other.mIsBgScanSupported) return false; 553 if (!Objects.equals(mDabFrequencyTable, other.mDabFrequencyTable)) return false; 554 if (!Objects.equals(mVendorInfo, other.mVendorInfo)) return false; 555 return true; 556 } 557 } 558 559 /** Radio band descriptor: an element in ModuleProperties bands array. 560 * It is either an instance of {@link FmBandDescriptor} or {@link AmBandDescriptor} */ 561 public static class BandDescriptor implements Parcelable { 562 563 private final int mRegion; 564 private final int mType; 565 private final int mLowerLimit; 566 private final int mUpperLimit; 567 private final int mSpacing; 568 BandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing)569 BandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing) { 570 if (type != BAND_AM && type != BAND_FM && type != BAND_FM_HD && type != BAND_AM_HD) { 571 throw new IllegalArgumentException("Unsupported band: " + type); 572 } 573 mRegion = region; 574 mType = type; 575 mLowerLimit = lowerLimit; 576 mUpperLimit = upperLimit; 577 mSpacing = spacing; 578 } 579 580 /** Region this band applies to. E.g. {@link #REGION_ITU_1} 581 * @return the region this band is associated to. 582 */ getRegion()583 public int getRegion() { 584 return mRegion; 585 } 586 /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to: 587 * <ul> 588 * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li> 589 * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li> 590 * </ul> 591 * @return the band type. 592 */ getType()593 public int getType() { 594 return mType; 595 } 596 597 /** 598 * Checks if the band is either AM or AM_HD. 599 * 600 * @return {@code true}, if band is AM or AM_HD. 601 */ isAmBand()602 public boolean isAmBand() { 603 return mType == BAND_AM || mType == BAND_AM_HD; 604 } 605 606 /** 607 * Checks if the band is either FM or FM_HD. 608 * 609 * @return {@code true}, if band is FM or FM_HD. 610 */ isFmBand()611 public boolean isFmBand() { 612 return mType == BAND_FM || mType == BAND_FM_HD; 613 } 614 615 /** Lower band limit expressed in units according to band type. 616 * Currently all defined band types express channels as frequency in kHz 617 * @return the lower band limit. 618 */ getLowerLimit()619 public int getLowerLimit() { 620 return mLowerLimit; 621 } 622 /** Upper band limit expressed in units according to band type. 623 * Currently all defined band types express channels as frequency in kHz 624 * @return the upper band limit. 625 */ getUpperLimit()626 public int getUpperLimit() { 627 return mUpperLimit; 628 } 629 /** Channel spacing in units according to band type. 630 * Currently all defined band types express channels as frequency in kHz 631 * @return the channel spacing. 632 */ getSpacing()633 public int getSpacing() { 634 return mSpacing; 635 } 636 BandDescriptor(Parcel in)637 private BandDescriptor(Parcel in) { 638 mRegion = in.readInt(); 639 mType = in.readInt(); 640 mLowerLimit = in.readInt(); 641 mUpperLimit = in.readInt(); 642 mSpacing = in.readInt(); 643 } 644 lookupTypeFromParcel(Parcel in)645 private static int lookupTypeFromParcel(Parcel in) { 646 int pos = in.dataPosition(); 647 in.readInt(); // skip region 648 int type = in.readInt(); 649 in.setDataPosition(pos); 650 return type; 651 } 652 653 public static final @android.annotation.NonNull Parcelable.Creator<BandDescriptor> CREATOR 654 = new Parcelable.Creator<BandDescriptor>() { 655 public BandDescriptor createFromParcel(Parcel in) { 656 int type = lookupTypeFromParcel(in); 657 switch (type) { 658 case BAND_FM: 659 case BAND_FM_HD: 660 return new FmBandDescriptor(in); 661 case BAND_AM: 662 case BAND_AM_HD: 663 return new AmBandDescriptor(in); 664 default: 665 throw new IllegalArgumentException("Unsupported band: " + type); 666 } 667 } 668 669 public BandDescriptor[] newArray(int size) { 670 return new BandDescriptor[size]; 671 } 672 }; 673 674 @Override writeToParcel(Parcel dest, int flags)675 public void writeToParcel(Parcel dest, int flags) { 676 dest.writeInt(mRegion); 677 dest.writeInt(mType); 678 dest.writeInt(mLowerLimit); 679 dest.writeInt(mUpperLimit); 680 dest.writeInt(mSpacing); 681 } 682 683 @Override describeContents()684 public int describeContents() { 685 return 0; 686 } 687 688 @NonNull 689 @Override toString()690 public String toString() { 691 return "BandDescriptor [mRegion=" + mRegion + ", mType=" + mType + ", mLowerLimit=" 692 + mLowerLimit + ", mUpperLimit=" + mUpperLimit + ", mSpacing=" + mSpacing + "]"; 693 } 694 695 @Override hashCode()696 public int hashCode() { 697 final int prime = 31; 698 int result = 1; 699 result = prime * result + mRegion; 700 result = prime * result + mType; 701 result = prime * result + mLowerLimit; 702 result = prime * result + mUpperLimit; 703 result = prime * result + mSpacing; 704 return result; 705 } 706 707 @Override equals(@ullable Object obj)708 public boolean equals(@Nullable Object obj) { 709 if (this == obj) 710 return true; 711 if (!(obj instanceof BandDescriptor)) 712 return false; 713 BandDescriptor other = (BandDescriptor) obj; 714 if (mRegion != other.getRegion()) 715 return false; 716 if (mType != other.getType()) 717 return false; 718 if (mLowerLimit != other.getLowerLimit()) 719 return false; 720 if (mUpperLimit != other.getUpperLimit()) 721 return false; 722 if (mSpacing != other.getSpacing()) 723 return false; 724 return true; 725 } 726 } 727 728 /** FM band descriptor 729 * @see #BAND_FM 730 * @see #BAND_FM_HD */ 731 public static class FmBandDescriptor extends BandDescriptor { 732 private final boolean mStereo; 733 private final boolean mRds; 734 private final boolean mTa; 735 private final boolean mAf; 736 private final boolean mEa; 737 738 /** @hide */ FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing, boolean stereo, boolean rds, boolean ta, boolean af, boolean ea)739 public FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing, 740 boolean stereo, boolean rds, boolean ta, boolean af, boolean ea) { 741 super(region, type, lowerLimit, upperLimit, spacing); 742 mStereo = stereo; 743 mRds = rds; 744 mTa = ta; 745 mAf = af; 746 mEa = ea; 747 } 748 749 /** Stereo is supported 750 * @return {@code true} if stereo is supported, {@code false} otherwise. 751 */ isStereoSupported()752 public boolean isStereoSupported() { 753 return mStereo; 754 } 755 /** RDS or RBDS(if region is ITU2) is supported 756 * @return {@code true} if RDS or RBDS is supported, {@code false} otherwise. 757 */ isRdsSupported()758 public boolean isRdsSupported() { 759 return mRds; 760 } 761 /** Traffic announcement is supported 762 * @return {@code true} if TA is supported, {@code false} otherwise. 763 */ isTaSupported()764 public boolean isTaSupported() { 765 return mTa; 766 } 767 /** Alternate Frequency Switching is supported 768 * @return {@code true} if AF switching is supported, {@code false} otherwise. 769 */ isAfSupported()770 public boolean isAfSupported() { 771 return mAf; 772 } 773 774 /** Emergency Announcement is supported 775 * @return {@code true} if Emergency annoucement is supported, {@code false} otherwise. 776 */ isEaSupported()777 public boolean isEaSupported() { 778 return mEa; 779 } 780 781 /* Parcelable implementation */ FmBandDescriptor(Parcel in)782 private FmBandDescriptor(Parcel in) { 783 super(in); 784 mStereo = in.readByte() == 1; 785 mRds = in.readByte() == 1; 786 mTa = in.readByte() == 1; 787 mAf = in.readByte() == 1; 788 mEa = in.readByte() == 1; 789 } 790 791 public static final @android.annotation.NonNull Parcelable.Creator<FmBandDescriptor> CREATOR 792 = new Parcelable.Creator<FmBandDescriptor>() { 793 public FmBandDescriptor createFromParcel(Parcel in) { 794 return new FmBandDescriptor(in); 795 } 796 797 public FmBandDescriptor[] newArray(int size) { 798 return new FmBandDescriptor[size]; 799 } 800 }; 801 802 @Override writeToParcel(Parcel dest, int flags)803 public void writeToParcel(Parcel dest, int flags) { 804 super.writeToParcel(dest, flags); 805 dest.writeByte((byte) (mStereo ? 1 : 0)); 806 dest.writeByte((byte) (mRds ? 1 : 0)); 807 dest.writeByte((byte) (mTa ? 1 : 0)); 808 dest.writeByte((byte) (mAf ? 1 : 0)); 809 dest.writeByte((byte) (mEa ? 1 : 0)); 810 } 811 812 @Override describeContents()813 public int describeContents() { 814 return 0; 815 } 816 817 @NonNull 818 @Override toString()819 public String toString() { 820 return "FmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo 821 + ", mRds=" + mRds + ", mTa=" + mTa + ", mAf=" + mAf + 822 ", mEa =" + mEa + "]"; 823 } 824 825 @Override hashCode()826 public int hashCode() { 827 final int prime = 31; 828 int result = super.hashCode(); 829 result = prime * result + (mStereo ? 1 : 0); 830 result = prime * result + (mRds ? 1 : 0); 831 result = prime * result + (mTa ? 1 : 0); 832 result = prime * result + (mAf ? 1 : 0); 833 result = prime * result + (mEa ? 1 : 0); 834 return result; 835 } 836 837 @Override equals(@ullable Object obj)838 public boolean equals(@Nullable Object obj) { 839 if (this == obj) 840 return true; 841 if (!super.equals(obj)) 842 return false; 843 if (!(obj instanceof FmBandDescriptor)) 844 return false; 845 FmBandDescriptor other = (FmBandDescriptor) obj; 846 if (mStereo != other.isStereoSupported()) 847 return false; 848 if (mRds != other.isRdsSupported()) 849 return false; 850 if (mTa != other.isTaSupported()) 851 return false; 852 if (mAf != other.isAfSupported()) 853 return false; 854 if (mEa != other.isEaSupported()) 855 return false; 856 return true; 857 } 858 } 859 860 /** AM band descriptor. 861 * @see #BAND_AM */ 862 public static class AmBandDescriptor extends BandDescriptor { 863 864 private final boolean mStereo; 865 866 /** @hide */ AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing, boolean stereo)867 public AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing, 868 boolean stereo) { 869 super(region, type, lowerLimit, upperLimit, spacing); 870 mStereo = stereo; 871 } 872 873 /** Stereo is supported 874 * @return {@code true} if stereo is supported, {@code false} otherwise. 875 */ isStereoSupported()876 public boolean isStereoSupported() { 877 return mStereo; 878 } 879 AmBandDescriptor(Parcel in)880 private AmBandDescriptor(Parcel in) { 881 super(in); 882 mStereo = in.readByte() == 1; 883 } 884 885 public static final @android.annotation.NonNull Parcelable.Creator<AmBandDescriptor> CREATOR 886 = new Parcelable.Creator<AmBandDescriptor>() { 887 public AmBandDescriptor createFromParcel(Parcel in) { 888 return new AmBandDescriptor(in); 889 } 890 891 public AmBandDescriptor[] newArray(int size) { 892 return new AmBandDescriptor[size]; 893 } 894 }; 895 896 @Override writeToParcel(Parcel dest, int flags)897 public void writeToParcel(Parcel dest, int flags) { 898 super.writeToParcel(dest, flags); 899 dest.writeByte((byte) (mStereo ? 1 : 0)); 900 } 901 902 @Override describeContents()903 public int describeContents() { 904 return 0; 905 } 906 907 @NonNull 908 @Override toString()909 public String toString() { 910 return "AmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo + "]"; 911 } 912 913 @Override hashCode()914 public int hashCode() { 915 final int prime = 31; 916 int result = super.hashCode(); 917 result = prime * result + (mStereo ? 1 : 0); 918 return result; 919 } 920 921 @Override equals(@ullable Object obj)922 public boolean equals(@Nullable Object obj) { 923 if (this == obj) 924 return true; 925 if (!super.equals(obj)) 926 return false; 927 if (!(obj instanceof AmBandDescriptor)) 928 return false; 929 AmBandDescriptor other = (AmBandDescriptor) obj; 930 if (mStereo != other.isStereoSupported()) 931 return false; 932 return true; 933 } 934 } 935 936 937 /** Radio band configuration. */ 938 public static class BandConfig implements Parcelable { 939 940 @NonNull final BandDescriptor mDescriptor; 941 BandConfig(BandDescriptor descriptor)942 BandConfig(BandDescriptor descriptor) { 943 Objects.requireNonNull(descriptor, "Descriptor cannot be null"); 944 mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(), 945 descriptor.getLowerLimit(), descriptor.getUpperLimit(), 946 descriptor.getSpacing()); 947 } 948 BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing)949 BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing) { 950 mDescriptor = new BandDescriptor(region, type, lowerLimit, upperLimit, spacing); 951 } 952 BandConfig(Parcel in)953 private BandConfig(Parcel in) { 954 mDescriptor = new BandDescriptor(in); 955 } 956 getDescriptor()957 BandDescriptor getDescriptor() { 958 return mDescriptor; 959 } 960 961 /** Region this band applies to. E.g. {@link #REGION_ITU_1} 962 * @return the region associated with this band. 963 */ getRegion()964 public int getRegion() { 965 return mDescriptor.getRegion(); 966 } 967 /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to: 968 * <ul> 969 * <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li> 970 * <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li> 971 * </ul> 972 * @return the band type. 973 */ getType()974 public int getType() { 975 return mDescriptor.getType(); 976 } 977 /** Lower band limit expressed in units according to band type. 978 * Currently all defined band types express channels as frequency in kHz 979 * @return the lower band limit. 980 */ getLowerLimit()981 public int getLowerLimit() { 982 return mDescriptor.getLowerLimit(); 983 } 984 /** Upper band limit expressed in units according to band type. 985 * Currently all defined band types express channels as frequency in kHz 986 * @return the upper band limit. 987 */ getUpperLimit()988 public int getUpperLimit() { 989 return mDescriptor.getUpperLimit(); 990 } 991 /** Channel spacing in units according to band type. 992 * Currently all defined band types express channels as frequency in kHz 993 * @return the channel spacing. 994 */ getSpacing()995 public int getSpacing() { 996 return mDescriptor.getSpacing(); 997 } 998 999 1000 public static final @android.annotation.NonNull Parcelable.Creator<BandConfig> CREATOR 1001 = new Parcelable.Creator<BandConfig>() { 1002 public BandConfig createFromParcel(Parcel in) { 1003 int type = BandDescriptor.lookupTypeFromParcel(in); 1004 switch (type) { 1005 case BAND_FM: 1006 case BAND_FM_HD: 1007 return new FmBandConfig(in); 1008 case BAND_AM: 1009 case BAND_AM_HD: 1010 return new AmBandConfig(in); 1011 default: 1012 throw new IllegalArgumentException("Unsupported band: " + type); 1013 } 1014 } 1015 1016 public BandConfig[] newArray(int size) { 1017 return new BandConfig[size]; 1018 } 1019 }; 1020 1021 @Override writeToParcel(Parcel dest, int flags)1022 public void writeToParcel(Parcel dest, int flags) { 1023 mDescriptor.writeToParcel(dest, flags); 1024 } 1025 1026 @Override describeContents()1027 public int describeContents() { 1028 return 0; 1029 } 1030 1031 @NonNull 1032 @Override toString()1033 public String toString() { 1034 return "BandConfig [ " + mDescriptor.toString() + "]"; 1035 } 1036 1037 @Override hashCode()1038 public int hashCode() { 1039 final int prime = 31; 1040 int result = 1; 1041 result = prime * result + mDescriptor.hashCode(); 1042 return result; 1043 } 1044 1045 @Override equals(@ullable Object obj)1046 public boolean equals(@Nullable Object obj) { 1047 if (this == obj) 1048 return true; 1049 if (!(obj instanceof BandConfig)) 1050 return false; 1051 BandConfig other = (BandConfig) obj; 1052 BandDescriptor otherDesc = other.getDescriptor(); 1053 if ((mDescriptor == null) != (otherDesc == null)) return false; 1054 if (mDescriptor != null && !mDescriptor.equals(otherDesc)) return false; 1055 return true; 1056 } 1057 } 1058 1059 /** FM band configuration. 1060 * @see #BAND_FM 1061 * @see #BAND_FM_HD */ 1062 public static class FmBandConfig extends BandConfig { 1063 private final boolean mStereo; 1064 private final boolean mRds; 1065 private final boolean mTa; 1066 private final boolean mAf; 1067 private final boolean mEa; 1068 1069 /** @hide */ FmBandConfig(FmBandDescriptor descriptor)1070 public FmBandConfig(FmBandDescriptor descriptor) { 1071 super((BandDescriptor)descriptor); 1072 mStereo = descriptor.isStereoSupported(); 1073 mRds = descriptor.isRdsSupported(); 1074 mTa = descriptor.isTaSupported(); 1075 mAf = descriptor.isAfSupported(); 1076 mEa = descriptor.isEaSupported(); 1077 } 1078 FmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing, boolean stereo, boolean rds, boolean ta, boolean af, boolean ea)1079 FmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing, 1080 boolean stereo, boolean rds, boolean ta, boolean af, boolean ea) { 1081 super(region, type, lowerLimit, upperLimit, spacing); 1082 mStereo = stereo; 1083 mRds = rds; 1084 mTa = ta; 1085 mAf = af; 1086 mEa = ea; 1087 } 1088 1089 /** Get stereo enable state 1090 * @return the enable state. 1091 */ getStereo()1092 public boolean getStereo() { 1093 return mStereo; 1094 } 1095 1096 /** Get RDS or RBDS(if region is ITU2) enable state 1097 * @return the enable state. 1098 */ getRds()1099 public boolean getRds() { 1100 return mRds; 1101 } 1102 1103 /** Get Traffic announcement enable state 1104 * @return the enable state. 1105 */ getTa()1106 public boolean getTa() { 1107 return mTa; 1108 } 1109 1110 /** Get Alternate Frequency Switching enable state 1111 * @return the enable state. 1112 */ getAf()1113 public boolean getAf() { 1114 return mAf; 1115 } 1116 1117 /** 1118 * Get Emergency announcement enable state 1119 * @return the enable state. 1120 */ getEa()1121 public boolean getEa() { 1122 return mEa; 1123 } 1124 FmBandConfig(Parcel in)1125 private FmBandConfig(Parcel in) { 1126 super(in); 1127 mStereo = in.readByte() == 1; 1128 mRds = in.readByte() == 1; 1129 mTa = in.readByte() == 1; 1130 mAf = in.readByte() == 1; 1131 mEa = in.readByte() == 1; 1132 } 1133 1134 public static final @android.annotation.NonNull Parcelable.Creator<FmBandConfig> CREATOR 1135 = new Parcelable.Creator<FmBandConfig>() { 1136 public FmBandConfig createFromParcel(Parcel in) { 1137 return new FmBandConfig(in); 1138 } 1139 1140 public FmBandConfig[] newArray(int size) { 1141 return new FmBandConfig[size]; 1142 } 1143 }; 1144 1145 @Override writeToParcel(Parcel dest, int flags)1146 public void writeToParcel(Parcel dest, int flags) { 1147 super.writeToParcel(dest, flags); 1148 dest.writeByte((byte) (mStereo ? 1 : 0)); 1149 dest.writeByte((byte) (mRds ? 1 : 0)); 1150 dest.writeByte((byte) (mTa ? 1 : 0)); 1151 dest.writeByte((byte) (mAf ? 1 : 0)); 1152 dest.writeByte((byte) (mEa ? 1 : 0)); 1153 } 1154 1155 @Override describeContents()1156 public int describeContents() { 1157 return 0; 1158 } 1159 1160 @NonNull 1161 @Override toString()1162 public String toString() { 1163 return "FmBandConfig [" + super.toString() 1164 + ", mStereo=" + mStereo + ", mRds=" + mRds + ", mTa=" + mTa 1165 + ", mAf=" + mAf + ", mEa =" + mEa + "]"; 1166 } 1167 1168 @Override hashCode()1169 public int hashCode() { 1170 final int prime = 31; 1171 int result = super.hashCode(); 1172 result = prime * result + (mStereo ? 1 : 0); 1173 result = prime * result + (mRds ? 1 : 0); 1174 result = prime * result + (mTa ? 1 : 0); 1175 result = prime * result + (mAf ? 1 : 0); 1176 result = prime * result + (mEa ? 1 : 0); 1177 return result; 1178 } 1179 1180 @Override equals(@ullable Object obj)1181 public boolean equals(@Nullable Object obj) { 1182 if (this == obj) 1183 return true; 1184 if (!super.equals(obj)) 1185 return false; 1186 if (!(obj instanceof FmBandConfig)) 1187 return false; 1188 FmBandConfig other = (FmBandConfig) obj; 1189 if (mStereo != other.mStereo) 1190 return false; 1191 if (mRds != other.mRds) 1192 return false; 1193 if (mTa != other.mTa) 1194 return false; 1195 if (mAf != other.mAf) 1196 return false; 1197 if (mEa != other.mEa) 1198 return false; 1199 return true; 1200 } 1201 1202 /** 1203 * Builder class for {@link FmBandConfig} objects. 1204 */ 1205 public static class Builder { 1206 private final BandDescriptor mDescriptor; 1207 private boolean mStereo; 1208 private boolean mRds; 1209 private boolean mTa; 1210 private boolean mAf; 1211 private boolean mEa; 1212 1213 /** 1214 * Constructs a new Builder with the defaults from an {@link FmBandDescriptor} . 1215 * @param descriptor the FmBandDescriptor defaults are read from . 1216 */ Builder(FmBandDescriptor descriptor)1217 public Builder(FmBandDescriptor descriptor) { 1218 mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(), 1219 descriptor.getLowerLimit(), descriptor.getUpperLimit(), 1220 descriptor.getSpacing()); 1221 mStereo = descriptor.isStereoSupported(); 1222 mRds = descriptor.isRdsSupported(); 1223 mTa = descriptor.isTaSupported(); 1224 mAf = descriptor.isAfSupported(); 1225 mEa = descriptor.isEaSupported(); 1226 } 1227 1228 /** 1229 * Constructs a new Builder from a given {@link FmBandConfig} 1230 * @param config the FmBandConfig object whose data will be reused in the new Builder. 1231 */ Builder(FmBandConfig config)1232 public Builder(FmBandConfig config) { 1233 mDescriptor = new BandDescriptor(config.getRegion(), config.getType(), 1234 config.getLowerLimit(), config.getUpperLimit(), config.getSpacing()); 1235 mStereo = config.getStereo(); 1236 mRds = config.getRds(); 1237 mTa = config.getTa(); 1238 mAf = config.getAf(); 1239 mEa = config.getEa(); 1240 } 1241 1242 /** 1243 * Combines all of the parameters that have been set and return a new 1244 * {@link FmBandConfig} object. 1245 * @return a new {@link FmBandConfig} object 1246 */ build()1247 public FmBandConfig build() { 1248 FmBandConfig config = new FmBandConfig(mDescriptor.getRegion(), 1249 mDescriptor.getType(), mDescriptor.getLowerLimit(), 1250 mDescriptor.getUpperLimit(), mDescriptor.getSpacing(), 1251 mStereo, mRds, mTa, mAf, mEa); 1252 return config; 1253 } 1254 1255 /** Set stereo enable state 1256 * @param state The new enable state. 1257 * @return the same Builder instance. 1258 */ setStereo(boolean state)1259 public Builder setStereo(boolean state) { 1260 mStereo = state; 1261 return this; 1262 } 1263 1264 /** Set RDS or RBDS(if region is ITU2) enable state 1265 * @param state The new enable state. 1266 * @return the same Builder instance. 1267 */ setRds(boolean state)1268 public Builder setRds(boolean state) { 1269 mRds = state; 1270 return this; 1271 } 1272 1273 /** Set Traffic announcement enable state 1274 * @param state The new enable state. 1275 * @return the same Builder instance. 1276 */ setTa(boolean state)1277 public Builder setTa(boolean state) { 1278 mTa = state; 1279 return this; 1280 } 1281 1282 /** Set Alternate Frequency Switching enable state 1283 * @param state The new enable state. 1284 * @return the same Builder instance. 1285 */ setAf(boolean state)1286 public Builder setAf(boolean state) { 1287 mAf = state; 1288 return this; 1289 } 1290 1291 /** Set Emergency Announcement enable state 1292 * @param state The new enable state. 1293 * @return the same Builder instance. 1294 */ setEa(boolean state)1295 public Builder setEa(boolean state) { 1296 mEa = state; 1297 return this; 1298 } 1299 }; 1300 } 1301 1302 /** AM band configuration. 1303 * @see #BAND_AM */ 1304 public static class AmBandConfig extends BandConfig { 1305 private final boolean mStereo; 1306 1307 /** @hide */ AmBandConfig(AmBandDescriptor descriptor)1308 public AmBandConfig(AmBandDescriptor descriptor) { 1309 super((BandDescriptor)descriptor); 1310 mStereo = descriptor.isStereoSupported(); 1311 } 1312 AmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing, boolean stereo)1313 AmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing, 1314 boolean stereo) { 1315 super(region, type, lowerLimit, upperLimit, spacing); 1316 mStereo = stereo; 1317 } 1318 1319 /** Get stereo enable state 1320 * @return the enable state. 1321 */ getStereo()1322 public boolean getStereo() { 1323 return mStereo; 1324 } 1325 AmBandConfig(Parcel in)1326 private AmBandConfig(Parcel in) { 1327 super(in); 1328 mStereo = in.readByte() == 1; 1329 } 1330 1331 public static final @android.annotation.NonNull Parcelable.Creator<AmBandConfig> CREATOR 1332 = new Parcelable.Creator<AmBandConfig>() { 1333 public AmBandConfig createFromParcel(Parcel in) { 1334 return new AmBandConfig(in); 1335 } 1336 1337 public AmBandConfig[] newArray(int size) { 1338 return new AmBandConfig[size]; 1339 } 1340 }; 1341 1342 @Override writeToParcel(Parcel dest, int flags)1343 public void writeToParcel(Parcel dest, int flags) { 1344 super.writeToParcel(dest, flags); 1345 dest.writeByte((byte) (mStereo ? 1 : 0)); 1346 } 1347 1348 @Override describeContents()1349 public int describeContents() { 1350 return 0; 1351 } 1352 1353 @NonNull 1354 @Override toString()1355 public String toString() { 1356 return "AmBandConfig [" + super.toString() 1357 + ", mStereo=" + mStereo + "]"; 1358 } 1359 1360 @Override hashCode()1361 public int hashCode() { 1362 final int prime = 31; 1363 int result = super.hashCode(); 1364 result = prime * result + (mStereo ? 1 : 0); 1365 return result; 1366 } 1367 1368 @Override equals(@ullable Object obj)1369 public boolean equals(@Nullable Object obj) { 1370 if (this == obj) 1371 return true; 1372 if (!super.equals(obj)) 1373 return false; 1374 if (!(obj instanceof AmBandConfig)) 1375 return false; 1376 AmBandConfig other = (AmBandConfig) obj; 1377 if (mStereo != other.getStereo()) 1378 return false; 1379 return true; 1380 } 1381 1382 /** 1383 * Builder class for {@link AmBandConfig} objects. 1384 */ 1385 public static class Builder { 1386 private final BandDescriptor mDescriptor; 1387 private boolean mStereo; 1388 1389 /** 1390 * Constructs a new Builder with the defaults from an {@link AmBandDescriptor} . 1391 * @param descriptor the FmBandDescriptor defaults are read from . 1392 */ Builder(AmBandDescriptor descriptor)1393 public Builder(AmBandDescriptor descriptor) { 1394 mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(), 1395 descriptor.getLowerLimit(), descriptor.getUpperLimit(), 1396 descriptor.getSpacing()); 1397 mStereo = descriptor.isStereoSupported(); 1398 } 1399 1400 /** 1401 * Constructs a new Builder from a given {@link AmBandConfig} 1402 * @param config the FmBandConfig object whose data will be reused in the new Builder. 1403 */ Builder(AmBandConfig config)1404 public Builder(AmBandConfig config) { 1405 mDescriptor = new BandDescriptor(config.getRegion(), config.getType(), 1406 config.getLowerLimit(), config.getUpperLimit(), config.getSpacing()); 1407 mStereo = config.getStereo(); 1408 } 1409 1410 /** 1411 * Combines all of the parameters that have been set and return a new 1412 * {@link AmBandConfig} object. 1413 * @return a new {@link AmBandConfig} object 1414 */ build()1415 public AmBandConfig build() { 1416 AmBandConfig config = new AmBandConfig(mDescriptor.getRegion(), 1417 mDescriptor.getType(), mDescriptor.getLowerLimit(), 1418 mDescriptor.getUpperLimit(), mDescriptor.getSpacing(), 1419 mStereo); 1420 return config; 1421 } 1422 1423 /** Set stereo enable state 1424 * @param state The new enable state. 1425 * @return the same Builder instance. 1426 */ setStereo(boolean state)1427 public Builder setStereo(boolean state) { 1428 mStereo = state; 1429 return this; 1430 } 1431 }; 1432 } 1433 1434 /** Radio program information. */ 1435 public static class ProgramInfo implements Parcelable { 1436 1437 // sourced from hardware/interfaces/broadcastradio/2.0/types.hal 1438 private static final int FLAG_LIVE = 1 << 0; 1439 private static final int FLAG_MUTED = 1 << 1; 1440 private static final int FLAG_TRAFFIC_PROGRAM = 1 << 2; 1441 private static final int FLAG_TRAFFIC_ANNOUNCEMENT = 1 << 3; 1442 private static final int FLAG_TUNED = 1 << 4; 1443 private static final int FLAG_STEREO = 1 << 5; 1444 1445 @NonNull private final ProgramSelector mSelector; 1446 @Nullable private final ProgramSelector.Identifier mLogicallyTunedTo; 1447 @Nullable private final ProgramSelector.Identifier mPhysicallyTunedTo; 1448 @NonNull private final Collection<ProgramSelector.Identifier> mRelatedContent; 1449 private final int mInfoFlags; 1450 private final int mSignalQuality; 1451 @Nullable private final RadioMetadata mMetadata; 1452 @NonNull private final Map<String, String> mVendorInfo; 1453 1454 /** @hide */ ProgramInfo(@onNull ProgramSelector selector, @Nullable ProgramSelector.Identifier logicallyTunedTo, @Nullable ProgramSelector.Identifier physicallyTunedTo, @Nullable Collection<ProgramSelector.Identifier> relatedContent, int infoFlags, int signalQuality, @Nullable RadioMetadata metadata, @Nullable Map<String, String> vendorInfo)1455 public ProgramInfo(@NonNull ProgramSelector selector, 1456 @Nullable ProgramSelector.Identifier logicallyTunedTo, 1457 @Nullable ProgramSelector.Identifier physicallyTunedTo, 1458 @Nullable Collection<ProgramSelector.Identifier> relatedContent, 1459 int infoFlags, int signalQuality, @Nullable RadioMetadata metadata, 1460 @Nullable Map<String, String> vendorInfo) { 1461 mSelector = Objects.requireNonNull(selector); 1462 mLogicallyTunedTo = logicallyTunedTo; 1463 mPhysicallyTunedTo = physicallyTunedTo; 1464 if (relatedContent == null) { 1465 mRelatedContent = Collections.emptyList(); 1466 } else { 1467 Preconditions.checkCollectionElementsNotNull(relatedContent, "relatedContent"); 1468 mRelatedContent = relatedContent; 1469 } 1470 mInfoFlags = infoFlags; 1471 mSignalQuality = signalQuality; 1472 mMetadata = metadata; 1473 mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo; 1474 } 1475 1476 /** 1477 * Program selector, necessary for tuning to a program. 1478 * 1479 * @return the program selector. 1480 */ getSelector()1481 public @NonNull ProgramSelector getSelector() { 1482 return mSelector; 1483 } 1484 1485 /** 1486 * Identifier currently used for program selection. 1487 * 1488 * This identifier can be used to determine which technology is 1489 * currently being used for reception. 1490 * 1491 * Some program selectors contain tuning information for different radio 1492 * technologies (i.e. FM RDS and DAB). For example, user may tune using 1493 * a ProgramSelector with RDS_PI primary identifier, but the tuner hardware 1494 * may choose to use DAB technology to make actual tuning. This identifier 1495 * must reflect that. 1496 */ getLogicallyTunedTo()1497 public @Nullable ProgramSelector.Identifier getLogicallyTunedTo() { 1498 return mLogicallyTunedTo; 1499 } 1500 1501 /** 1502 * Identifier currently used by hardware to physically tune to a channel. 1503 * 1504 * Some radio technologies broadcast the same program on multiple channels, 1505 * i.e. with RDS AF the same program may be broadcasted on multiple 1506 * alternative frequencies; the same DAB program may be broadcast on 1507 * multiple ensembles. This identifier points to the channel to which the 1508 * radio hardware is physically tuned to. 1509 */ getPhysicallyTunedTo()1510 public @Nullable ProgramSelector.Identifier getPhysicallyTunedTo() { 1511 return mPhysicallyTunedTo; 1512 } 1513 1514 /** 1515 * Primary identifiers of related contents. 1516 * 1517 * Some radio technologies provide pointers to other programs that carry 1518 * related content (i.e. DAB soft-links). This field is a list of pointers 1519 * to other programs on the program list. 1520 * 1521 * Please note, that these identifiers does not have to exist on the program 1522 * list - i.e. DAB tuner may provide information on FM RDS alternatives 1523 * despite not supporting FM RDS. If the system has multiple tuners, another 1524 * one may have it on its list. 1525 */ getRelatedContent()1526 public @Nullable Collection<ProgramSelector.Identifier> getRelatedContent() { 1527 return mRelatedContent; 1528 } 1529 1530 /** Main channel expressed in units according to band type. 1531 * Currently all defined band types express channels as frequency in kHz 1532 * @return the program channel 1533 * @deprecated Use {@link getSelector()} instead. 1534 */ 1535 @Deprecated getChannel()1536 public int getChannel() { 1537 try { 1538 return (int) mSelector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY); 1539 } catch (IllegalArgumentException ex) { 1540 Log.w(TAG, "Not an AM/FM program"); 1541 return 0; 1542 } 1543 } 1544 1545 /** Sub channel ID. E.g 1 for HD radio HD1 1546 * @return the program sub channel 1547 * @deprecated Use {@link getSelector()} instead. 1548 */ 1549 @Deprecated getSubChannel()1550 public int getSubChannel() { 1551 try { 1552 return (int) mSelector.getFirstId( 1553 ProgramSelector.IDENTIFIER_TYPE_HD_SUBCHANNEL) + 1; 1554 } catch (IllegalArgumentException ex) { 1555 // this is a normal behavior for analog AM/FM selector 1556 return 0; 1557 } 1558 } 1559 1560 /** {@code true} if the tuner is currently tuned on a valid station 1561 * @return {@code true} if currently tuned, {@code false} otherwise. 1562 */ isTuned()1563 public boolean isTuned() { 1564 return (mInfoFlags & FLAG_TUNED) != 0; 1565 } 1566 1567 /** {@code true} if the received program is stereo 1568 * @return {@code true} if stereo, {@code false} otherwise. 1569 */ isStereo()1570 public boolean isStereo() { 1571 return (mInfoFlags & FLAG_STEREO) != 0; 1572 } 1573 1574 /** {@code true} if the received program is digital (e.g HD radio) 1575 * @return {@code true} if digital, {@code false} otherwise. 1576 * @deprecated Use {@link getLogicallyTunedTo()} instead. 1577 */ 1578 @Deprecated isDigital()1579 public boolean isDigital() { 1580 ProgramSelector.Identifier id = mLogicallyTunedTo; 1581 if (id == null) id = mSelector.getPrimaryId(); 1582 1583 int type = id.getType(); 1584 return (type != ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY 1585 && type != ProgramSelector.IDENTIFIER_TYPE_RDS_PI); 1586 } 1587 1588 /** 1589 * {@code true} if the program is currently playing live stream. 1590 * This may result in a slightly altered reception parameters, 1591 * usually targetted at reduced latency. 1592 */ isLive()1593 public boolean isLive() { 1594 return (mInfoFlags & FLAG_LIVE) != 0; 1595 } 1596 1597 /** 1598 * {@code true} if radio stream is not playing, ie. due to bad reception 1599 * conditions or buffering. In this state volume knob MAY be disabled to 1600 * prevent user increasing volume too much. 1601 * It does NOT mean the user has muted audio. 1602 */ isMuted()1603 public boolean isMuted() { 1604 return (mInfoFlags & FLAG_MUTED) != 0; 1605 } 1606 1607 /** 1608 * {@code true} if radio station transmits traffic information 1609 * regularily. 1610 */ isTrafficProgram()1611 public boolean isTrafficProgram() { 1612 return (mInfoFlags & FLAG_TRAFFIC_PROGRAM) != 0; 1613 } 1614 1615 /** 1616 * {@code true} if radio station transmits traffic information 1617 * at the very moment. 1618 */ isTrafficAnnouncementActive()1619 public boolean isTrafficAnnouncementActive() { 1620 return (mInfoFlags & FLAG_TRAFFIC_ANNOUNCEMENT) != 0; 1621 } 1622 1623 /** 1624 * Signal quality (as opposed to the name) indication from 0 (no signal) 1625 * to 100 (excellent) 1626 * @return the signal quality indication. 1627 */ getSignalStrength()1628 public int getSignalStrength() { 1629 return mSignalQuality; 1630 } 1631 1632 /** Metadata currently received from this station. 1633 * null if no metadata have been received 1634 * @return current meta data received from this program. 1635 */ getMetadata()1636 public RadioMetadata getMetadata() { 1637 return mMetadata; 1638 } 1639 1640 /** 1641 * A map of vendor-specific opaque strings, passed from HAL without changes. 1642 * Format of these strings can vary across vendors. 1643 * 1644 * It may be used for extra features, that's not supported by a platform, 1645 * for example: paid-service=true; bitrate=320kbps. 1646 * 1647 * Keys must be prefixed with unique vendor Java-style namespace, 1648 * eg. 'com.somecompany.parameter1'. 1649 */ getVendorInfo()1650 public @NonNull Map<String, String> getVendorInfo() { 1651 return mVendorInfo; 1652 } 1653 ProgramInfo(Parcel in)1654 private ProgramInfo(Parcel in) { 1655 mSelector = Objects.requireNonNull(in.readTypedObject(ProgramSelector.CREATOR)); 1656 mLogicallyTunedTo = in.readTypedObject(ProgramSelector.Identifier.CREATOR); 1657 mPhysicallyTunedTo = in.readTypedObject(ProgramSelector.Identifier.CREATOR); 1658 mRelatedContent = in.createTypedArrayList(ProgramSelector.Identifier.CREATOR); 1659 mInfoFlags = in.readInt(); 1660 mSignalQuality = in.readInt(); 1661 mMetadata = in.readTypedObject(RadioMetadata.CREATOR); 1662 mVendorInfo = Utils.readStringMap(in); 1663 } 1664 1665 public static final @android.annotation.NonNull Parcelable.Creator<ProgramInfo> CREATOR 1666 = new Parcelable.Creator<ProgramInfo>() { 1667 public ProgramInfo createFromParcel(Parcel in) { 1668 return new ProgramInfo(in); 1669 } 1670 1671 public ProgramInfo[] newArray(int size) { 1672 return new ProgramInfo[size]; 1673 } 1674 }; 1675 1676 @Override writeToParcel(Parcel dest, int flags)1677 public void writeToParcel(Parcel dest, int flags) { 1678 dest.writeTypedObject(mSelector, flags); 1679 dest.writeTypedObject(mLogicallyTunedTo, flags); 1680 dest.writeTypedObject(mPhysicallyTunedTo, flags); 1681 Utils.writeTypedCollection(dest, mRelatedContent); 1682 dest.writeInt(mInfoFlags); 1683 dest.writeInt(mSignalQuality); 1684 dest.writeTypedObject(mMetadata, flags); 1685 Utils.writeStringMap(dest, mVendorInfo); 1686 } 1687 1688 @Override describeContents()1689 public int describeContents() { 1690 return 0; 1691 } 1692 1693 @NonNull 1694 @Override toString()1695 public String toString() { 1696 return "ProgramInfo" 1697 + " [selector=" + mSelector 1698 + ", logicallyTunedTo=" + Objects.toString(mLogicallyTunedTo) 1699 + ", physicallyTunedTo=" + Objects.toString(mPhysicallyTunedTo) 1700 + ", relatedContent=" + mRelatedContent.size() 1701 + ", infoFlags=" + mInfoFlags 1702 + ", mSignalQuality=" + mSignalQuality 1703 + ", mMetadata=" + Objects.toString(mMetadata) 1704 + "]"; 1705 } 1706 1707 @Override hashCode()1708 public int hashCode() { 1709 return Objects.hash(mSelector, mLogicallyTunedTo, mPhysicallyTunedTo, 1710 mRelatedContent, mInfoFlags, mSignalQuality, mMetadata, mVendorInfo); 1711 } 1712 1713 @Override equals(@ullable Object obj)1714 public boolean equals(@Nullable Object obj) { 1715 if (this == obj) return true; 1716 if (!(obj instanceof ProgramInfo)) return false; 1717 ProgramInfo other = (ProgramInfo) obj; 1718 1719 if (!mSelector.strictEquals(other.mSelector)) return false; 1720 if (!Objects.equals(mLogicallyTunedTo, other.mLogicallyTunedTo)) return false; 1721 if (!Objects.equals(mPhysicallyTunedTo, other.mPhysicallyTunedTo)) return false; 1722 if (!Objects.equals(mRelatedContent, other.mRelatedContent)) return false; 1723 if (mInfoFlags != other.mInfoFlags) return false; 1724 if (mSignalQuality != other.mSignalQuality) return false; 1725 if (!Objects.equals(mMetadata, other.mMetadata)) return false; 1726 if (!Objects.equals(mVendorInfo, other.mVendorInfo)) return false; 1727 1728 return true; 1729 } 1730 } 1731 1732 1733 /** 1734 * Returns a list of descriptors for all broadcast radio modules present on the device. 1735 * @param modules An List of {@link ModuleProperties} where the list will be returned. 1736 * @return 1737 * <ul> 1738 * <li>{@link #STATUS_OK} in case of success, </li> 1739 * <li>{@link #STATUS_ERROR} in case of unspecified error, </li> 1740 * <li>{@link #STATUS_NO_INIT} if the native service cannot be reached, </li> 1741 * <li>{@link #STATUS_BAD_VALUE} if modules is null, </li> 1742 * <li>{@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails, </li> 1743 * </ul> 1744 */ 1745 @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) 1746 @RadioStatusType listModules(List<ModuleProperties> modules)1747 public int listModules(List<ModuleProperties> modules) { 1748 if (modules == null) { 1749 Log.e(TAG, "the output list must not be empty"); 1750 return STATUS_BAD_VALUE; 1751 } 1752 1753 Log.d(TAG, "Listing available tuners..."); 1754 List<ModuleProperties> returnedList; 1755 try { 1756 returnedList = mService.listModules(); 1757 } catch (RemoteException e) { 1758 Log.e(TAG, "Failed listing available tuners", e); 1759 return STATUS_DEAD_OBJECT; 1760 } 1761 1762 if (returnedList == null) { 1763 Log.e(TAG, "Returned list was a null"); 1764 return STATUS_ERROR; 1765 } 1766 1767 modules.addAll(returnedList); 1768 return STATUS_OK; 1769 } 1770 nativeListModules(List<ModuleProperties> modules)1771 private native int nativeListModules(List<ModuleProperties> modules); 1772 1773 /** 1774 * Open an interface to control a tuner on a given broadcast radio module. 1775 * Optionally selects and applies the configuration passed as "config" argument. 1776 * @param moduleId radio module identifier {@link ModuleProperties#getId()}. Mandatory. 1777 * @param config desired band and configuration to apply when enabling the hardware module. 1778 * optional, can be null. 1779 * @param withAudio {@code true} to request a tuner with an audio source. 1780 * This tuner is intended for live listening or recording or a radio program. 1781 * If {@code false}, the tuner can only be used to retrieve program informations. 1782 * @param callback {@link RadioTuner.Callback} interface. Mandatory. 1783 * @param handler the Handler on which the callbacks will be received. 1784 * Can be null if default handler is OK. 1785 * @return a valid {@link RadioTuner} interface in case of success or null in case of error. 1786 */ 1787 @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) openTuner(int moduleId, BandConfig config, boolean withAudio, RadioTuner.Callback callback, Handler handler)1788 public RadioTuner openTuner(int moduleId, BandConfig config, boolean withAudio, 1789 RadioTuner.Callback callback, Handler handler) { 1790 if (callback == null) { 1791 throw new IllegalArgumentException("callback must not be empty"); 1792 } 1793 1794 Log.d(TAG, "Opening tuner " + moduleId + "..."); 1795 1796 ITuner tuner; 1797 TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler); 1798 try { 1799 tuner = mService.openTuner(moduleId, config, withAudio, halCallback); 1800 } catch (RemoteException | IllegalArgumentException | IllegalStateException ex) { 1801 Log.e(TAG, "Failed to open tuner", ex); 1802 return null; 1803 } 1804 if (tuner == null) { 1805 Log.e(TAG, "Failed to open tuner"); 1806 return null; 1807 } 1808 return new TunerAdapter(tuner, halCallback, 1809 config != null ? config.getType() : BAND_INVALID); 1810 } 1811 1812 private final Map<Announcement.OnListUpdatedListener, ICloseHandle> mAnnouncementListeners = 1813 new HashMap<>(); 1814 1815 /** 1816 * Adds new announcement listener. 1817 * 1818 * @param enabledAnnouncementTypes a set of announcement types to listen to 1819 * @param listener announcement listener 1820 */ 1821 @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) addAnnouncementListener(@onNull Set<Integer> enabledAnnouncementTypes, @NonNull Announcement.OnListUpdatedListener listener)1822 public void addAnnouncementListener(@NonNull Set<Integer> enabledAnnouncementTypes, 1823 @NonNull Announcement.OnListUpdatedListener listener) { 1824 addAnnouncementListener(cmd -> cmd.run(), enabledAnnouncementTypes, listener); 1825 } 1826 1827 /** 1828 * Adds new announcement listener with executor. 1829 * 1830 * @param executor the executor 1831 * @param enabledAnnouncementTypes a set of announcement types to listen to 1832 * @param listener announcement listener 1833 */ 1834 @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) addAnnouncementListener(@onNull @allbackExecutor Executor executor, @NonNull Set<Integer> enabledAnnouncementTypes, @NonNull Announcement.OnListUpdatedListener listener)1835 public void addAnnouncementListener(@NonNull @CallbackExecutor Executor executor, 1836 @NonNull Set<Integer> enabledAnnouncementTypes, 1837 @NonNull Announcement.OnListUpdatedListener listener) { 1838 Objects.requireNonNull(executor); 1839 Objects.requireNonNull(listener); 1840 int[] types = enabledAnnouncementTypes.stream().mapToInt(Integer::intValue).toArray(); 1841 IAnnouncementListener listenerIface = new IAnnouncementListener.Stub() { 1842 public void onListUpdated(List<Announcement> activeAnnouncements) { 1843 executor.execute(() -> listener.onListUpdated(activeAnnouncements)); 1844 } 1845 }; 1846 synchronized (mAnnouncementListeners) { 1847 ICloseHandle closeHandle = null; 1848 try { 1849 closeHandle = mService.addAnnouncementListener(types, listenerIface); 1850 } catch (RemoteException ex) { 1851 ex.rethrowFromSystemServer(); 1852 } 1853 Objects.requireNonNull(closeHandle); 1854 ICloseHandle oldCloseHandle = mAnnouncementListeners.put(listener, closeHandle); 1855 if (oldCloseHandle != null) Utils.close(oldCloseHandle); 1856 } 1857 } 1858 1859 /** 1860 * Removes previously registered announcement listener. 1861 * 1862 * @param listener announcement listener, previously registered with 1863 * {@link addAnnouncementListener} 1864 */ 1865 @RequiresPermission(Manifest.permission.ACCESS_BROADCAST_RADIO) removeAnnouncementListener(@onNull Announcement.OnListUpdatedListener listener)1866 public void removeAnnouncementListener(@NonNull Announcement.OnListUpdatedListener listener) { 1867 Objects.requireNonNull(listener); 1868 synchronized (mAnnouncementListeners) { 1869 ICloseHandle closeHandle = mAnnouncementListeners.remove(listener); 1870 if (closeHandle != null) Utils.close(closeHandle); 1871 } 1872 } 1873 1874 @NonNull private final Context mContext; 1875 @NonNull private final IRadioService mService; 1876 1877 /** 1878 * @hide 1879 */ RadioManager(Context context)1880 public RadioManager(Context context) throws ServiceNotFoundException { 1881 this(context, IRadioService.Stub.asInterface(ServiceManager.getServiceOrThrow( 1882 Context.RADIO_SERVICE))); 1883 } 1884 1885 /** 1886 * @hide 1887 */ 1888 @VisibleForTesting RadioManager(Context context, IRadioService service)1889 public RadioManager(Context context, IRadioService service) { 1890 mContext = context; 1891 mService = service; 1892 } 1893 } 1894