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