Merge "broadcast radio API"
diff --git a/api/system-current.txt b/api/system-current.txt
index 9365ea3..5388207 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -14189,6 +14189,195 @@
 
 }
 
+package android.hardware.radio {
+
+  public class RadioManager {
+    method public int listModules(java.util.List<android.hardware.radio.RadioManager.ModuleProperties>);
+    method public android.hardware.radio.RadioTuner openTuner(int, android.hardware.radio.RadioManager.BandConfig, boolean, android.hardware.radio.RadioTuner.Callback, android.os.Handler);
+    field public static final int BAND_AM = 0; // 0x0
+    field public static final int BAND_AM_HD = 3; // 0x3
+    field public static final int BAND_FM = 1; // 0x1
+    field public static final int BAND_FM_HD = 2; // 0x2
+    field public static final int CLASS_AM_FM = 0; // 0x0
+    field public static final int CLASS_DT = 2; // 0x2
+    field public static final int CLASS_SAT = 1; // 0x1
+    field public static final int REGION_ITU_1 = 0; // 0x0
+    field public static final int REGION_ITU_2 = 1; // 0x1
+    field public static final int REGION_JAPAN = 3; // 0x3
+    field public static final int REGION_KOREA = 4; // 0x4
+    field public static final int REGION_OIRT = 2; // 0x2
+    field public static final int STATUS_BAD_VALUE = -22; // 0xffffffea
+    field public static final int STATUS_DEAD_OBJECT = -32; // 0xffffffe0
+    field public static final int STATUS_ERROR = -2147483648; // 0x80000000
+    field public static final int STATUS_INVALID_OPERATION = -38; // 0xffffffda
+    field public static final int STATUS_NO_INIT = -19; // 0xffffffed
+    field public static final int STATUS_OK = 0; // 0x0
+    field public static final int STATUS_PERMISSION_DENIED = -1; // 0xffffffff
+    field public static final int STATUS_TIMED_OUT = -110; // 0xffffff92
+  }
+
+  public static class RadioManager.AmBandConfig extends android.hardware.radio.RadioManager.BandConfig {
+    method public boolean getStereo();
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.AmBandConfig> CREATOR;
+  }
+
+  public static class RadioManager.AmBandConfig.Builder {
+    ctor public RadioManager.AmBandConfig.Builder(android.hardware.radio.RadioManager.AmBandDescriptor);
+    ctor public RadioManager.AmBandConfig.Builder(android.hardware.radio.RadioManager.AmBandConfig);
+    method public android.hardware.radio.RadioManager.AmBandConfig build();
+    method public android.hardware.radio.RadioManager.AmBandConfig.Builder setStereo(boolean);
+  }
+
+  public static class RadioManager.AmBandDescriptor extends android.hardware.radio.RadioManager.BandDescriptor {
+    method public boolean isStereoSupported();
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.AmBandDescriptor> CREATOR;
+  }
+
+  public static class RadioManager.BandConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getLowerLimit();
+    method public int getRegion();
+    method public int getSpacing();
+    method public int getType();
+    method public int getUpperLimit();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.BandConfig> CREATOR;
+  }
+
+  public static class RadioManager.BandDescriptor implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getLowerLimit();
+    method public int getRegion();
+    method public int getSpacing();
+    method public int getType();
+    method public int getUpperLimit();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.BandDescriptor> CREATOR;
+  }
+
+  public static class RadioManager.FmBandConfig extends android.hardware.radio.RadioManager.BandConfig {
+    method public boolean getAf();
+    method public boolean getRds();
+    method public boolean getStereo();
+    method public boolean getTa();
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.FmBandConfig> CREATOR;
+  }
+
+  public static class RadioManager.FmBandConfig.Builder {
+    ctor public RadioManager.FmBandConfig.Builder(android.hardware.radio.RadioManager.FmBandDescriptor);
+    ctor public RadioManager.FmBandConfig.Builder(android.hardware.radio.RadioManager.FmBandConfig);
+    method public android.hardware.radio.RadioManager.FmBandConfig build();
+    method public android.hardware.radio.RadioManager.FmBandConfig.Builder setAf(boolean);
+    method public android.hardware.radio.RadioManager.FmBandConfig.Builder setRds(boolean);
+    method public android.hardware.radio.RadioManager.FmBandConfig.Builder setStereo(boolean);
+    method public android.hardware.radio.RadioManager.FmBandConfig.Builder setTa(boolean);
+  }
+
+  public static class RadioManager.FmBandDescriptor extends android.hardware.radio.RadioManager.BandDescriptor {
+    method public boolean isAfSupported();
+    method public boolean isRdsSupported();
+    method public boolean isStereoSupported();
+    method public boolean isTaSupported();
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.FmBandDescriptor> CREATOR;
+  }
+
+  public static class RadioManager.ModuleProperties implements android.os.Parcelable {
+    method public int describeContents();
+    method public android.hardware.radio.RadioManager.BandDescriptor[] getBands();
+    method public int getClassId();
+    method public int getId();
+    method public java.lang.String getImplementor();
+    method public int getNumAudioSources();
+    method public int getNumTuners();
+    method public java.lang.String getProduct();
+    method public java.lang.String getSerial();
+    method public java.lang.String getVersion();
+    method public boolean isCaptureSupported();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.ModuleProperties> CREATOR;
+  }
+
+  public static class RadioManager.ProgramInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getChannel();
+    method public android.hardware.radio.RadioMetadata getMetadata();
+    method public int getSignalStrength();
+    method public int getSubChannel();
+    method public boolean isDigital();
+    method public boolean isStereo();
+    method public boolean isTuned();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.ProgramInfo> CREATOR;
+  }
+
+  public final class RadioMetadata implements android.os.Parcelable {
+    method public boolean containsKey(java.lang.String);
+    method public int describeContents();
+    method public android.graphics.Bitmap getBitmap(java.lang.String);
+    method public int getInt(java.lang.String);
+    method public java.lang.String getString(java.lang.String);
+    method public java.util.Set<java.lang.String> keySet();
+    method public int size();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioMetadata> CREATOR;
+    field public static final java.lang.String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM";
+    field public static final java.lang.String METADATA_KEY_ART = "android.hardware.radio.metadata.ART";
+    field public static final java.lang.String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST";
+    field public static final java.lang.String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE";
+    field public static final java.lang.String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON";
+    field public static final java.lang.String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY";
+    field public static final java.lang.String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI";
+    field public static final java.lang.String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS";
+    field public static final java.lang.String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY";
+    field public static final java.lang.String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT";
+    field public static final java.lang.String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE";
+  }
+
+  public static final class RadioMetadata.Builder {
+    ctor public RadioMetadata.Builder();
+    ctor public RadioMetadata.Builder(android.hardware.radio.RadioMetadata);
+    method public android.hardware.radio.RadioMetadata build();
+    method public android.hardware.radio.RadioMetadata.Builder putBitmap(java.lang.String, android.graphics.Bitmap);
+    method public android.hardware.radio.RadioMetadata.Builder putInt(java.lang.String, int);
+    method public android.hardware.radio.RadioMetadata.Builder putString(java.lang.String, java.lang.String);
+  }
+
+  public abstract class RadioTuner {
+    ctor public RadioTuner();
+    method public abstract int cancel();
+    method public abstract void close();
+    method public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
+    method public abstract boolean getMute();
+    method public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
+    method public abstract boolean hasControl();
+    method public abstract boolean isAntennaConnected();
+    method public abstract int scan(int, boolean);
+    method public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig);
+    method public abstract int setMute(boolean);
+    method public abstract int step(int, boolean);
+    method public abstract int tune(int, int);
+    field public static final int DIRECTION_DOWN = 1; // 0x1
+    field public static final int DIRECTION_UP = 0; // 0x0
+    field public static final int ERROR_CANCELLED = 2; // 0x2
+    field public static final int ERROR_CONFIG = 4; // 0x4
+    field public static final int ERROR_HARDWARE_FAILURE = 0; // 0x0
+    field public static final int ERROR_SCAN_TIMEOUT = 3; // 0x3
+    field public static final int ERROR_SERVER_DIED = 1; // 0x1
+  }
+
+  public static abstract class RadioTuner.Callback {
+    ctor public RadioTuner.Callback();
+    method public void onAntennaState(boolean);
+    method public void onConfigurationChanged(android.hardware.radio.RadioManager.BandConfig);
+    method public void onControlChanged(boolean);
+    method public void onError(int);
+    method public void onMetadataChanged(android.hardware.radio.RadioMetadata);
+    method public void onProgramInfoChanged(android.hardware.radio.RadioManager.ProgramInfo);
+    method public void onTrafficAnnouncement(boolean);
+  }
+
+}
+
 package android.hardware.usb {
 
   public class UsbAccessory implements android.os.Parcelable {
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 993f416..fd7bae7 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -49,6 +49,7 @@
 import android.hardware.input.InputManager;
 import android.hardware.usb.IUsbManager;
 import android.hardware.usb.UsbManager;
+import android.hardware.radio.RadioManager;
 import android.location.CountryDetector;
 import android.location.ICountryDetector;
 import android.location.ILocationManager;
@@ -683,6 +684,13 @@
                 IBinder b = ServiceManager.getService(Context.MIDI_SERVICE);
                 return new MidiManager(ctx, IMidiManager.Stub.asInterface(b));
             }});
+
+        registerService(Context.RADIO_SERVICE, RadioManager.class,
+                new CachedServiceFetcher<RadioManager>() {
+            @Override
+            public RadioManager createService(ContextImpl ctx) {
+                return new RadioManager(ctx);
+            }});
     }
 
     /**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 61cdec3..80b5e0b 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2200,6 +2200,7 @@
             //@hide: PERSISTENT_DATA_BLOCK_SERVICE,
             MEDIA_PROJECTION_SERVICE,
             MIDI_SERVICE,
+            RADIO_SERVICE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -3026,6 +3027,17 @@
      */
     public static final String MIDI_SERVICE = "midi";
 
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.hardware.radio.RadioManager} for accessing the broadcast radio service.
+     *
+     * @see #getSystemService
+     * @hide
+     */
+    public static final String RADIO_SERVICE = "radio";
+
+
     /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java
new file mode 100644
index 0000000..32930a7
--- /dev/null
+++ b/core/java/android/hardware/radio/RadioManager.java
@@ -0,0 +1,1308 @@
+/**
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.util.List;
+import java.util.Arrays;
+
+/**
+ * The RadioManager class allows to control a broadcast radio tuner present on the device.
+ * It provides data structures and methods to query for available radio modules, list their
+ * properties and open an interface to control tuning operations and receive callbacks when
+ * asynchronous operations complete or events occur.
+ * @hide
+ */
+@SystemApi
+public class RadioManager {
+
+    /** Method return status: successful operation */
+    public static final int STATUS_OK = 0;
+    /** Method return status: unspecified error */
+    public static final int STATUS_ERROR = Integer.MIN_VALUE;
+    /** Method return status: permission denied */
+    public static final int STATUS_PERMISSION_DENIED = -1;
+    /** Method return status: initialization failure */
+    public static final int STATUS_NO_INIT = -19;
+    /** Method return status: invalid argument provided */
+    public static final int STATUS_BAD_VALUE = -22;
+    /** Method return status: cannot reach service */
+    public static final int STATUS_DEAD_OBJECT = -32;
+    /** Method return status: invalid or out of sequence operation */
+    public static final int STATUS_INVALID_OPERATION = -38;
+    /** Method return status: time out before operation completion */
+    public static final int STATUS_TIMED_OUT = -110;
+
+
+    // keep in sync with radio_class_t in /system/core/incluse/system/radio.h
+    /** Radio module class supporting FM (including HD radio) and AM */
+    public static final int CLASS_AM_FM = 0;
+    /** Radio module class supporting satellite radio */
+    public static final int CLASS_SAT = 1;
+    /** Radio module class supporting Digital terrestrial radio */
+    public static final int CLASS_DT = 2;
+
+    // keep in sync with radio_band_t in /system/core/incluse/system/radio.h
+    /** AM radio band (LW/MW/SW).
+     * @see BandDescriptor */
+    public static final int BAND_AM = 0;
+    /** FM radio band.
+     * @see BandDescriptor */
+    public static final int BAND_FM = 1;
+    /** FM HD radio or DRM  band.
+     * @see BandDescriptor */
+    public static final int BAND_FM_HD = 2;
+    /** AM HD radio or DRM band.
+     * @see BandDescriptor */
+    public static final int BAND_AM_HD = 3;
+
+    // keep in sync with radio_region_t in /system/core/incluse/system/radio.h
+    /** Africa, Europe.
+     * @see BandDescriptor */
+    public static final int REGION_ITU_1  = 0;
+    /** Americas.
+     * @see BandDescriptor */
+    public static final int REGION_ITU_2  = 1;
+    /** Russia.
+     * @see BandDescriptor */
+    public static final int REGION_OIRT   = 2;
+    /** Japan.
+     * @see BandDescriptor */
+    public static final int REGION_JAPAN  = 3;
+    /** Korea.
+     * @see BandDescriptor */
+    public static final int REGION_KOREA  = 4;
+
+    /*****************************************************************************
+     * Lists properties, options and radio bands supported by a given broadcast radio module.
+     * Each module has a unique ID used to address it when calling RadioManager APIs.
+     * Module properties are returned by {@link #listModules(List <ModuleProperties>)} method.
+     ****************************************************************************/
+    public static class ModuleProperties implements Parcelable {
+
+        private final int mId;
+        private final int mClassId;
+        private final String mImplementor;
+        private final String mProduct;
+        private final String mVersion;
+        private final String mSerial;
+        private final int mNumTuners;
+        private final int mNumAudioSources;
+        private final boolean mIsCaptureSupported;
+        private final BandDescriptor[] mBands;
+
+        ModuleProperties(int id, int classId, String implementor, String product, String version,
+                String serial, int numTuners, int numAudioSources, boolean isCaptureSupported,
+                BandDescriptor[] bands) {
+            mId = id;
+            mClassId = classId;
+            mImplementor = implementor;
+            mProduct = product;
+            mVersion = version;
+            mSerial = serial;
+            mNumTuners = numTuners;
+            mNumAudioSources = numAudioSources;
+            mIsCaptureSupported = isCaptureSupported;
+            mBands = bands;
+        }
+
+
+        /** Unique module identifier provided by the native service.
+         * For use with {@link #openTuner(int, BandConfig, boolean, Callback, Handler)}.
+         * @return the radio module unique identifier.
+         */
+        public int getId() {
+            return mId;
+        }
+
+        /** Module class identifier: {@link #CLASS_AM_FM}, {@link #CLASS_SAT}, {@link #CLASS_DT}
+         * @return the radio module class identifier.
+         */
+        public int getClassId() {
+            return mClassId;
+        }
+
+        /** Human readable broadcast radio module implementor
+         * @return the name of the radio module implementator.
+         */
+        public String getImplementor() {
+            return mImplementor;
+        }
+
+        /** Human readable broadcast radio module product name
+         * @return the radio module product name.
+         */
+        public String getProduct() {
+            return mProduct;
+        }
+
+        /** Human readable broadcast radio module version number
+         * @return the radio module version.
+         */
+        public String getVersion() {
+            return mVersion;
+        }
+
+        /** Radio module serial number.
+         * Can be used for subscription services.
+         * @return the radio module serial number.
+         */
+        public String getSerial() {
+            return mSerial;
+        }
+
+        /** Number of tuners available.
+         * This is the number of tuners that can be open simultaneously.
+         * @return the number of tuners supported.
+         */
+        public int getNumTuners() {
+            return mNumTuners;
+        }
+
+        /** Number tuner audio sources available. Must be less or equal to getNumTuners().
+         * When more than one tuner is supported, one is usually for playback and has one
+         * associated audio source and the other is for pre scanning and building a
+         * program list.
+         * @return the number of audio sources available.
+         */
+        public int getNumAudioSources() {
+            return mNumAudioSources;
+        }
+
+        /** {@code true} if audio capture is possible from radio tuner output.
+         * This indicates if routing to audio devices not connected to the same HAL as the FM radio
+         * is possible (e.g. to USB) or DAR (Digital Audio Recorder) feature can be implemented.
+         * @return {@code true} if audio capture is possible, {@code false} otherwise.
+         */
+        public boolean isCaptureSupported() {
+            return mIsCaptureSupported;
+        }
+
+        /** List of descriptors for all bands supported by this module.
+         * @return an array of {@link BandDescriptor}.
+         */
+        public BandDescriptor[] getBands() {
+            return mBands;
+        }
+
+        private ModuleProperties(Parcel in) {
+            mId = in.readInt();
+            mClassId = in.readInt();
+            mImplementor = in.readString();
+            mProduct = in.readString();
+            mVersion = in.readString();
+            mSerial = in.readString();
+            mNumTuners = in.readInt();
+            mNumAudioSources = in.readInt();
+            mIsCaptureSupported = in.readInt() == 1;
+            Parcelable[] tmp = in.readParcelableArray(BandDescriptor.class.getClassLoader());
+            mBands = new BandDescriptor[tmp.length];
+            for (int i = 0; i < tmp.length; i++) {
+                mBands[i] = (BandDescriptor) tmp[i];
+            }
+        }
+
+        public static final Parcelable.Creator<ModuleProperties> CREATOR
+                = new Parcelable.Creator<ModuleProperties>() {
+            public ModuleProperties createFromParcel(Parcel in) {
+                return new ModuleProperties(in);
+            }
+
+            public ModuleProperties[] newArray(int size) {
+                return new ModuleProperties[size];
+            }
+        };
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mId);
+            dest.writeInt(mClassId);
+            dest.writeString(mImplementor);
+            dest.writeString(mProduct);
+            dest.writeString(mVersion);
+            dest.writeString(mSerial);
+            dest.writeInt(mNumTuners);
+            dest.writeInt(mNumAudioSources);
+            dest.writeInt(mIsCaptureSupported ? 1 : 0);
+            dest.writeParcelableArray(mBands, flags);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "ModuleProperties [mId=" + mId + ", mClassId=" + mClassId
+                    + ", mImplementor=" + mImplementor + ", mProduct=" + mProduct
+                    + ", mVersion=" + mVersion + ", mSerial=" + mSerial
+                    + ", mNumTuners=" + mNumTuners
+                    + ", mNumAudioSources=" + mNumAudioSources
+                    + ", mIsCaptureSupported=" + mIsCaptureSupported
+                    + ", mBands=" + Arrays.toString(mBands) + "]";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + mId;
+            result = prime * result + mClassId;
+            result = prime * result + ((mImplementor == null) ? 0 : mImplementor.hashCode());
+            result = prime * result + ((mProduct == null) ? 0 : mProduct.hashCode());
+            result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode());
+            result = prime * result + ((mSerial == null) ? 0 : mSerial.hashCode());
+            result = prime * result + mNumTuners;
+            result = prime * result + mNumAudioSources;
+            result = prime * result + (mIsCaptureSupported ? 1 : 0);
+            result = prime * result + Arrays.hashCode(mBands);
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!(obj instanceof ModuleProperties))
+                return false;
+            ModuleProperties other = (ModuleProperties) obj;
+            if (mId != other.getId())
+                return false;
+            if (mClassId != other.getClassId())
+                return false;
+            if (mImplementor == null) {
+                if (other.getImplementor() != null)
+                    return false;
+            } else if (!mImplementor.equals(other.getImplementor()))
+                return false;
+            if (mProduct == null) {
+                if (other.getProduct() != null)
+                    return false;
+            } else if (!mProduct.equals(other.getProduct()))
+                return false;
+            if (mVersion == null) {
+                if (other.getVersion() != null)
+                    return false;
+            } else if (!mVersion.equals(other.getVersion()))
+                return false;
+            if (mSerial == null) {
+                if (other.getSerial() != null)
+                    return false;
+            } else if (!mSerial.equals(other.getSerial()))
+                return false;
+            if (mNumTuners != other.getNumTuners())
+                return false;
+            if (mNumAudioSources != other.getNumAudioSources())
+                return false;
+            if (mIsCaptureSupported != other.isCaptureSupported())
+                return false;
+            if (!Arrays.equals(mBands, other.getBands()))
+                return false;
+            return true;
+        }
+    }
+
+    /** Radio band descriptor: an element in ModuleProperties bands array.
+     * It is either an instance of {@link FmBandDescriptor} or {@link AmBandDescriptor} */
+    public static class BandDescriptor implements Parcelable {
+
+        private final int mRegion;
+        private final int mType;
+        private final int mLowerLimit;
+        private final int mUpperLimit;
+        private final int mSpacing;
+
+        BandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing) {
+            mRegion = region;
+            mType = type;
+            mLowerLimit = lowerLimit;
+            mUpperLimit = upperLimit;
+            mSpacing = spacing;
+        }
+
+        /** Region this band applies to. E.g. {@link #REGION_ITU_1}
+         * @return the region this band is associated to.
+         */
+        public int getRegion() {
+            return mRegion;
+        }
+        /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to:
+         * <ul>
+         *  <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li>
+         *  <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li>
+         * </ul>
+         * @return the band type.
+         */
+        public int getType() {
+            return mType;
+        }
+        /** Lower band limit expressed in units according to band type.
+         * Currently all defined band types express channels as frequency in kHz
+         * @return the lower band limit.
+         */
+        public int getLowerLimit() {
+            return mLowerLimit;
+        }
+        /** Upper band limit expressed in units according to band type.
+         * Currently all defined band types express channels as frequency in kHz
+         * @return the upper band limit.
+         */
+        public int getUpperLimit() {
+            return mUpperLimit;
+        }
+        /** Channel spacing in units according to band type.
+         * Currently all defined band types express channels as frequency in kHz
+         * @return the channel spacing.
+         */
+        public int getSpacing() {
+            return mSpacing;
+        }
+
+        private BandDescriptor(Parcel in) {
+            mRegion = in.readInt();
+            mType = in.readInt();
+            mLowerLimit = in.readInt();
+            mUpperLimit = in.readInt();
+            mSpacing = in.readInt();
+        }
+
+        public static final Parcelable.Creator<BandDescriptor> CREATOR
+                = new Parcelable.Creator<BandDescriptor>() {
+            public BandDescriptor createFromParcel(Parcel in) {
+                return new BandDescriptor(in);
+            }
+
+            public BandDescriptor[] newArray(int size) {
+                return new BandDescriptor[size];
+            }
+        };
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mRegion);
+            dest.writeInt(mType);
+            dest.writeInt(mLowerLimit);
+            dest.writeInt(mUpperLimit);
+            dest.writeInt(mSpacing);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "BandDescriptor [mRegion=" + mRegion + ", mType=" + mType + ", mLowerLimit="
+                    + mLowerLimit + ", mUpperLimit=" + mUpperLimit + ", mSpacing=" + mSpacing + "]";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + mRegion;
+            result = prime * result + mType;
+            result = prime * result + mLowerLimit;
+            result = prime * result + mUpperLimit;
+            result = prime * result + mSpacing;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!(obj instanceof BandDescriptor))
+                return false;
+            BandDescriptor other = (BandDescriptor) obj;
+            if (mRegion != other.getRegion())
+                return false;
+            if (mType != other.getType())
+                return false;
+            if (mLowerLimit != other.getLowerLimit())
+                return false;
+            if (mUpperLimit != other.getUpperLimit())
+                return false;
+            if (mSpacing != other.getSpacing())
+                return false;
+            return true;
+        }
+    }
+
+    /** FM band descriptor
+     * @see #BAND_FM
+     * @see #BAND_FM_HD */
+    public static class FmBandDescriptor extends BandDescriptor {
+        private final boolean mStereo;
+        private final boolean mRds;
+        private final boolean mTa;
+        private final boolean mAf;
+
+        FmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+                boolean stereo, boolean rds, boolean ta, boolean af) {
+            super(region, type, lowerLimit, upperLimit, spacing);
+            mStereo = stereo;
+            mRds = rds;
+            mTa = ta;
+            mAf = af;
+        }
+
+        /** Stereo is supported
+         * @return {@code true} if stereo is supported, {@code false} otherwise.
+         */
+        public boolean isStereoSupported() {
+            return mStereo;
+        }
+        /** RDS or RBDS(if region is ITU2) is supported
+         * @return {@code true} if RDS or RBDS is supported, {@code false} otherwise.
+         */
+        public boolean isRdsSupported() {
+            return mRds;
+        }
+        /** Traffic announcement is supported
+         * @return {@code true} if TA is supported, {@code false} otherwise.
+         */
+        public boolean isTaSupported() {
+            return mTa;
+        }
+        /** Alternate Frequency Switching is supported
+         * @return {@code true} if AF switching is supported, {@code false} otherwise.
+         */
+        public boolean isAfSupported() {
+            return mAf;
+        }
+
+        /* Parcelable implementation */
+        private FmBandDescriptor(Parcel in) {
+            super(in);
+            mStereo = in.readByte() == 1;
+            mRds = in.readByte() == 1;
+            mTa = in.readByte() == 1;
+            mAf = in.readByte() == 1;
+        }
+
+        public static final Parcelable.Creator<FmBandDescriptor> CREATOR
+                = new Parcelable.Creator<FmBandDescriptor>() {
+            public FmBandDescriptor createFromParcel(Parcel in) {
+                return new FmBandDescriptor(in);
+            }
+
+            public FmBandDescriptor[] newArray(int size) {
+                return new FmBandDescriptor[size];
+            }
+        };
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeByte((byte) (mStereo ? 1 : 0));
+            dest.writeByte((byte) (mRds ? 1 : 0));
+            dest.writeByte((byte) (mTa ? 1 : 0));
+            dest.writeByte((byte) (mAf ? 1 : 0));
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "FmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo
+                    + ", mRds=" + mRds + ", mTa=" + mTa + ", mAf=" + mAf + "]";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = super.hashCode();
+            result = prime * result + (mStereo ? 1 : 0);
+            result = prime * result + (mRds ? 1 : 0);
+            result = prime * result + (mTa ? 1 : 0);
+            result = prime * result + (mAf ? 1 : 0);
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj))
+                return false;
+            if (!(obj instanceof FmBandDescriptor))
+                return false;
+            FmBandDescriptor other = (FmBandDescriptor) obj;
+            if (mStereo != other.isStereoSupported())
+                return false;
+            if (mRds != other.isRdsSupported())
+                return false;
+            if (mTa != other.isTaSupported())
+                return false;
+            if (mAf != other.isAfSupported())
+                return false;
+            return true;
+        }
+    }
+
+    /** AM band descriptor.
+     * @see #BAND_AM */
+    public static class AmBandDescriptor extends BandDescriptor {
+
+        private final boolean mStereo;
+
+        AmBandDescriptor(int region, int type, int lowerLimit, int upperLimit, int spacing,
+                boolean stereo) {
+            super(region, type, lowerLimit, upperLimit, spacing);
+            mStereo = stereo;
+        }
+
+        /** Stereo is supported
+         *  @return {@code true} if stereo is supported, {@code false} otherwise.
+         */
+        public boolean isStereoSupported() {
+            return mStereo;
+        }
+
+        private AmBandDescriptor(Parcel in) {
+            super(in);
+            mStereo = in.readByte() == 1;
+        }
+
+        public static final Parcelable.Creator<AmBandDescriptor> CREATOR
+                = new Parcelable.Creator<AmBandDescriptor>() {
+            public AmBandDescriptor createFromParcel(Parcel in) {
+                return new AmBandDescriptor(in);
+            }
+
+            public AmBandDescriptor[] newArray(int size) {
+                return new AmBandDescriptor[size];
+            }
+        };
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeByte((byte) (mStereo ? 1 : 0));
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "AmBandDescriptor [ "+ super.toString() + " mStereo=" + mStereo + "]";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = super.hashCode();
+            result = prime * result + (mStereo ? 1 : 0);
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj))
+                return false;
+            if (!(obj instanceof AmBandDescriptor))
+                return false;
+            AmBandDescriptor other = (AmBandDescriptor) obj;
+            if (mStereo != other.isStereoSupported())
+                return false;
+            return true;
+        }
+    }
+
+
+    /** Radio band configuration. */
+    public static class BandConfig implements Parcelable {
+
+        final BandDescriptor mDescriptor;
+
+        BandConfig(BandDescriptor descriptor) {
+            mDescriptor = descriptor;
+        }
+
+        BandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing) {
+            mDescriptor = new BandDescriptor(region, type, lowerLimit, upperLimit, spacing);
+        }
+
+        private BandConfig(Parcel in) {
+            mDescriptor = new BandDescriptor(in);
+        }
+
+        BandDescriptor getDescriptor() {
+            return mDescriptor;
+        }
+
+        /** Region this band applies to. E.g. {@link #REGION_ITU_1}
+         *  @return the region associated with this band.
+         */
+        public int getRegion() {
+            return mDescriptor.getRegion();
+        }
+        /** Band type, e.g {@link #BAND_FM}. Defines the subclass this descriptor can be cast to:
+         * <ul>
+         *  <li>{@link #BAND_FM} or {@link #BAND_FM_HD} cast to {@link FmBandDescriptor}, </li>
+         *  <li>{@link #BAND_AM} cast to {@link AmBandDescriptor}, </li>
+         * </ul>
+         *  @return the band type.
+         */
+        public int getType() {
+            return mDescriptor.getType();
+        }
+        /** Lower band limit expressed in units according to band type.
+         * Currently all defined band types express channels as frequency in kHz
+         *  @return the lower band limit.
+         */
+        public int getLowerLimit() {
+            return mDescriptor.getLowerLimit();
+        }
+        /** Upper band limit expressed in units according to band type.
+         * Currently all defined band types express channels as frequency in kHz
+         *  @return the upper band limit.
+         */
+        public int getUpperLimit() {
+            return mDescriptor.getUpperLimit();
+        }
+        /** Channel spacing in units according to band type.
+         * Currently all defined band types express channels as frequency in kHz
+         *  @return the channel spacing.
+         */
+        public int getSpacing() {
+            return mDescriptor.getSpacing();
+        }
+
+
+        public static final Parcelable.Creator<BandConfig> CREATOR
+                = new Parcelable.Creator<BandConfig>() {
+            public BandConfig createFromParcel(Parcel in) {
+                return new BandConfig(in);
+            }
+
+            public BandConfig[] newArray(int size) {
+                return new BandConfig[size];
+            }
+        };
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            mDescriptor.writeToParcel(dest, flags);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "BandConfig [ " + mDescriptor.toString() + "]";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + mDescriptor.hashCode();
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!(obj instanceof BandConfig))
+                return false;
+            BandConfig other = (BandConfig) obj;
+            if (mDescriptor != other.getDescriptor())
+                return false;
+            return true;
+        }
+    }
+
+    /** FM band configuration.
+     * @see #BAND_FM
+     * @see #BAND_FM_HD */
+    public static class FmBandConfig extends BandConfig {
+        private final boolean mStereo;
+        private final boolean mRds;
+        private final boolean mTa;
+        private final boolean mAf;
+
+        FmBandConfig(FmBandDescriptor descriptor) {
+            super((BandDescriptor)descriptor);
+            mStereo = descriptor.isStereoSupported();
+            mRds = descriptor.isRdsSupported();
+            mTa = descriptor.isTaSupported();
+            mAf = descriptor.isAfSupported();
+        }
+
+        FmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing,
+                boolean stereo, boolean rds, boolean ta, boolean af) {
+            super(region, type, lowerLimit, upperLimit, spacing);
+            mStereo = stereo;
+            mRds = rds;
+            mTa = ta;
+            mAf = af;
+        }
+
+        /** Get stereo enable state
+         * @return the enable state.
+         */
+        public boolean getStereo() {
+            return mStereo;
+        }
+
+        /** Get RDS or RBDS(if region is ITU2) enable state
+         * @return the enable state.
+         */
+        public boolean getRds() {
+            return mRds;
+        }
+
+        /** Get Traffic announcement enable state
+         * @return the enable state.
+         */
+        public boolean getTa() {
+            return mTa;
+        }
+
+        /** Get Alternate Frequency Switching enable state
+         * @return the enable state.
+         */
+        public boolean getAf() {
+            return mAf;
+        }
+
+        private FmBandConfig(Parcel in) {
+            super(in);
+            mStereo = in.readByte() == 1;
+            mRds = in.readByte() == 1;
+            mTa = in.readByte() == 1;
+            mAf = in.readByte() == 1;
+        }
+
+        public static final Parcelable.Creator<FmBandConfig> CREATOR
+                = new Parcelable.Creator<FmBandConfig>() {
+            public FmBandConfig createFromParcel(Parcel in) {
+                return new FmBandConfig(in);
+            }
+
+            public FmBandConfig[] newArray(int size) {
+                return new FmBandConfig[size];
+            }
+        };
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeByte((byte) (mStereo ? 1 : 0));
+            dest.writeByte((byte) (mRds ? 1 : 0));
+            dest.writeByte((byte) (mTa ? 1 : 0));
+            dest.writeByte((byte) (mAf ? 1 : 0));
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "FmBandConfig [" + super.toString()
+                    + ", mStereo=" + mStereo + ", mRds=" + mRds + ", mTa=" + mTa
+                    + ", mAf=" + mAf + "]";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = super.hashCode();
+            result = prime * result + (mStereo ? 1 : 0);
+            result = prime * result + (mRds ? 1 : 0);
+            result = prime * result + (mTa ? 1 : 0);
+            result = prime * result + (mAf ? 1 : 0);
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj))
+                return false;
+            if (!(obj instanceof FmBandConfig))
+                return false;
+            FmBandConfig other = (FmBandConfig) obj;
+            if (mStereo != other.mStereo)
+                return false;
+            if (mRds != other.mRds)
+                return false;
+            if (mTa != other.mTa)
+                return false;
+            if (mAf != other.mAf)
+                return false;
+            return true;
+        }
+
+        /**
+         * Builder class for {@link FmBandConfig} objects.
+         */
+        public static class Builder {
+            private final BandDescriptor mDescriptor;
+            private boolean mStereo;
+            private boolean mRds;
+            private boolean mTa;
+            private boolean mAf;
+
+            /**
+             * Constructs a new Builder with the defaults from an {@link FmBandDescriptor} .
+             * @param descriptor the FmBandDescriptor defaults are read from .
+             */
+            public Builder(FmBandDescriptor descriptor) {
+                mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(),
+                        descriptor.getLowerLimit(), descriptor.getUpperLimit(),
+                        descriptor.getSpacing());
+                mStereo = descriptor.isStereoSupported();
+                mRds = descriptor.isRdsSupported();
+                mTa = descriptor.isTaSupported();
+                mAf = descriptor.isAfSupported();
+            }
+
+            /**
+             * Constructs a new Builder from a given {@link FmBandConfig}
+             * @param config the FmBandConfig object whose data will be reused in the new Builder.
+             */
+            public Builder(FmBandConfig config) {
+                mDescriptor = new BandDescriptor(config.getRegion(), config.getType(),
+                        config.getLowerLimit(), config.getUpperLimit(), config.getSpacing());
+                mStereo = config.getStereo();
+                mRds = config.getRds();
+                mTa = config.getTa();
+                mAf = config.getAf();
+            }
+
+            /**
+             * Combines all of the parameters that have been set and return a new
+             * {@link FmBandConfig} object.
+             * @return a new {@link FmBandConfig} object
+             */
+            public FmBandConfig build() {
+                FmBandConfig config = new FmBandConfig(mDescriptor.getRegion(),
+                        mDescriptor.getType(), mDescriptor.getLowerLimit(),
+                        mDescriptor.getUpperLimit(), mDescriptor.getSpacing(),
+                        mStereo, mRds, mTa, mAf);
+                return config;
+            }
+
+            /** Set stereo enable state
+             * @param state The new enable state.
+             * @return the same Builder instance.
+             */
+            public Builder setStereo(boolean state) {
+                mStereo = state;
+                return this;
+            }
+
+            /** Set RDS or RBDS(if region is ITU2) enable state
+             * @param state The new enable state.
+             * @return the same Builder instance.
+             */
+            public Builder setRds(boolean state) {
+                mRds = state;
+                return this;
+            }
+
+            /** Set Traffic announcement enable state
+             * @param state The new enable state.
+             * @return the same Builder instance.
+             */
+            public Builder setTa(boolean state) {
+                mTa = state;
+                return this;
+            }
+
+            /** Set Alternate Frequency Switching enable state
+             * @param state The new enable state.
+             * @return the same Builder instance.
+             */
+            public Builder setAf(boolean state) {
+                mAf = state;
+                return this;
+            }
+        };
+    }
+
+    /** AM band configuration.
+     * @see #BAND_AM */
+    public static class AmBandConfig extends BandConfig {
+        private final boolean mStereo;
+
+        AmBandConfig(AmBandDescriptor descriptor) {
+            super((BandDescriptor)descriptor);
+            mStereo = descriptor.isStereoSupported();
+        }
+
+        AmBandConfig(int region, int type, int lowerLimit, int upperLimit, int spacing,
+                boolean stereo) {
+            super(region, type, lowerLimit, upperLimit, spacing);
+            mStereo = stereo;
+        }
+
+        /** Get stereo enable state
+         * @return the enable state.
+         */
+        public boolean getStereo() {
+            return mStereo;
+        }
+
+        private AmBandConfig(Parcel in) {
+            super(in);
+            mStereo = in.readByte() == 1;
+        }
+
+        public static final Parcelable.Creator<AmBandConfig> CREATOR
+                = new Parcelable.Creator<AmBandConfig>() {
+            public AmBandConfig createFromParcel(Parcel in) {
+                return new AmBandConfig(in);
+            }
+
+            public AmBandConfig[] newArray(int size) {
+                return new AmBandConfig[size];
+            }
+        };
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            super.writeToParcel(dest, flags);
+            dest.writeByte((byte) (mStereo ? 1 : 0));
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "AmBandConfig [" + super.toString()
+                    + ", mStereo=" + mStereo + "]";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = super.hashCode();
+            result = prime * result + (mStereo ? 1 : 0);
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj))
+                return false;
+            if (!(obj instanceof AmBandConfig))
+                return false;
+            AmBandConfig other = (AmBandConfig) obj;
+            if (mStereo != other.getStereo())
+                return false;
+            return true;
+        }
+
+        /**
+         * Builder class for {@link AmBandConfig} objects.
+         */
+        public static class Builder {
+            private final BandDescriptor mDescriptor;
+            private boolean mStereo;
+
+            /**
+             * Constructs a new Builder with the defaults from an {@link AmBandDescriptor} .
+             * @param descriptor the FmBandDescriptor defaults are read from .
+             */
+            public Builder(AmBandDescriptor descriptor) {
+                mDescriptor = new BandDescriptor(descriptor.getRegion(), descriptor.getType(),
+                        descriptor.getLowerLimit(), descriptor.getUpperLimit(),
+                        descriptor.getSpacing());
+                mStereo = descriptor.isStereoSupported();
+            }
+
+            /**
+             * Constructs a new Builder from a given {@link AmBandConfig}
+             * @param config the FmBandConfig object whose data will be reused in the new Builder.
+             */
+            public Builder(AmBandConfig config) {
+                mDescriptor = new BandDescriptor(config.getRegion(), config.getType(),
+                        config.getLowerLimit(), config.getUpperLimit(), config.getSpacing());
+                mStereo = config.getStereo();
+            }
+
+            /**
+             * Combines all of the parameters that have been set and return a new
+             * {@link AmBandConfig} object.
+             * @return a new {@link AmBandConfig} object
+             */
+            public AmBandConfig build() {
+                AmBandConfig config = new AmBandConfig(mDescriptor.getRegion(),
+                        mDescriptor.getType(), mDescriptor.getLowerLimit(),
+                        mDescriptor.getUpperLimit(), mDescriptor.getSpacing(),
+                        mStereo);
+                return config;
+            }
+
+            /** Set stereo enable state
+             * @param state The new enable state.
+             * @return the same Builder instance.
+             */
+            public Builder setStereo(boolean state) {
+                mStereo = state;
+                return this;
+            }
+        };
+    }
+
+    /** Radio program information returned by
+     * {@link RadioTuner#getProgramInformation(RadioManager.ProgramInfo[])} */
+    public static class ProgramInfo implements Parcelable {
+
+        private final int mChannel;
+        private final int mSubChannel;
+        private final boolean mTuned;
+        private final boolean mStereo;
+        private final boolean mDigital;
+        private final int mSignalStrength;
+        private final RadioMetadata mMetadata;
+
+        ProgramInfo(int channel, int subChannel, boolean tuned, boolean stereo,
+                boolean digital, int signalStrength, RadioMetadata metadata) {
+            mChannel = channel;
+            mSubChannel = subChannel;
+            mTuned = tuned;
+            mStereo = stereo;
+            mDigital = digital;
+            mSignalStrength = signalStrength;
+            mMetadata = metadata;
+        }
+
+        /** Main channel expressed in units according to band type.
+         * Currently all defined band types express channels as frequency in kHz
+         * @return the program channel
+         */
+        public int getChannel() {
+            return mChannel;
+        }
+        /** Sub channel ID. E.g 1 for HD radio HD1
+         * @return the program sub channel
+         */
+        public int getSubChannel() {
+            return mSubChannel;
+        }
+        /** {@code true} if the tuner is currently tuned on a valid station
+         * @return {@code true} if currently tuned, {@code false} otherwise.
+         */
+        public boolean isTuned() {
+            return mTuned;
+        }
+        /** {@code true} if the received program is stereo
+         * @return {@code true} if stereo, {@code false} otherwise.
+         */
+        public boolean isStereo() {
+            return mStereo;
+        }
+        /** {@code true} if the received program is digital (e.g HD radio)
+         * @return {@code true} if digital, {@code false} otherwise.
+         */
+        public boolean isDigital() {
+            return mDigital;
+        }
+        /** Signal strength indicator from 0 (no signal) to 100 (excellent)
+         * @return the signal strength indication.
+         */
+        public int getSignalStrength() {
+            return mSignalStrength;
+        }
+        /** Metadata currently received from this station.
+         * null if no metadata have been received
+         * @return current meta data received from this program.
+         */
+        public RadioMetadata getMetadata() {
+            return mMetadata;
+        }
+
+        private ProgramInfo(Parcel in) {
+            mChannel = in.readInt();
+            mSubChannel = in.readInt();
+            mTuned = in.readByte() == 1;
+            mStereo = in.readByte() == 1;
+            mDigital = in.readByte() == 1;
+            mSignalStrength = in.readInt();
+            if (in.readByte() == 1) {
+                mMetadata = RadioMetadata.CREATOR.createFromParcel(in);
+            } else {
+                mMetadata = null;
+            }
+        }
+
+        public static final Parcelable.Creator<ProgramInfo> CREATOR
+                = new Parcelable.Creator<ProgramInfo>() {
+            public ProgramInfo createFromParcel(Parcel in) {
+                return new ProgramInfo(in);
+            }
+
+            public ProgramInfo[] newArray(int size) {
+                return new ProgramInfo[size];
+            }
+        };
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mChannel);
+            dest.writeInt(mSubChannel);
+            dest.writeByte((byte)(mTuned ? 1 : 0));
+            dest.writeByte((byte)(mStereo ? 1 : 0));
+            dest.writeByte((byte)(mDigital ? 1 : 0));
+            dest.writeInt(mSignalStrength);
+            if (mMetadata == null) {
+                dest.writeByte((byte)0);
+            } else {
+                dest.writeByte((byte)1);
+                mMetadata.writeToParcel(dest, flags);
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "ProgramInfo [mChannel=" + mChannel + ", mSubChannel=" + mSubChannel
+                    + ", mTuned=" + mTuned + ", mStereo=" + mStereo + ", mDigital=" + mDigital
+                    + ", mSignalStrength=" + mSignalStrength
+                    + ((mMetadata == null) ? "" : (", mMetadata=" + mMetadata.toString()))
+                    + "]";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + mChannel;
+            result = prime * result + mSubChannel;
+            result = prime * result + (mTuned ? 1 : 0);
+            result = prime * result + (mStereo ? 1 : 0);
+            result = prime * result + (mDigital ? 1 : 0);
+            result = prime * result + mSignalStrength;
+            result = prime * result + ((mMetadata == null) ? 0 : mMetadata.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!(obj instanceof ProgramInfo))
+                return false;
+            ProgramInfo other = (ProgramInfo) obj;
+            if (mChannel != other.getChannel())
+                return false;
+            if (mSubChannel != other.getSubChannel())
+                return false;
+            if (mTuned != other.isTuned())
+                return false;
+            if (mStereo != other.isStereo())
+                return false;
+            if (mDigital != other.isDigital())
+                return false;
+            if (mSignalStrength != other.getSignalStrength())
+                return false;
+            if (mMetadata == null) {
+                if (other.getMetadata() != null)
+                    return false;
+            } else if (!mMetadata.equals(other.getMetadata()))
+                return false;
+            return true;
+        }
+    }
+
+
+    /**
+     * Returns a list of descriptors for all broadcast radio modules present on the device.
+     * @param modules An List of {@link ModuleProperties} where the list will be returned.
+     * @return
+     * <ul>
+     *  <li>{@link #STATUS_OK} in case of success, </li>
+     *  <li>{@link #STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link #STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link #STATUS_BAD_VALUE} if modules is null, </li>
+     *  <li>{@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails, </li>
+     * </ul>
+     */
+    public native int listModules(List <ModuleProperties> modules);
+
+    /**
+     * Open an interface to control a tuner on a given broadcast radio module.
+     * Optionally selects and applies the configuration passed as "config" argument.
+     * @param moduleId radio module identifier {@link ModuleProperties#getId()}. Mandatory.
+     * @param config desired band and configuration to apply when enabling the hardware module.
+     * optional, can be null.
+     * @param withAudio {@code true} to request a tuner with an audio source.
+     * This tuner is intended for live listening or recording or a radio program.
+     * If {@code false}, the tuner can only be used to retrieve program informations.
+     * @param callback {@link RadioTuner.Callback} interface. Mandatory.
+     * @param handler the Handler on which the callbacks will be received.
+     * Can be null if default handler is OK.
+     * @return a valid {@link RadioTuner} interface in case of success or null in case of error.
+     */
+    public RadioTuner openTuner(int moduleId, BandConfig config, boolean withAudio,
+            RadioTuner.Callback callback, Handler handler) {
+        if (callback == null) {
+            return null;
+        }
+        RadioModule module = new RadioModule(moduleId, config, withAudio, callback, handler);
+        if (module != null) {
+            if (!module.initCheck()) {
+                module = null;
+            }
+        }
+        return (RadioTuner)module;
+    }
+
+    private final Context mContext;
+
+    /**
+     * @hide
+     */
+    public RadioManager(Context context) {
+        mContext = context;
+    }
+}
diff --git a/core/java/android/hardware/radio/RadioMetadata.java b/core/java/android/hardware/radio/RadioMetadata.java
new file mode 100644
index 0000000..8b1851b
--- /dev/null
+++ b/core/java/android/hardware/radio/RadioMetadata.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.radio;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.content.ContentResolver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * Contains meta data about a radio program such as station name, song title, artist etc...
+ * @hide
+ */
+@SystemApi
+public final class RadioMetadata implements Parcelable {
+    private static final String TAG = "RadioMetadata";
+
+    /**
+     * The RDS Program Information.
+     */
+    public static final String METADATA_KEY_RDS_PI = "android.hardware.radio.metadata.RDS_PI";
+
+    /**
+     * The RDS Program Service.
+     */
+    public static final String METADATA_KEY_RDS_PS = "android.hardware.radio.metadata.RDS_PS";
+
+    /**
+     * The RDS PTY.
+     */
+    public static final String METADATA_KEY_RDS_PTY = "android.hardware.radio.metadata.RDS_PTY";
+
+    /**
+     * The RBDS PTY.
+     */
+    public static final String METADATA_KEY_RBDS_PTY = "android.hardware.radio.metadata.RBDS_PTY";
+
+    /**
+     * The RBDS Radio Text.
+     */
+    public static final String METADATA_KEY_RDS_RT = "android.hardware.radio.metadata.RDS_RT";
+
+    /**
+     * The song title.
+     */
+    public static final String METADATA_KEY_TITLE = "android.hardware.radio.metadata.TITLE";
+
+    /**
+     * The artist name.
+     */
+    public static final String METADATA_KEY_ARTIST = "android.hardware.radio.metadata.ARTIST";
+
+    /**
+     * The album name.
+     */
+    public static final String METADATA_KEY_ALBUM = "android.hardware.radio.metadata.ALBUM";
+
+    /**
+     * The music genre.
+     */
+    public static final String METADATA_KEY_GENRE = "android.hardware.radio.metadata.GENRE";
+
+    /**
+     * The radio station icon {@link Bitmap}.
+     */
+    public static final String METADATA_KEY_ICON = "android.hardware.radio.metadata.ICON";
+
+    /**
+     * The artwork for the song/album {@link Bitmap}.
+     */
+    public static final String METADATA_KEY_ART = "android.hardware.radio.metadata.ART";
+
+
+    private static final int METADATA_TYPE_INVALID = -1;
+    private static final int METADATA_TYPE_INT = 0;
+    private static final int METADATA_TYPE_TEXT = 1;
+    private static final int METADATA_TYPE_BITMAP = 2;
+
+    private static final ArrayMap<String, Integer> METADATA_KEYS_TYPE;
+
+    static {
+        METADATA_KEYS_TYPE = new ArrayMap<String, Integer>();
+        METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PI, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PS, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_PTY, METADATA_TYPE_INT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_RBDS_PTY, METADATA_TYPE_INT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_RDS_RT, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ICON, METADATA_TYPE_BITMAP);
+        METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP);
+    }
+
+    // keep in sync with: system/media/radio/include/system/radio_metadata.h
+    private static final int NATIVE_KEY_INVALID     = -1;
+    private static final int NATIVE_KEY_RDS_PI      = 0;
+    private static final int NATIVE_KEY_RDS_PS      = 1;
+    private static final int NATIVE_KEY_RDS_PTY     = 2;
+    private static final int NATIVE_KEY_RBDS_PTY    = 3;
+    private static final int NATIVE_KEY_RDS_RT      = 4;
+    private static final int NATIVE_KEY_TITLE       = 5;
+    private static final int NATIVE_KEY_ARTIST      = 6;
+    private static final int NATIVE_KEY_ALBUM       = 7;
+    private static final int NATIVE_KEY_GENRE       = 8;
+    private static final int NATIVE_KEY_ICON        = 9;
+    private static final int NATIVE_KEY_ART         = 10;
+
+    private static final SparseArray<String> NATIVE_KEY_MAPPING;
+
+    static {
+        NATIVE_KEY_MAPPING = new SparseArray<String>();
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PI, METADATA_KEY_RDS_PI);
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PS, METADATA_KEY_RDS_PS);
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_PTY, METADATA_KEY_RDS_PTY);
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_RBDS_PTY, METADATA_KEY_RBDS_PTY);
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_RDS_RT, METADATA_KEY_RDS_RT);
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_TITLE, METADATA_KEY_TITLE);
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_ARTIST, METADATA_KEY_ARTIST);
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_ALBUM, METADATA_KEY_ALBUM);
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_GENRE, METADATA_KEY_GENRE);
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_ICON, METADATA_KEY_ICON);
+        NATIVE_KEY_MAPPING.put(NATIVE_KEY_ART, METADATA_KEY_ART);
+    }
+
+    private final Bundle mBundle;
+
+    RadioMetadata() {
+        mBundle = new Bundle();
+    }
+
+    private RadioMetadata(Bundle bundle) {
+        mBundle = new Bundle(bundle);
+    }
+
+    private RadioMetadata(Parcel in) {
+        mBundle = in.readBundle();
+    }
+
+    /**
+     * Returns {@code true} if the given key is contained in the meta data
+     *
+     * @param key a String key
+     * @return {@code true} if the key exists in this meta data, {@code false} otherwise
+     */
+    public boolean containsKey(String key) {
+        return mBundle.containsKey(key);
+    }
+
+    /**
+     * Returns the text value associated with the given key as a String, or null
+     * if the key is not found in the meta data.
+     *
+     * @param key The key the value is stored under
+     * @return a String value, or null
+     */
+    public String getString(String key) {
+        return mBundle.getString(key);
+    }
+
+    /**
+     * Returns the value associated with the given key,
+     * or 0 if the key is not found in the meta data.
+     *
+     * @param key The key the value is stored under
+     * @return an int value
+     */
+    public int getInt(String key) {
+        return mBundle.getInt(key, 0);
+    }
+
+    /**
+     * Returns a {@link Bitmap} for the given key or null if the key is not found in the meta data.
+     *
+     * @param key The key the value is stored under
+     * @return a {@link Bitmap} or null
+     */
+    public Bitmap getBitmap(String key) {
+        Bitmap bmp = null;
+        try {
+            bmp = mBundle.getParcelable(key);
+        } catch (Exception e) {
+            // ignore, value was not a bitmap
+            Log.w(TAG, "Failed to retrieve a key as Bitmap.", e);
+        }
+        return bmp;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeBundle(mBundle);
+    }
+
+    /**
+     * Returns the number of fields in this meta data.
+     *
+     * @return the number of fields in the meta data.
+     */
+    public int size() {
+        return mBundle.size();
+    }
+
+    /**
+     * Returns a Set containing the Strings used as keys in this meta data.
+     *
+     * @return a Set of String keys
+     */
+    public Set<String> keySet() {
+        return mBundle.keySet();
+    }
+
+    /**
+     * Helper for getting the String key used by {@link RadioMetadata} from the
+     * corrsponding native integer key.
+     *
+     * @param editorKey The key used by the editor
+     * @return the key used by this class or null if no mapping exists
+     * @hide
+     */
+    public static String getKeyFromNativeKey(int nativeKey) {
+        return NATIVE_KEY_MAPPING.get(nativeKey, null);
+    }
+
+    public static final Parcelable.Creator<RadioMetadata> CREATOR =
+            new Parcelable.Creator<RadioMetadata>() {
+                @Override
+                public RadioMetadata createFromParcel(Parcel in) {
+                    return new RadioMetadata(in);
+                }
+
+                @Override
+                public RadioMetadata[] newArray(int size) {
+                    return new RadioMetadata[size];
+                }
+            };
+
+    /**
+     * Use to build RadioMetadata objects.
+     */
+    public static final class Builder {
+        private final Bundle mBundle;
+
+        /**
+         * Create an empty Builder. Any field that should be included in the
+         * {@link RadioMetadata} must be added.
+         */
+        public Builder() {
+            mBundle = new Bundle();
+        }
+
+        /**
+         * Create a Builder using a {@link RadioMetadata} instance to set the
+         * initial values. All fields in the source meta data will be included in
+         * the new meta data. Fields can be overwritten by adding the same key.
+         *
+         * @param source
+         */
+        public Builder(RadioMetadata source) {
+            mBundle = new Bundle(source.mBundle);
+        }
+
+        /**
+         * Create a Builder using a {@link RadioMetadata} instance to set
+         * initial values, but replace bitmaps with a scaled down copy if they
+         * are larger than maxBitmapSize.
+         *
+         * @param source The original meta data to copy.
+         * @param maxBitmapSize The maximum height/width for bitmaps contained
+         *            in the meta data.
+         * @hide
+         */
+        public Builder(RadioMetadata source, int maxBitmapSize) {
+            this(source);
+            for (String key : mBundle.keySet()) {
+                Object value = mBundle.get(key);
+                if (value != null && value instanceof Bitmap) {
+                    Bitmap bmp = (Bitmap) value;
+                    if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) {
+                        putBitmap(key, scaleBitmap(bmp, maxBitmapSize));
+                    }
+                }
+            }
+        }
+
+        /**
+         * Put a String value into the meta data. Custom keys may be used, but if
+         * the METADATA_KEYs defined in this class are used they may only be one
+         * of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_RDS_PI}</li>
+         * <li>{@link #METADATA_KEY_RDS_PS}</li>
+         * <li>{@link #METADATA_KEY_RDS_RT}</li>
+         * <li>{@link #METADATA_KEY_TITLE}</li>
+         * <li>{@link #METADATA_KEY_ARTIST}</li>
+         * <li>{@link #METADATA_KEY_ALBUM}</li>
+         * <li>{@link #METADATA_KEY_GENRE}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The String value to store
+         * @return the same Builder instance
+         */
+        public Builder putString(String key, String value) {
+            if (!METADATA_KEYS_TYPE.containsKey(key) ||
+                    METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+                throw new IllegalArgumentException("The " + key
+                        + " key cannot be used to put a String");
+            }
+            mBundle.putString(key, value);
+            return this;
+        }
+
+        /**
+         * Put an int value into the meta data. Custom keys may be used, but if
+         * the METADATA_KEYs defined in this class are used they may only be one
+         * of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_RDS_PTY}</li>
+         * <li>{@link #METADATA_KEY_RBDS_PTY}</li>
+         * </ul>
+         *
+         * @param key The key for referencing this value
+         * @param value The int value to store
+         * @return the same Builder instance
+         */
+        public Builder putInt(String key, int value) {
+            if (!METADATA_KEYS_TYPE.containsKey(key) ||
+                    METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_INT) {
+                throw new IllegalArgumentException("The " + key
+                        + " key cannot be used to put a long");
+            }
+            mBundle.putInt(key, value);
+            return this;
+        }
+
+        /**
+         * Put a {@link Bitmap} into the meta data. Custom keys may be used, but
+         * if the METADATA_KEYs defined in this class are used they may only be
+         * one of the following:
+         * <ul>
+         * <li>{@link #METADATA_KEY_ICON}</li>
+         * <li>{@link #METADATA_KEY_ART}</li>
+         * </ul>
+         * <p>
+         *
+         * @param key The key for referencing this value
+         * @param value The Bitmap to store
+         * @return the same Builder instance
+         */
+        public Builder putBitmap(String key, Bitmap value) {
+            if (!METADATA_KEYS_TYPE.containsKey(key) ||
+                    METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+                throw new IllegalArgumentException("The " + key
+                        + " key cannot be used to put a Bitmap");
+            }
+            mBundle.putParcelable(key, value);
+            return this;
+        }
+
+        /**
+         * Creates a {@link RadioMetadata} instance with the specified fields.
+         *
+         * @return a new {@link RadioMetadata} object
+         */
+        public RadioMetadata build() {
+            return new RadioMetadata(mBundle);
+        }
+
+        private Bitmap scaleBitmap(Bitmap bmp, int maxSize) {
+            float maxSizeF = maxSize;
+            float widthScale = maxSizeF / bmp.getWidth();
+            float heightScale = maxSizeF / bmp.getHeight();
+            float scale = Math.min(widthScale, heightScale);
+            int height = (int) (bmp.getHeight() * scale);
+            int width = (int) (bmp.getWidth() * scale);
+            return Bitmap.createScaledBitmap(bmp, width, height, true);
+        }
+    }
+
+    int putIntFromNative(int nativeKey, int value) {
+        String key = getKeyFromNativeKey(nativeKey);
+        if (!METADATA_KEYS_TYPE.containsKey(key) ||
+                METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_INT) {
+            return -1;
+        }
+        mBundle.putInt(key, value);
+        return 0;
+    }
+
+    int putStringFromNative(int nativeKey, String value) {
+        String key = getKeyFromNativeKey(nativeKey);
+        if (!METADATA_KEYS_TYPE.containsKey(key) ||
+                METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) {
+            return -1;
+        }
+        mBundle.putString(key, value);
+        return 0;
+    }
+
+    int putBitmapFromNative(int nativeKey, byte[] value) {
+        String key = getKeyFromNativeKey(nativeKey);
+        if (!METADATA_KEYS_TYPE.containsKey(key) ||
+                METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) {
+            return -1;
+        }
+        Bitmap bmp = null;
+        try {
+            bmp = BitmapFactory.decodeByteArray(value, 0, value.length);
+        } catch (Exception e) {
+        } finally {
+            if (bmp == null) {
+                return -1;
+            }
+            mBundle.putParcelable(key, bmp);
+            return 0;
+        }
+    }
+}
diff --git a/core/java/android/hardware/radio/RadioModule.java b/core/java/android/hardware/radio/RadioModule.java
new file mode 100644
index 0000000..15916ae
--- /dev/null
+++ b/core/java/android/hardware/radio/RadioModule.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import java.lang.ref.WeakReference;
+import java.util.UUID;
+
+/**
+ * A RadioModule implements the RadioTuner interface for a broadcast radio tuner physically
+ * present on the device and exposed by the radio HAL.
+ *
+ * @hide
+ */
+public class RadioModule extends RadioTuner {
+    private long mNativeContext = 0;
+    private int mId;
+    private NativeEventHandlerDelegate mEventHandlerDelegate;
+
+    RadioModule(int moduleId, RadioManager.BandConfig config, boolean withAudio,
+            RadioTuner.Callback callback, Handler handler) {
+        mId = moduleId;
+        mEventHandlerDelegate = new NativeEventHandlerDelegate(callback, handler);
+        native_setup(new WeakReference<RadioModule>(this), config, withAudio);
+    }
+    private native void native_setup(Object module_this,
+            RadioManager.BandConfig config, boolean withAudio);
+
+    @Override
+    protected void finalize() {
+        native_finalize();
+    }
+    private native void native_finalize();
+
+    boolean initCheck() {
+        return mNativeContext != 0;
+    }
+
+    // RadioTuner implementation
+    public native void close();
+
+    public native int setConfiguration(RadioManager.BandConfig config);
+
+    public native int getConfiguration(RadioManager.BandConfig[] config);
+
+    public native int setMute(boolean mute);
+
+    public native boolean getMute();
+
+    public native int step(int direction, boolean skipSubChannel);
+
+    public native int scan(int direction, boolean skipSubChannel);
+
+    public native int tune(int channel, int subChannel);
+
+    public native int cancel();
+
+    public native int getProgramInformation(RadioManager.ProgramInfo[] info);
+
+    public native boolean isAntennaConnected();
+
+    public native boolean hasControl();
+
+
+    /* keep in sync with radio_event_type_t in system/core/include/system/radio.h */
+    static final int EVENT_HW_FAILURE = 0;
+    static final int EVENT_CONFIG = 1;
+    static final int EVENT_ANTENNA = 2;
+    static final int EVENT_TUNED = 3;
+    static final int EVENT_METADATA = 4;
+    static final int EVENT_TA = 5;
+    static final int EVENT_AF_SWITCH = 6;
+    static final int EVENT_CONTROL = 100;
+    static final int EVENT_SERVER_DIED = 101;
+
+    private class NativeEventHandlerDelegate {
+        private final Handler mHandler;
+
+        NativeEventHandlerDelegate(final RadioTuner.Callback callback,
+                                   Handler handler) {
+            // find the looper for our new event handler
+            Looper looper;
+            if (handler != null) {
+                looper = handler.getLooper();
+            } else {
+                looper = Looper.getMainLooper();
+            }
+
+            // construct the event handler with this looper
+            if (looper != null) {
+                // implement the event handler delegate
+                mHandler = new Handler(looper) {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        switch (msg.what) {
+                        case EVENT_HW_FAILURE:
+                            if (callback != null) {
+                                callback.onError(RadioTuner.ERROR_HARDWARE_FAILURE);
+                            }
+                            break;
+                        case EVENT_CONFIG: {
+                            RadioManager.BandConfig config = (RadioManager.BandConfig)msg.obj;
+                            switch(msg.arg1) {
+                            case RadioManager.STATUS_OK:
+                                if (callback != null) {
+                                    callback.onConfigurationChanged(config);
+                                }
+                                break;
+                            default:
+                                if (callback != null) {
+                                    callback.onError(RadioTuner.ERROR_CONFIG);
+                                }
+                                break;
+                            }
+                        } break;
+                        case EVENT_ANTENNA:
+                            if (callback != null) {
+                                callback.onAntennaState(msg.arg2 == 1);
+                            }
+                            break;
+                        case EVENT_AF_SWITCH:
+                        case EVENT_TUNED: {
+                            RadioManager.ProgramInfo info = (RadioManager.ProgramInfo)msg.obj;
+                            switch (msg.arg1) {
+                            case RadioManager.STATUS_OK:
+                                if (callback != null) {
+                                    callback.onProgramInfoChanged(info);
+                                }
+                                break;
+                            case RadioManager.STATUS_TIMED_OUT:
+                                if (callback != null) {
+                                    callback.onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+                                }
+                                break;
+                            case RadioManager.STATUS_INVALID_OPERATION:
+                            default:
+                                if (callback != null) {
+                                    callback.onError(RadioTuner.ERROR_CANCELLED);
+                                }
+                                break;
+                            }
+                        } break;
+                        case EVENT_METADATA: {
+                            RadioMetadata metadata = (RadioMetadata)msg.obj;
+                            if (callback != null) {
+                                callback.onMetadataChanged(metadata);
+                            }
+                        } break;
+                        case EVENT_TA:
+                            if (callback != null) {
+                                callback.onTrafficAnnouncement(msg.arg2 == 1);
+                            }
+                            break;
+                        case EVENT_CONTROL:
+                            if (callback != null) {
+                                callback.onControlChanged(msg.arg2 == 1);
+                            }
+                            break;
+                        case EVENT_SERVER_DIED:
+                            if (callback != null) {
+                                callback.onError(RadioTuner.ERROR_SERVER_DIED);
+                            }
+                            break;
+                        default:
+                            // Should not happen
+                            break;
+                        }
+                    }
+                };
+            } else {
+                mHandler = null;
+            }
+        }
+
+        Handler handler() {
+            return mHandler;
+        }
+    }
+
+
+    @SuppressWarnings("unused")
+    private static void postEventFromNative(Object module_ref,
+                                            int what, int arg1, int arg2, Object obj) {
+        RadioModule module = (RadioModule)((WeakReference)module_ref).get();
+        if (module == null) {
+            return;
+        }
+
+        NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate;
+        if (delegate != null) {
+            Handler handler = delegate.handler();
+            if (handler != null) {
+                Message m = handler.obtainMessage(what, arg1, arg2, obj);
+                handler.sendMessage(m);
+            }
+        }
+    }
+}
+
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
new file mode 100644
index 0000000..376900a
--- /dev/null
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.radio;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import java.lang.ref.WeakReference;
+import java.util.UUID;
+
+/**
+ * RadioTuner interface provides methods to control a radio tuner on the device: selecting and
+ * configuring the active band, muting/unmuting, scanning and tuning, etc...
+ *
+ * Obtain a RadioTuner interface by calling {@link RadioManager#openTuner(int,
+ * RadioManager.BandConfig, boolean, RadioTuner.Callback, Handler)}.
+ * @hide
+ */
+@SystemApi
+public abstract class RadioTuner {
+
+    /** Scanning direction UP for {@link #step(int, boolean)}, {@link #scan(int, boolean)} */
+    public static final int DIRECTION_UP      = 0;
+
+    /** Scanning directions DOWN for {@link #step(int, boolean)}, {@link #scan(int, boolean)} */
+    public static final int DIRECTION_DOWN    = 1;
+
+    /**
+     * Close the tuner interface. The {@link Callback} callback will not be called
+     * anymore and associated resources will be released.
+     * Must be called when the tuner is not needed to make hardware resources available to others.
+     * */
+    public abstract void close();
+
+    /**
+     * Set the active band configuration for this module.
+     * Must be a valid configuration obtained via buildConfig() from a valid BandDescriptor listed
+     * in the ModuleProperties of the module with the specified ID.
+     * @param config The desired band configuration (FmBandConfig or AmBandConfig).
+     * @return
+     * <ul>
+     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *  service fails, </li>
+     * </ul>
+     */
+    public abstract int setConfiguration(RadioManager.BandConfig config);
+
+    /**
+     * Get current configuration.
+     * @param config a BandConfig array of lengh 1 where the configuration is returned.
+     * @return
+     * <ul>
+     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *  service fails, </li>
+     * </ul>
+     */
+    public abstract int getConfiguration(RadioManager.BandConfig[] config);
+
+
+    /**
+     * Set mute state. When muted, the radio tuner audio source is not available for playback on
+     * any audio device. when unmuted, the radio tuner audio source is output as a media source
+     * and renderd over the audio device selected for media use case.
+     * The radio tuner audio source is muted by default when the tuner is first attached.
+     * Only effective if the tuner is attached with audio enabled.
+     *
+     * @param mute the requested mute state.
+     * @return
+     * <ul>
+     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *  service fails, </li>
+     * </ul>
+     */
+    public abstract int setMute(boolean mute);
+
+    /**
+     * Get mute state.
+     *
+     * @return {@code true} if the radio tuner audio source is muted or a problem occured
+     * retrieving the mute state, {@code false} otherwise.
+     */
+    public abstract boolean getMute();
+
+    /**
+     * Step up or down by one channel spacing.
+     * The operation is asynchronous and {@link Callback}
+     * onProgramInfoChanged() will be called when step completes or
+     * onError() when cancelled or timeout.
+     * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
+     * @param skipSubChannel indicates to skip sub channels when the configuration currently
+     * selected supports sub channel (e.g HD Radio). N/A otherwise.
+     * @return
+     * <ul>
+     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *  service fails, </li>
+     * </ul>
+     */
+    public abstract int step(int direction, boolean skipSubChannel);
+
+    /**
+     * Scan up or down to next valid station.
+     * The operation is asynchronous and {@link Callback}
+     * onProgramInfoChanged() will be called when scan completes or
+     * onError() when cancelled or timeout.
+     * @param direction {@link #DIRECTION_UP} or {@link #DIRECTION_DOWN}.
+     * @param skipSubChannel indicates to skip sub channels when the configuration currently
+     * selected supports sub channel (e.g HD Radio). N/A otherwise.
+     * @return
+     * <ul>
+     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *  service fails, </li>
+     * </ul>
+     */
+    public abstract int scan(int direction, boolean skipSubChannel);
+
+    /**
+     * Tune to a specific frequency.
+     * The operation is asynchronous and {@link Callback}
+     * onProgramInfoChanged() will be called when tune completes or
+     * onError() when cancelled or timeout.
+     * @param channel the specific channel or frequency to tune to.
+     * @param subChannel the specific sub-channel to tune to. N/A if the selected configuration
+     * does not support cub channels.
+     * @return
+     * <ul>
+     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *  service fails, </li>
+     * </ul>
+     */
+    public abstract int tune(int channel, int subChannel);
+
+    /**
+     * Cancel a pending scan or tune operation.
+     * If an operation is pending, {@link Callback} onError() will be called with
+     * {@link #ERROR_CANCELLED}.
+     * @return
+     * <ul>
+     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *  service fails, </li>
+     * </ul>
+     */
+    public abstract int cancel();
+
+    /**
+     * Get current station information.
+     * @param info a ProgramInfo array of lengh 1 where the information is returned.
+     * @return
+     * <ul>
+     *  <li>{@link RadioManager#STATUS_OK} in case of success, </li>
+     *  <li>{@link RadioManager#STATUS_ERROR} in case of unspecified error, </li>
+     *  <li>{@link RadioManager#STATUS_NO_INIT} if the native service cannot be reached, </li>
+     *  <li>{@link RadioManager#STATUS_BAD_VALUE} if parameters are invalid, </li>
+     *  <li>{@link RadioManager#STATUS_INVALID_OPERATION} if the call is out of sequence, </li>
+     *  <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
+     *  service fails, </li>
+     * </ul>
+     */
+    public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);
+
+    /**
+     * Get current antenna connection state for current configuration.
+     * Only valid if a configuration has been applied.
+     * @return {@code true} if the antenna is connected, {@code false} otherwise.
+     */
+    public abstract boolean isAntennaConnected();
+
+    /**
+     * Indicates if this client actually controls the tuner.
+     * Control is always granted after
+     * {@link RadioManager#openTuner(int,
+     * RadioManager.BandConfig, boolean, Callback, Handler)}
+     * returns a non null tuner interface.
+     * Control is lost when another client opens an interface on the same tuner.
+     * When this happens, {@link Callback#onControlChanged(boolean)} is received.
+     * The client can either wait for control to be returned (which is indicated by the same
+     * callback) or close and reopen the tuner interface.
+     * @return {@code true} if this interface controls the tuner,
+     * {@code false} otherwise or if a problem occured retrieving the state.
+     */
+    public abstract boolean hasControl();
+
+    /** Indicates a failure of radio IC or driver.
+     * The application must close and re open the tuner */
+    public static final int ERROR_HARDWARE_FAILURE = 0;
+    /** Indicates a failure of the radio service.
+     * The application must close and re open the tuner */
+    public static final  int ERROR_SERVER_DIED = 1;
+    /** A pending seek or tune operation was cancelled */
+    public static final  int ERROR_CANCELLED = 2;
+    /** A pending seek or tune operation timed out */
+    public static final  int ERROR_SCAN_TIMEOUT = 3;
+    /** The requested configuration could not be applied */
+    public static final  int ERROR_CONFIG = 4;
+
+    /**
+     * Callback provided by the client application when opening a {@link RadioTuner}
+     * to receive asynchronous operation results, updates and error notifications.
+     */
+    public static abstract class Callback {
+        /**
+         * onError() is called when an error occured while performing an asynchronous
+         * operation of when the hardware or system service experiences a problem.
+         * status is one of {@link #ERROR_HARDWARE_FAILURE}, {@link #ERROR_SERVER_DIED},
+         * {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT},
+         * {@link #ERROR_CONFIG}
+         */
+        public void onError(int status) {}
+        /**
+         * onConfigurationChanged() is called upon successful completion of
+         * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)}
+         * or {@link RadioTuner#setConfiguration(RadioManager.BandConfig)}
+         */
+        public void onConfigurationChanged(RadioManager.BandConfig config) {}
+        /**
+         * onProgramInfoChanged() is called upon successful completion of
+         * {@link RadioTuner#step(int, boolean)}, {@link RadioTuner#scan(int, boolean)},
+         * {@link RadioTuner#tune(int, int)} or when a switching to alternate frequency occurs.
+         * Note that if metadata only are updated,  {@link #onMetadataChanged(RadioMetadata)} will
+         * be called.
+         */
+        public void onProgramInfoChanged(RadioManager.ProgramInfo info) {}
+        /**
+         * onMetadataChanged() is called when new meta data are received on current program.
+         * Meta data are also received in {@link RadioManager.ProgramInfo} when
+         *  {@link #onProgramInfoChanged(RadioManager.ProgramInfo)} is called.
+         */
+        public void onMetadataChanged(RadioMetadata metadata) {}
+        /**
+         * onTrafficAnnouncement() is called when a traffic announcement starts and stops.
+         */
+        public void onTrafficAnnouncement(boolean active) {}
+        /**
+         * onAntennaState() is called when the antenna is connected or disconnected.
+         */
+        public void onAntennaState(boolean connected) {}
+        /**
+         * onControlChanged() is called when the client loses or gains control of the radio tuner.
+         * The control is always granted after a successful call to
+         * {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)}.
+         * If another client opens the same tuner, onControlChanged() will be called with
+         * control set to {@code false} to indicate loss of control.
+         * At this point, RadioTuner APIs other than getters will return
+         * {@link RadioManager#STATUS_INVALID_OPERATION}.
+         * When the other client releases the tuner, onControlChanged() will be called
+         * with control set to {@code true}.
+         */
+        public void onControlChanged(boolean control) {}
+    }
+
+}
+